Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track IsEffectivelyVisible state #13972

Merged
merged 11 commits into from
Jan 23, 2024
54 changes: 35 additions & 19 deletions src/Avalonia.Base/Visual.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,23 +195,32 @@ public Geometry? Clip
/// <summary>
/// Gets a value indicating whether this control and all its parents are visible.
/// </summary>
public bool IsEffectivelyVisible
public bool IsEffectivelyVisible { get; private set; } = true;

/// <summary>
/// Updates the <see cref="IsEffectivelyVisible"/> property based on the parent's
/// <see cref="IsEffectivelyVisible"/>.
/// </summary>
/// <param name="parentState">The effective visibility of the parent control.</param>
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);
}
}

Expand Down Expand Up @@ -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();

Expand All @@ -463,7 +476,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
}
}
}

protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
base.LogicalChildrenCollectionChanged(sender, e);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -529,6 +544,7 @@ protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArg
}

DisableTransitions();
UpdateIsEffectivelyVisible(true);
OnDetachedFromVisualTree(e);
DetachFromCompositor();

Expand Down
103 changes: 103 additions & 0 deletions tests/Avalonia.Base.UnitTests/VisualTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Loading