diff --git a/osu.Framework.Tests/Visual/Containers/TestSceneCompositeDrawable.cs b/osu.Framework.Tests/Visual/Containers/TestSceneCompositeDrawable.cs
index 8064c71d70..7c3e521bd5 100644
--- a/osu.Framework.Tests/Visual/Containers/TestSceneCompositeDrawable.cs
+++ b/osu.Framework.Tests/Visual/Containers/TestSceneCompositeDrawable.cs
@@ -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;
@@ -89,6 +90,91 @@ public void TestClearTransformsOnDelayedAutoSize()
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[]
+ {
+ 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()
diff --git a/osu.Framework.Tests/Visual/Layout/TestSceneLayoutDurations.cs b/osu.Framework.Tests/Visual/Layout/TestSceneLayoutDurations.cs
index 0d868976ce..59e8bd9943 100644
--- a/osu.Framework.Tests/Visual/Layout/TestSceneLayoutDurations.cs
+++ b/osu.Framework.Tests/Visual/Layout/TestSceneLayoutDurations.cs
@@ -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;
@@ -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),
@@ -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; });
@@ -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;
});
@@ -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;
});
@@ -132,7 +98,6 @@ public void TestInterruptExistingDuration()
AddStep("set duration 0", () =>
{
- autoSizeContainer.AutoSizeDuration = 0;
fillFlowContainer.LayoutDuration = 0;
});
@@ -146,7 +111,6 @@ public void TestInterruptExistingDuration()
AddStep("alter values", () =>
{
- box1.Size = new Vector2(0);
fillFlowContainer.Width = 200;
});
@@ -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();
}
diff --git a/osu.Framework/Graphics/Containers/CompositeDrawable.cs b/osu.Framework/Graphics/Containers/CompositeDrawable.cs
index 3d9de04b5f..1f1f5bde19 100644
--- a/osu.Framework/Graphics/Containers/CompositeDrawable.cs
+++ b/osu.Framework/Graphics/Containers/CompositeDrawable.cs
@@ -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;
@@ -945,6 +946,7 @@ public override bool UpdateSubTree()
UpdateAfterChildren();
updateChildrenSizeDependencies();
+ applyAutoSize();
UpdateAfterAutoSize();
return true;
}
@@ -1813,17 +1815,11 @@ protected set
}
///
- /// 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.
///
public float AutoSizeDuration { get; protected set; }
- ///
- /// The type of easing which should be used for smooth automatic sizing when
- /// is non-zero.
- ///
- public Easing AutoSizeEasing { get; protected set; }
-
///
/// Fired after this 's is updated through autosize.
///
@@ -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.
@@ -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);
+
+ if (Precision.AlmostEquals(newSize, targetSize, 0.5f))
+ {
+ newSize = targetSize;
+ targetAutoSize.Invalidate();
}
+
+ baseSize = newSize;
}
///
- /// A helper property for to change the size of s with .
+ /// Immediately resizes to the current target size if is non-zero.
+ ///
+ protected void FinishAutoSizeTransforms()
+ {
+ updateChildrenSizeDependencies();
+
+ if (targetAutoSize.IsValid)
+ autoSizeResizeTo(targetAutoSize.Value, 0);
+ }
+
+ ///
+ /// When valid, holds the current target size that should be approached when using automatic sizing and is non-zero.
+ ///
+ private readonly Cached targetAutoSize = new Cached();
+
+ private bool didInitialAutoSize;
+
+ ///
+ /// A helper property for to change the size of s with .
///
private Vector2 baseSize
{
@@ -2000,14 +2031,6 @@ private Vector2 baseSize
}
}
- private class AutoSizeTransform : TransformCustom
- {
- public AutoSizeTransform()
- : base(nameof(baseSize))
- {
- }
- }
-
#endregion
}
}
diff --git a/osu.Framework/Graphics/Containers/Container.cs b/osu.Framework/Graphics/Containers/Container.cs
index 2c996f04b1..f879f9742a 100644
--- a/osu.Framework/Graphics/Containers/Container.cs
+++ b/osu.Framework/Graphics/Containers/Container.cs
@@ -492,8 +492,8 @@ public void ChangeChildDepth(T child, float newDepth)
}
///
- /// 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.
///
public new float AutoSizeDuration
{
@@ -502,14 +502,9 @@ public void ChangeChildDepth(T child, float newDepth)
}
///
- /// The type of easing which should be used for smooth automatic sizing when
- /// is non-zero.
+ /// Immediately resizes to the current target size if is non-zero.
///
- public new Easing AutoSizeEasing
- {
- get => base.AutoSizeEasing;
- set => base.AutoSizeEasing = value;
- }
+ public new void FinishAutoSizeTransforms() => base.FinishAutoSizeTransforms();
public struct Enumerator : IEnumerator
{
diff --git a/osu.Framework/Graphics/Containers/FlowContainer.cs b/osu.Framework/Graphics/Containers/FlowContainer.cs
index 005202c749..5361970787 100644
--- a/osu.Framework/Graphics/Containers/FlowContainer.cs
+++ b/osu.Framework/Graphics/Containers/FlowContainer.cs
@@ -31,11 +31,7 @@ protected FlowContainer()
///
/// The easing that should be used when children are moved to their position in the layout.
///
- public Easing LayoutEasing
- {
- get => AutoSizeEasing;
- set => AutoSizeEasing = value;
- }
+ public Easing LayoutEasing { get; set; }
///
/// The time it should take to move a child from its current position to its new layout position.
diff --git a/osu.Framework/Utils/Interpolation.cs b/osu.Framework/Utils/Interpolation.cs
index 68b077bf0a..3df9cbdd83 100644
--- a/osu.Framework/Utils/Interpolation.cs
+++ b/osu.Framework/Utils/Interpolation.cs
@@ -32,6 +32,21 @@ public static double Damp(double start, double final, double @base, double expon
return Lerp(start, final, 1 - Math.Pow(@base, exponent));
}
+ ///
+ /// Interpolates between 2 vectors (start and final) using a given base and exponent.
+ ///
+ /// The start value.
+ /// The end value.
+ /// The base of the exponential. The valid range is [0, 1], where smaller values mean that the final value is achieved more quickly, and values closer to 1 results in slow convergence to the final value.
+ /// The exponent of the exponential. An exponent of 0 results in the start values, whereas larger exponents make the result converge to the final value.
+ public static Vector2 Damp(Vector2 start, Vector2 final, double @base, double exponent)
+ {
+ if (@base < 0 || @base > 1)
+ throw new ArgumentOutOfRangeException(nameof(@base), $"{nameof(@base)} has to lie in [0,1], but is {@base}.");
+
+ return Vector2.Lerp(start, final, (float)(1 - Math.Pow(@base, exponent)));
+ }
+
///
/// Interpolate the current value towards the target value based on the elapsed time.
/// If the current value is updated every frame using this function, the result is approximately frame-rate independent.
@@ -50,6 +65,24 @@ public static double DampContinuously(double current, double target, double half
return Damp(current, target, 0.5, exponent);
}
+ ///
+ /// Interpolate the current value towards the target value based on the elapsed time.
+ /// If the current value is updated every frame using this function, the result is approximately frame-rate independent.
+ ///
+ ///
+ /// Because floating-point errors can accumulate over a long time, this function shouldn't be
+ /// used for things requiring accurate values.
+ ///
+ /// The current value.
+ /// The target value.
+ /// The time it takes to reach the middle value of the current and the target value.
+ /// The elapsed time of the current frame.
+ public static Vector2 DampContinuously(Vector2 current, Vector2 target, double halfTime, double elapsedTime)
+ {
+ double exponent = elapsedTime / halfTime;
+ return Damp(current, target, 0.5, exponent);
+ }
+
///
/// Interpolates between a set of points using a lagrange polynomial.
///