Skip to content

Apply autosize continuously instead of relying on transforms #6566

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

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osuTK;
using osuTK.Graphics;

Expand Down Expand Up @@ -89,6 +90,91 @@
AddUntilStep("container still autosized", () => container.Size == new Vector2(100));
}

[Test]
public void TestAutoSizeDuration()
{
Container parent = null;
Drawable child = null;

AddStep("create hierarchy", () =>
{
Child = parent = new Container
{
Masking = true,
AutoSizeAxes = Axes.Both,
AutoSizeDuration = 500,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Yellow,
},
new Container
{
Padding = new MarginPadding(50),
AutoSizeAxes = Axes.Both,
Child = child = new Box
{
Size = new Vector2(100),
Colour = Color4.Red,
}
}
}
};
});

AddSliderStep("AutoSizeDuration", 0f, 1500f, 500f, value =>
{
if (parent != null) parent.AutoSizeDuration = value;
});
AddSliderStep("Width", 0f, 300f, 100f, value =>
{
if (child != null) child.Width = value;
});
AddSliderStep("Height", 0f, 300f, 100f, value =>
{
if (child != null) child.Height = value;
});
}

[Test]
public void TestFinishAutoSizeTransforms()
{
Container parent = null;
Drawable child = null;

AddStep("create hierarchy", () =>
{
Child = parent = new Container
{
Masking = true,
AutoSizeAxes = Axes.Both,
AutoSizeDuration = 1000,
Name = "Parent",
Children = new Drawable[]

Check failure on line 155 in osu.Framework.Tests/Visual/Containers/TestSceneCompositeDrawable.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Redundant explicit array type specification in osu.Framework.Tests\Visual\Containers\TestSceneCompositeDrawable.cs on line 155
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Yellow,
},
child = new Box
{
Size = new Vector2(100),
Colour = Color4.Red,
Alpha = 0.5f,
}
}
};
});
AddAssert("size matches child", () => Precision.AlmostEquals(parent.ChildSize, child.LayoutSize));
AddStep("resize child", () => child.Size = new Vector2(200));
AddAssert("size doesn't match child", () => !Precision.AlmostEquals(parent.ChildSize, child.LayoutSize));
AddStep("finish autosize transform", () => parent.FinishAutoSizeTransforms());
AddAssert("size matches child", () => Precision.AlmostEquals(parent.ChildSize, child.LayoutSize));
}

private partial class SortableComposite : CompositeDrawable
{
public SortableComposite()
Expand Down
47 changes: 5 additions & 42 deletions osu.Framework.Tests/Visual/Layout/TestSceneLayoutDurations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ namespace osu.Framework.Tests.Visual.Layout
public partial class TestSceneLayoutDurations : FrameworkTestScene
{
private ManualClock manualClock;
private Container autoSizeContainer;
private FillFlowContainer fillFlowContainer;

private Box box1, box2;
private Box box;

private const float duration = 1000;

Expand All @@ -34,24 +33,6 @@ public void SetUp() => Schedule(() =>

Children = new Drawable[]
{
autoSizeContainer = new Container
{
Clock = new FramedClock(manualClock),
AutoSizeEasing = Easing.None,
Children = new[]
{
new Box
{
Colour = Color4.Red,
RelativeSizeAxes = Axes.Both
},
box1 = new Box
{
Colour = Color4.Transparent,
Size = Vector2.Zero,
},
}
},
fillFlowContainer = new FillFlowContainer
{
Clock = new FramedClock(manualClock),
Expand All @@ -60,27 +41,20 @@ public void SetUp() => Schedule(() =>
Children = new Drawable[]
{
new Box { Colour = Color4.Red, Size = new Vector2(100) },
box2 = new Box { Colour = Color4.Blue, Size = new Vector2(100) },
box = new Box { Colour = Color4.Blue, Size = new Vector2(100) },
}
}
};

paused = false;
autoSizeContainer.FinishTransforms();
fillFlowContainer.FinishTransforms();

autoSizeContainer.AutoSizeAxes = Axes.None;
autoSizeContainer.AutoSizeDuration = 0;
autoSizeContainer.Size = Vector2.Zero;
box1.Size = Vector2.Zero;

fillFlowContainer.LayoutDuration = 0;
fillFlowContainer.Size = new Vector2(200, 200);
});

private void check(float ratio) =>
AddAssert($"Check @{ratio}", () => Precision.AlmostEquals(autoSizeContainer.Size, new Vector2(changed_value * ratio)) &&
Precision.AlmostEquals(box2.Position, new Vector2(changed_value * (1 - ratio), changed_value * ratio)));
AddAssert($"Check @{ratio}", () => Precision.AlmostEquals(box.Position, new Vector2(changed_value * (1 - ratio), changed_value * ratio)));

private void skipTo(float ratio) => AddStep($"skip to {ratio}", () => { manualClock.CurrentTime = duration * ratio; });

Expand All @@ -91,13 +65,8 @@ public void TestChangeAfterDuration()
{
paused = true;
manualClock.CurrentTime = 0;
autoSizeContainer.FinishTransforms();
fillFlowContainer.FinishTransforms();

autoSizeContainer.AutoSizeAxes = Axes.Both;
autoSizeContainer.AutoSizeDuration = duration;
box1.Size = new Vector2(100);

fillFlowContainer.LayoutDuration = duration;
fillFlowContainer.Width = 100;
});
Expand All @@ -116,14 +85,11 @@ public void TestInterruptExistingDuration()
{
paused = true;
manualClock.CurrentTime = 0;
autoSizeContainer.FinishTransforms();
fillFlowContainer.FinishTransforms();

autoSizeContainer.AutoSizeAxes = Axes.Both;
autoSizeContainer.AutoSizeDuration = duration;
fillFlowContainer.LayoutDuration = duration;

box1.Size = new Vector2(changed_value);
box.Size = new Vector2(changed_value);
fillFlowContainer.Width = changed_value;
});

