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
66 changes: 49 additions & 17 deletions src/Avalonia.Base/Visual.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,26 +195,42 @@ 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="parent">The parent control.</param>
jmacato marked this conversation as resolved.
Show resolved Hide resolved
private void UpdateIsEffectivelyVisible(bool state)
{
get
// Default to true if there's no Visual parent.
IsEffectivelyVisible = state && (_visualParent?.IsVisible ?? 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)
{
Visual? node = this;
var child = children[i];

while (node != null)
// Don't override IsEffectivelyVisible
// if it's already set on the child visual
if (child.IsVisible)
{
if (!node.IsVisible)
{
return false;
}

node = node.VisualParent;
child.UpdateIsEffectivelyVisible(state);
}

return true;
}
}

private static void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
{
((Visual)e.Sender).UpdateIsEffectivelyVisible(e.GetNewValue<bool>());
}

/// <summary>
/// Gets or sets a value indicating whether this control is visible.
/// </summary>
Expand Down Expand Up @@ -453,7 +469,11 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
{
base.OnPropertyChanged(change);

if (change.Property == FlowDirectionProperty)
if (change.Property == IsVisibleProperty)
{
IsVisibleChanged(change);
jmacato marked this conversation as resolved.
Show resolved Hide resolved
}
else if (change.Property == FlowDirectionProperty)
{
InvalidateMirrorTransform();

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

protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
base.LogicalChildrenCollectionChanged(sender, e);
Expand Down Expand Up @@ -495,10 +515,13 @@ protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs
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;

SyncIsEffectivelyVisible(_visualParent);
jmacato marked this conversation as resolved.
Show resolved Hide resolved

var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count;
Expand All @@ -512,6 +535,13 @@ protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs
}
}

private void SyncIsEffectivelyVisible(Visual? parent)
{
// Default to true if there's no Visual parent.
var vis = (parent?.IsVisible ?? true) && IsVisible;
UpdateIsEffectivelyVisible(vis);
}

/// <summary>
/// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// for this control and all of its visual descendants.
Expand All @@ -535,6 +565,8 @@ protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArg
DetachedFromVisualTree?.Invoke(this, e);
e.Root.Renderer.AddDirty(this);

SyncIsEffectivelyVisible(_visualParent);

var visualChildren = VisualChildren;
var visualChildrenCount = visualChildren.Count;

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