diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index 9774468e163..85dc082ff6c 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -195,23 +195,32 @@ public Geometry? Clip
///
/// Gets a value indicating whether this control and all its parents are visible.
///
- public bool IsEffectivelyVisible
+ public bool IsEffectivelyVisible { get; private set; } = true;
+
+ ///
+ /// Updates the property based on the parent's
+ /// .
+ ///
+ /// The effective visibility of the parent control.
+ private void UpdateIsEffectivelyVisible(bool parentState)
{
- get
- {
- Visual? node = this;
+ var isEffectivelyVisible = parentState && IsVisible;
- while (node != null)
- {
- if (!node.IsVisible)
- {
- return false;
- }
+ if (IsEffectivelyVisible == isEffectivelyVisible)
+ return;
- node = node.VisualParent;
- }
+ IsEffectivelyVisible = isEffectivelyVisible;
- return true;
+ // PERF-SENSITIVE: This is called on entire hierarchy and using foreach or LINQ
+ // will cause extra allocations and overhead.
+
+ var children = VisualChildren;
+
+ // ReSharper disable once ForCanBeConvertedToForeach
+ for (int i = 0; i < children.Count; ++i)
+ {
+ var child = children[i];
+ child.UpdateIsEffectivelyVisible(isEffectivelyVisible);
}
}
@@ -453,7 +462,11 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
{
base.OnPropertyChanged(change);
- if (change.Property == FlowDirectionProperty)
+ if (change.Property == IsVisibleProperty)
+ {
+ UpdateIsEffectivelyVisible(VisualParent?.IsEffectivelyVisible ?? true);
+ }
+ else if (change.Property == FlowDirectionProperty)
{
InvalidateMirrorTransform();
@@ -463,7 +476,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
}
}
}
-
+
protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
base.LogicalChildrenCollectionChanged(sender, e);
@@ -492,14 +505,16 @@ protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs
AttachToCompositor(compositingRenderer.Compositor);
}
InvalidateMirrorTransform();
+ UpdateIsEffectivelyVisible(_visualParent!.IsEffectivelyVisible);
OnAttachedToVisualTree(e);
AttachedToVisualTree?.Invoke(this, e);
InvalidateVisual();
+
_visualRoot.Renderer.RecalculateChildren(_visualParent!);
-
- if (ZIndex != 0 && VisualParent is Visual parent)
- parent.HasNonUniformZIndexChildren = true;
-
+
+ if (ZIndex != 0 && _visualParent is { })
+ _visualParent.HasNonUniformZIndexChildren = true;
+
var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count;
@@ -529,6 +544,7 @@ protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArg
}
DisableTransitions();
+ UpdateIsEffectivelyVisible(true);
OnDetachedFromVisualTree(e);
DetachFromCompositor();
diff --git a/tests/Avalonia.Base.UnitTests/VisualTests.cs b/tests/Avalonia.Base.UnitTests/VisualTests.cs
index 0150a067152..044793007d2 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTests.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTests.cs
@@ -349,5 +349,108 @@ public void Changing_ZIndex_Should_Recalculate_Parent_Children()
renderer.Verify(x => x.RecalculateChildren(stackPanel));
}
+
+ [Theory]
+ [InlineData(new[] { 1, 2, 3 }, true, true, true, true, true, true)]
+ [InlineData(new[] { 3, 2, 1 }, true, true, true, true, true, true)]
+ [InlineData(new[] { 1 }, false, true, true, false, false, false)]
+ [InlineData(new[] { 2 }, true, false, true, true, false, false)]
+ [InlineData(new[] { 3 }, true, true, false, true, true, false)]
+ [InlineData(new[] { 3, 1}, true, true, false, true, true, false)]
+
+ [InlineData(new[] { 2, 3, 1 }, true, false, true, true, false, false, true)]
+ [InlineData(new[] { 3, 1, 2 }, true, true, false, true, true, false, true)]
+ [InlineData(new[] { 3, 2, 1 }, true, true, false, true, true, false, true)]
+
+ public void IsEffectivelyVisible_Propagates_To_Visual_Children(int[] assignOrder, bool rootV, bool child1V,
+ bool child2V, bool rootExpected, bool child1Expected, bool child2Expected, bool initialSetToFalse = false)
+ {
+ var child2 = new Decorator();
+ var child1 = new Decorator { Child = child2 };
+ var root = new TestRoot { Child = child1 };
+
+ Assert.True(child2.IsEffectivelyVisible);
+
+ if (initialSetToFalse)
+ {
+ root.IsVisible = false;
+ child1.IsVisible = false;
+ child2.IsVisible = false;
+ }
+
+ foreach (var order in assignOrder)
+ {
+ switch (order)
+ {
+ case 1:
+ root.IsVisible = rootV;
+ break;
+ case 2:
+ child1.IsVisible = child1V;
+ break;
+ case 3:
+ child2.IsVisible = child2V;
+ break;
+ }
+ }
+
+ Assert.Equal(rootExpected, root.IsEffectivelyVisible);
+ Assert.Equal(child1Expected, child1.IsEffectivelyVisible);
+ Assert.Equal(child2Expected, child2.IsEffectivelyVisible);
+ }
+
+ [Fact]
+ public void Added_Child_Has_Correct_IsEffectivelyVisible()
+ {
+ var root = new TestRoot { IsVisible = false };
+ var child = new Decorator();
+
+ root.Child = child;
+ Assert.False(child.IsEffectivelyVisible);
+ }
+
+ [Fact]
+ public void Added_Grandchild_Has_Correct_IsEffectivelyVisible()
+ {
+ var child = new Decorator();
+ var grandchild = new Decorator();
+ var root = new TestRoot
+ {
+ IsVisible = false,
+ Child = child
+ };
+
+ child.Child = grandchild;
+ Assert.False(grandchild.IsEffectivelyVisible);
+ }
+
+ [Fact]
+ public void Removing_Child_Resets_IsEffectivelyVisible()
+ {
+ var child = new Decorator();
+ var root = new TestRoot { Child = child, IsVisible = false };
+
+ Assert.False(child.IsEffectivelyVisible);
+
+ root.Child = null;
+
+ Assert.True(child.IsEffectivelyVisible);
+ }
+
+ [Fact]
+ public void Removing_Child_Resets_IsEffectivelyVisible_Of_Grandchild()
+ {
+ var grandchild = new Decorator();
+ var child = new Decorator { Child = grandchild };
+ var root = new TestRoot { Child = child, IsVisible = false };
+
+ Assert.False(child.IsEffectivelyVisible);
+ Assert.False(grandchild.IsEffectivelyVisible);
+
+ root.Child = null;
+
+ Assert.True(child.IsEffectivelyVisible);
+ Assert.True(grandchild.IsEffectivelyVisible);
+ }
}
}