Expand All @@ -132,7 +98,6 @@ public void TestInterruptExistingDuration()

AddStep("set duration 0", () =>
{
autoSizeContainer.AutoSizeDuration = 0;
fillFlowContainer.LayoutDuration = 0;
});

Expand All @@ -146,7 +111,6 @@ public void TestInterruptExistingDuration()

AddStep("alter values", () =>
{
box1.Size = new Vector2(0);
fillFlowContainer.Width = 200;
});

Expand All @@ -162,11 +126,10 @@ public void TestInterruptExistingDuration()

protected override void Update()
{
if (autoSizeContainer != null)
if (fillFlowContainer != null)
{
if (!paused) manualClock.CurrentTime = Clock.CurrentTime;

autoSizeContainer.Children[0].Invalidate();
fillFlowContainer.Invalidate();
}

Expand Down
89 changes: 56 additions & 33 deletions osu.Framework/Graphics/Containers/CompositeDrawable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using osu.Framework.Statistics;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Caching;
using osu.Framework.Development;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Extensions.ExceptionExtensions;
Expand Down Expand Up @@ -945,6 +946,7 @@ public override bool UpdateSubTree()
UpdateAfterChildren();

updateChildrenSizeDependencies();
applyAutoSize();
UpdateAfterAutoSize();
return true;
}
Expand Down Expand Up @@ -1813,17 +1815,11 @@ protected set
}

/// <summary>
/// The duration which automatic sizing should take. If zero, then it is instantaneous.
/// Otherwise, this is equivalent to applying an automatic size via a resize transform.
/// The duration which automatic sizing should approximately take. If zero, then it is instantaneous.
/// AutoSize is being applied continuously so the actual amount of time taken depends on the overall change in value.
/// </summary>
public float AutoSizeDuration { get; protected set; }

/// <summary>
/// The type of easing which should be used for smooth automatic sizing when <see cref="AutoSizeDuration"/>
/// is non-zero.
/// </summary>
public Easing AutoSizeEasing { get; protected set; }

/// <summary>
/// Fired after this <see cref="CompositeDrawable"/>'s <see cref="Size"/> is updated through autosize.
/// </summary>
Expand Down Expand Up @@ -1938,14 +1934,15 @@ private Vector2 computeAutoSize()
private void updateAutoSize()
{
if (AutoSizeAxes == Axes.None)
{
targetAutoSize.Invalidate();
return;
}

Vector2 b = computeAutoSize() + Padding.Total;
targetAutoSize.Value = computeAutoSize() + Padding.Total;

autoSizeResizeTo(new Vector2(
AutoSizeAxes.HasFlagFast(Axes.X) ? b.X : base.Width,
AutoSizeAxes.HasFlagFast(Axes.Y) ? b.Y : base.Height
), AutoSizeDuration, AutoSizeEasing);
if (!didInitialAutoSize || AutoSizeDuration <= 0)
autoSizeResizeTo(targetAutoSize.Value, 0);

//note that this is called before autoSize becomes valid. may be something to consider down the line.
//might work better to add an OnRefresh event in Cached<> and invoke there.
Expand All @@ -1970,25 +1967,59 @@ private void updateChildrenSizeDependencies()
}
}

