1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 12:57:36 +08:00

Refactor ZoomableScrollContainer to allow setting up zoom range and initial zoom after load

This commit is contained in:
Salman Ahmed 2022-07-25 11:54:10 +03:00
parent 93175eaf6e
commit 123930306b
2 changed files with 56 additions and 60 deletions

View File

@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editing
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(30)
},
scrollContainer = new ZoomableScrollContainer
scrollContainer = new ZoomableScrollContainer(1, 60, 1)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@ -80,21 +80,6 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
}
[Test]
public void TestZoomRangeUpdate()
{
AddStep("set zoom to 2", () => scrollContainer.Zoom = 2);
AddStep("set min zoom to 5", () => scrollContainer.MinZoom = 5);
AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
AddStep("set max zoom to 10", () => scrollContainer.MaxZoom = 10);
AddAssert("zoom = 5", () => scrollContainer.Zoom == 5);
AddStep("set min zoom to 20", () => scrollContainer.MinZoom = 20);
AddStep("set max zoom to 40", () => scrollContainer.MaxZoom = 40);
AddAssert("zoom = 20", () => scrollContainer.Zoom == 20);
}
[Test]
public void TestZoom0()
{

View File

@ -32,19 +32,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private readonly Container zoomedContent;
protected override Container<Drawable> Content => zoomedContent;
private float currentZoom = 1;
/// <summary>
/// The current zoom level of <see cref="ZoomableScrollContainer" />.
/// It may differ from <see cref="Zoom" /> during transitions.
/// The current zoom level of <see cref="ZoomableScrollContainer"/>.
/// It may differ from <see cref="Zoom"/> during transitions.
/// </summary>
public float CurrentZoom => currentZoom;
public float CurrentZoom { get; private set; } = 1;
private bool isZoomSetUp;
[Resolved(canBeNull: true)]
private IFrameBasedClock editorClock { get; set; }
private readonly LayoutValue zoomedContentWidthCache = new LayoutValue(Invalidation.DrawSize);
private float minZoom;
private float maxZoom;
/// <summary>
/// Creates a <see cref="ZoomableScrollContainer"/> with no zoom range.
/// Functionality will be disabled until zoom is set up via <see cref="SetupZoom"/>.
/// </summary>
public ZoomableScrollContainer()
: base(Direction.Horizontal)
{
@ -53,46 +61,36 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
AddLayout(zoomedContentWidthCache);
}
private float minZoom = 1;
/// <summary>
/// The minimum zoom level allowed.
/// Creates a <see cref="ZoomableScrollContainer"/> with a defined zoom range.
/// </summary>
public float MinZoom
public ZoomableScrollContainer(float minimum, float maximum, float initial)
: this()
{
get => minZoom;
set
{
if (value < 1)
throw new ArgumentException($"{nameof(MinZoom)} must be >= 1.", nameof(value));
minZoom = value;
// ensure zoom range is in valid state before updating zoom.
if (MinZoom < MaxZoom)
updateZoom();
}
SetupZoom(initial, minimum, maximum);
}
private float maxZoom = 60;
/// <summary>
/// The maximum zoom level allowed.
/// Sets up the minimum and maximum range of this zoomable scroll container, along with the initial zoom value.
/// </summary>
public float MaxZoom
/// <param name="initial">The initial zoom value, applied immediately.</param>
/// <param name="minimum">The minimum zoom value.</param>
/// <param name="maximum">The maximum zoom value.</param>
public void SetupZoom(float initial, float minimum, float maximum)
{
get => maxZoom;
set
{
if (value < 1)
throw new ArgumentException($"{nameof(MaxZoom)} must be >= 1.", nameof(value));
if (minimum < 1)
throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be >= 1.", nameof(maximum));
maxZoom = value;
if (maximum < 1)
throw new ArgumentException($"{nameof(maximum)} ({maximum}) must be >= 1.", nameof(maximum));
// ensure zoom range is in valid state before updating zoom.
if (MaxZoom > MinZoom)
updateZoom();
}
if (minimum > maximum)
throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be less than {nameof(maximum)} ({maximum})");
minZoom = minimum;
maxZoom = maximum;
CurrentZoom = zoomTarget = initial;
isZoomSetUp = true;
}
/// <summary>
@ -104,14 +102,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
set => updateZoom(value);
}
private void updateZoom(float? value = null)
private void updateZoom(float value)
{
float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom);
if (!isZoomSetUp)
return;
float newZoom = Math.Clamp(value, minZoom, maxZoom);
if (IsLoaded)
setZoomTarget(newZoom, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X);
else
currentZoom = zoomTarget = newZoom;
CurrentZoom = zoomTarget = newZoom;
}
protected override void Update()
@ -141,22 +142,32 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private void updateZoomedContentWidth()
{
zoomedContent.Width = DrawWidth * currentZoom;
zoomedContent.Width = DrawWidth * CurrentZoom;
zoomedContentWidthCache.Validate();
}
public void AdjustZoomRelatively(float change, float? focusPoint = null)
{
if (!isZoomSetUp)
return;
const float zoom_change_sensitivity = 0.02f;
setZoomTarget(zoomTarget + change * (MaxZoom - minZoom) * zoom_change_sensitivity, focusPoint);
setZoomTarget(zoomTarget + change * (maxZoom - minZoom) * zoom_change_sensitivity, focusPoint);
}
protected void SetZoomImmediately(float value, float min, float max)
{
maxZoom = max;
minZoom = min;
CurrentZoom = zoomTarget = value;
}
private float zoomTarget = 1;
private void setZoomTarget(float newZoom, float? focusPoint = null)
{
zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom);
zoomTarget = Math.Clamp(newZoom, minZoom, maxZoom);
focusPoint ??= zoomedContent.ToLocalSpace(ToScreenSpace(new Vector2(DrawWidth / 2, 0))).X;
transformZoomTo(zoomTarget, focusPoint.Value, ZoomDuration, ZoomEasing);
@ -192,7 +203,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private readonly float scrollOffset;
/// <summary>
/// Transforms <see cref="ZoomableScrollContainer.currentZoom"/> to a new value.
/// Transforms <see cref="ZoomableScrollContainer.CurrentZoom"/> to a new value.
/// </summary>
/// <param name="focusPoint">The focus point in absolute coordinates local to the content.</param>
/// <param name="contentSize">The size of the content.</param>
@ -204,7 +215,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
this.scrollOffset = scrollOffset;
}
public override string TargetMember => nameof(currentZoom);
public override string TargetMember => nameof(CurrentZoom);
private float valueAt(double time)
{
@ -222,7 +233,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
float expectedWidth = d.DrawWidth * newZoom;
float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;
d.currentZoom = newZoom;
d.CurrentZoom = newZoom;
d.updateZoomedContentWidth();
// Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area.
@ -231,7 +242,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
d.ScrollTo(targetOffset, false);
}
protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.currentZoom;
protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.CurrentZoom;
}
}
}