private void autoSizeResizeTo(Vector2 newSize, double duration = 0, Easing easing = Easing.None)
private void applyAutoSize()
{
if (targetAutoSize.IsValid)
autoSizeResizeTo(targetAutoSize.Value, AutoSizeDuration);

didInitialAutoSize = true;
}

private void autoSizeResizeTo(Vector2 targetSize, double duration)
{
var currentTransform = TransformsForTargetMember(nameof(baseSize)).FirstOrDefault() as AutoSizeTransform;
targetSize = new Vector2(
AutoSizeAxes.HasFlagFast(Axes.X) ? targetSize.X : base.Width,
AutoSizeAxes.HasFlagFast(Axes.Y) ? targetSize.Y : base.Height
);

if ((currentTransform?.EndValue ?? Size) != newSize)
if (duration <= 0)
{
if (duration == 0)
{
if (currentTransform != null)
ClearTransforms(false, nameof(baseSize));
baseSize = newSize;
}
else
this.TransformTo(this.PopulateTransform(new AutoSizeTransform { Rewindable = false }, newSize, duration, easing));
baseSize = targetSize;
targetAutoSize.Invalidate();
return;
}

Vector2 newSize = Interpolation.DampContinuously(baseSize, targetSize, duration / 4, Time.Elapsed);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duration / 4 value is kind of eyeballed here, it will bring the current value within 0.4% of the target value after the given duration has passed. When testing this the resulting animation roughly landed in the right ballpark for most cases.


if (Precision.AlmostEquals(newSize, targetSize, 0.5f))
{
newSize = targetSize;
targetAutoSize.Invalidate();
}

baseSize = newSize;
}

/// <summary>
/// A helper property for <see cref="autoSizeResizeTo(Vector2, double, Easing)"/> to change the size of <see cref="CompositeDrawable"/>s with <see cref="AutoSizeAxes"/>.
/// Immediately resizes to the current target size if <see cref="AutoSizeDuration"/> is non-zero.
/// </summary>
protected void FinishAutoSizeTransforms()
{
updateChildrenSizeDependencies();

if (targetAutoSize.IsValid)
autoSizeResizeTo(targetAutoSize.Value, 0);
}

/// <summary>
/// When valid, holds the current target size that should be approached when using automatic sizing and <see cref="AutoSizeDuration"/> is non-zero.
/// </summary>
private readonly Cached<Vector2> targetAutoSize = new Cached<Vector2>();

private bool didInitialAutoSize;

/// <summary>
/// A helper property for <see cref="autoSizeResizeTo(Vector2, double)"/> to change the size of <see cref="CompositeDrawable"/>s with <see cref="AutoSizeAxes"/>.
/// </summary>
private Vector2 baseSize
{
Expand All @@ -2000,14 +2031,6 @@ private Vector2 baseSize
}
}

private class AutoSizeTransform : TransformCustom<Vector2, CompositeDrawable>
{
public AutoSizeTransform()
: base(nameof(baseSize))
{
}
}

#endregion
}
}
13 changes: 4 additions & 9 deletions osu.Framework/Graphics/Containers/Container.cs
Original file line number Diff line number Diff line change
Expand Up @@ -492,8 +492,8 @@ public void ChangeChildDepth(T child, float newDepth)
}

/// <summary>
/// The duration which automatic sizing should take. If zero, then it is instantaneous.
/// Otherwise, this is equivalent to applying an automatic size via a resize transform.
/// The duration which automatic sizing should approximately take. If zero, then it is instantaneous.
/// AutoSize is being applied continuously so the actual amount of time taken depends on the overall change in value.
/// </summary>
public new float AutoSizeDuration
{
Expand All @@ -502,14 +502,9 @@ public void ChangeChildDepth(T child, float newDepth)
}

/// <summary>
/// The type of easing which should be used for smooth automatic sizing when <see cref="AutoSizeDuration"/>
/// is non-zero.
/// Immediately resizes to the current target size if <see cref="AutoSizeDuration"/> is non-zero.
/// </summary>
public new Easing AutoSizeEasing
{
get => base.AutoSizeEasing;
set => base.AutoSizeEasing = value;
}
public new void FinishAutoSizeTransforms() => base.FinishAutoSizeTransforms();

public struct Enumerator : IEnumerator<T>
{
Expand Down
Loading
Loading