Merge pull request #12570 from frenzibyte/corner-rotation-controls

Add rotation controls to editor selection box corners
This commit is contained in:
Dean Herbert 2021-05-10 23:09:44 +09:00 committed by GitHub
commit 6bb52ebcf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 478 additions and 153 deletions

View File

@ -1,45 +1,50 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Threading;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osuTK; using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing namespace osu.Game.Tests.Visual.Editing
{ {
public class TestSceneComposeSelectBox : OsuTestScene public class TestSceneComposeSelectBox : OsuManualInputManagerTestScene
{ {
private Container selectionArea; private Container selectionArea;
private SelectionBox selectionBox;
public TestSceneComposeSelectBox() [SetUp]
public void SetUp() => Schedule(() =>
{ {
SelectionBox selectionBox = null; Child = selectionArea = new Container
{
AddStep("create box", () => Size = new Vector2(400),
Child = selectionArea = new Container Position = -new Vector2(150),
Anchor = Anchor.Centre,
Children = new Drawable[]
{ {
Size = new Vector2(400), selectionBox = new SelectionBox
Position = -new Vector2(150),
Anchor = Anchor.Centre,
Children = new Drawable[]
{ {
selectionBox = new SelectionBox RelativeSizeAxes = Axes.Both,
{
CanRotate = true,
CanScaleX = true,
CanScaleY = true,
OnRotation = handleRotation, CanRotate = true,
OnScale = handleScale CanScaleX = true,
} CanScaleY = true,
OnRotation = handleRotation,
OnScale = handleScale
} }
}); }
};
AddToggleStep("toggle rotation", state => selectionBox.CanRotate = state); InputManager.MoveMouseTo(selectionBox);
AddToggleStep("toggle x", state => selectionBox.CanScaleX = state); InputManager.ReleaseButton(MouseButton.Left);
AddToggleStep("toggle y", state => selectionBox.CanScaleY = state); });
}
private bool handleScale(Vector2 amount, Anchor reference) private bool handleScale(Vector2 amount, Anchor reference)
{ {
@ -68,5 +73,99 @@ namespace osu.Game.Tests.Visual.Editing
selectionArea.Rotation += angle; selectionArea.Rotation += angle;
return true; return true;
} }
[Test]
public void TestRotationHandleShownOnHover()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over handle", () => InputManager.MoveMouseTo(rotationHandle));
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestRotationHandleShownOnHoveringClosestScaleHandler()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestHoverRotationHandleFromScaleHandle()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddAssert("rotation handle not hovered", () => !rotationHandle.IsHovered);
AddStep("hover over rotation handle", () => InputManager.MoveMouseTo(rotationHandle));
AddAssert("rotation handle still shown", () => rotationHandle.Alpha == 1);
AddAssert("rotation handle hovered", () => rotationHandle.IsHovered);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestHoldingScaleHandleHidesCorrespondingRotationHandle()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("hold scale handle", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0);
int i;
ScheduledDelegate mouseMove = null;
AddStep("start dragging", () =>
{
i = 0;
mouseMove = Scheduler.AddDelayed(() =>
{
InputManager.MoveMouseTo(selectionBox.ScreenSpaceDrawQuad.TopLeft + Vector2.One * (5 * ++i));
}, 100, true);
});
AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0);
AddStep("end dragging", () => mouseMove.Cancel());
AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0);
AddStep("unhold left", () => InputManager.ReleaseButton(MouseButton.Left));
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox, new Vector2(20)));
AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0);
}
} }
} }

View File

@ -50,9 +50,9 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
new HitCircle { StartTime = 100 }, new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(50) }, new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(100) }, new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(150) }, new HitCircle { StartTime = 400, Position = new Vector2(300) },
})); }));
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
@ -95,9 +95,9 @@ namespace osu.Game.Tests.Visual.Editing
var addedObjects = new[] var addedObjects = new[]
{ {
new HitCircle { StartTime = 100 }, new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(50) }, new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(100) }, new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(150) }, new HitCircle { StartTime = 400, Position = new Vector2(300) },
}; };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
@ -131,9 +131,9 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
new HitCircle { StartTime = 100 }, new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(50) }, new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(100) }, new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(150) }, new HitCircle { StartTime = 400, Position = new Vector2(300) },
})); }));
moveMouseToObject(() => addedObjects[0]); moveMouseToObject(() => addedObjects[0]);

View File

@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
} }
} }
private Container dragHandles; private SelectionBoxDragHandleContainer dragHandles;
private FillFlowContainer buttons; private FillFlowContainer buttons;
private OsuSpriteText selectionDetailsText; private OsuSpriteText selectionDetailsText;
@ -195,7 +195,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
}, },
} }
}, },
dragHandles = new Container dragHandles = new SelectionBoxDragHandleContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
// ensures that the centres of all drag handles line up with the middle of the selection box border. // ensures that the centres of all drag handles line up with the middle of the selection box border.
@ -220,75 +220,76 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void addRotationComponents() private void addRotationComponents()
{ {
const float separation = 40;
addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90)); addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90));
addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90)); addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90));
AddRangeInternal(new Drawable[] addRotateHandle(Anchor.TopLeft);
{ addRotateHandle(Anchor.TopRight);
new Box addRotateHandle(Anchor.BottomLeft);
{ addRotateHandle(Anchor.BottomRight);
Depth = float.MaxValue,
Colour = colours.YellowLight,
Blending = BlendingParameters.Additive,
Alpha = 0.3f,
Size = new Vector2(BORDER_RADIUS, separation),
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
},
new SelectionBoxDragHandleButton(FontAwesome.Solid.Redo, "Free rotate")
{
Anchor = Anchor.TopCentre,
Y = -separation,
HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e)),
OperationStarted = operationStarted,
OperationEnded = operationEnded
}
});
} }
private void addYScaleComponents() private void addYScaleComponents()
{ {
addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical)); addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical));
addDragHandle(Anchor.TopCentre); addScaleHandle(Anchor.TopCentre);
addDragHandle(Anchor.BottomCentre); addScaleHandle(Anchor.BottomCentre);
} }
private void addFullScaleComponents() private void addFullScaleComponents()
{ {
addDragHandle(Anchor.TopLeft); addScaleHandle(Anchor.TopLeft);
addDragHandle(Anchor.TopRight); addScaleHandle(Anchor.TopRight);
addDragHandle(Anchor.BottomLeft); addScaleHandle(Anchor.BottomLeft);
addDragHandle(Anchor.BottomRight); addScaleHandle(Anchor.BottomRight);
} }
private void addXScaleComponents() private void addXScaleComponents()
{ {
addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal)); addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal));
addDragHandle(Anchor.CentreLeft); addScaleHandle(Anchor.CentreLeft);
addDragHandle(Anchor.CentreRight); addScaleHandle(Anchor.CentreRight);
} }
private void addButton(IconUsage icon, string tooltip, Action action) private void addButton(IconUsage icon, string tooltip, Action action)
{ {
buttons.Add(new SelectionBoxDragHandleButton(icon, tooltip) var button = new SelectionBoxButton(icon, tooltip)
{ {
OperationStarted = operationStarted,
OperationEnded = operationEnded,
Action = action Action = action
}); };
button.OperationStarted += operationStarted;
button.OperationEnded += operationEnded;
buttons.Add(button);
} }
private void addDragHandle(Anchor anchor) => dragHandles.Add(new SelectionBoxDragHandle private void addScaleHandle(Anchor anchor)
{ {
Anchor = anchor, var handle = new SelectionBoxScaleHandle
HandleDrag = e => OnScale?.Invoke(e.Delta, anchor), {
OperationStarted = operationStarted, Anchor = anchor,
OperationEnded = operationEnded HandleDrag = e => OnScale?.Invoke(e.Delta, anchor)
}); };
handle.OperationStarted += operationStarted;
handle.OperationEnded += operationEnded;
dragHandles.AddScaleHandle(handle);
}
private void addRotateHandle(Anchor anchor)
{
var handle = new SelectionBoxRotationHandle
{
Anchor = anchor,
HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e))
};
handle.OperationStarted += operationStarted;
handle.OperationEnded += operationEnded;
dragHandles.AddRotationHandle(handle);
}
private int activeOperations; private int activeOperations;

View File

@ -12,10 +12,7 @@ using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
{ {
/// <summary> public sealed class SelectionBoxButton : SelectionBoxControl, IHasTooltip
/// A drag "handle" which shares the visual appearance but behaves more like a clickable button.
/// </summary>
public sealed class SelectionBoxDragHandleButton : SelectionBoxDragHandle, IHasTooltip
{ {
private SpriteIcon icon; private SpriteIcon icon;
@ -23,7 +20,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
public Action Action; public Action Action;
public SelectionBoxDragHandleButton(IconUsage iconUsage, string tooltip) public SelectionBoxButton(IconUsage iconUsage, string tooltip)
{ {
this.iconUsage = iconUsage; this.iconUsage = iconUsage;
@ -36,7 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Size *= 2; Size = new Vector2(20);
AddInternal(icon = new SpriteIcon AddInternal(icon = new SpriteIcon
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -49,16 +46,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
OperationStarted?.Invoke(); TriggerOperationStarted();
Action?.Invoke(); Action?.Invoke();
OperationEnded?.Invoke(); TriggerOperatoinEnded();
return true; return true;
} }
protected override void UpdateHoverState() protected override void UpdateHoverState()
{ {
base.UpdateHoverState(); base.UpdateHoverState();
icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black; icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
} }
public string TooltipText { get; } public string TooltipText { get; }

View File

@ -0,0 +1,97 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components
{
/// <summary>
/// Represents the base appearance for UI controls of the <see cref="SelectionBox"/>,
/// such as scale handles, rotation handles, buttons, etc...
/// </summary>
public abstract class SelectionBoxControl : CompositeDrawable
{
public const double TRANSFORM_DURATION = 100;
public event Action OperationStarted;
public event Action OperationEnded;
private Circle circle;
/// <summary>
/// Whether the user is currently holding the control with mouse.
/// </summary>
public bool IsHeld { get; private set; }
[Resolved]
protected OsuColour Colours { get; private set; }
[BackgroundDependencyLoader]
private void load()
{
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
circle = new Circle
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
UpdateHoverState();
FinishTransforms(true);
}
protected override bool OnHover(HoverEvent e)
{
UpdateHoverState();
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
UpdateHoverState();
}
protected override bool OnMouseDown(MouseDownEvent e)
{
IsHeld = true;
UpdateHoverState();
return true;
}
protected override void OnMouseUp(MouseUpEvent e)
{
IsHeld = false;
UpdateHoverState();
}
protected virtual void UpdateHoverState()
{
if (IsHeld)
circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint);
else
circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint);
this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint);
}
protected void TriggerOperationStarted() => OperationStarted?.Invoke();
protected void TriggerOperatoinEnded() => OperationEnded?.Invoke();
}
}

View File

@ -2,75 +2,17 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
{ {
public class SelectionBoxDragHandle : Container public abstract class SelectionBoxDragHandle : SelectionBoxControl
{ {
public Action OperationStarted;
public Action OperationEnded;
public Action<DragEvent> HandleDrag { get; set; } public Action<DragEvent> HandleDrag { get; set; }
private Circle circle;
[Resolved]
private OsuColour colours { get; set; }
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(10);
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
circle = new Circle
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
UpdateHoverState();
}
protected override bool OnHover(HoverEvent e)
{
UpdateHoverState();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
UpdateHoverState();
}
protected bool HandlingMouse;
protected override bool OnMouseDown(MouseDownEvent e)
{
HandlingMouse = true;
UpdateHoverState();
return true;
}
protected override bool OnDragStart(DragStartEvent e) protected override bool OnDragStart(DragStartEvent e)
{ {
OperationStarted?.Invoke(); TriggerOperationStarted();
return true; return true;
} }
@ -82,24 +24,45 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override void OnDragEnd(DragEndEvent e) protected override void OnDragEnd(DragEndEvent e)
{ {
HandlingMouse = false; TriggerOperatoinEnded();
OperationEnded?.Invoke();
UpdateHoverState(); UpdateHoverState();
base.OnDragEnd(e); base.OnDragEnd(e);
} }
protected override void OnMouseUp(MouseUpEvent e) #region Internal events for SelectionBoxDragHandleContainer
internal event Action HoverGained;
internal event Action HoverLost;
internal event Action MouseDown;
internal event Action MouseUp;
protected override bool OnHover(HoverEvent e)
{ {
HandlingMouse = false; bool result = base.OnHover(e);
UpdateHoverState(); HoverGained?.Invoke();
base.OnMouseUp(e); return result;
} }
protected virtual void UpdateHoverState() protected override void OnHoverLost(HoverLostEvent e)
{ {
circle.Colour = HandlingMouse ? colours.GrayF : (IsHovered ? colours.Red : colours.YellowDark); base.OnHoverLost(e);
this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint); HoverLost?.Invoke();
} }
protected override bool OnMouseDown(MouseDownEvent e)
{
bool result = base.OnMouseDown(e);
MouseDown?.Invoke();
return result;
}
protected override void OnMouseUp(MouseUpEvent e)
{
base.OnMouseUp(e);
MouseUp?.Invoke();
}
#endregion
} }
} }

View File

@ -0,0 +1,109 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Screens.Edit.Compose.Components
{
/// <summary>
/// Represents a display composite containing and managing the visibility state of the selection box's drag handles.
/// </summary>
public class SelectionBoxDragHandleContainer : CompositeDrawable
{
private Container<SelectionBoxScaleHandle> scaleHandles;
private Container<SelectionBoxRotationHandle> rotationHandles;
private readonly List<SelectionBoxDragHandle> allDragHandles = new List<SelectionBoxDragHandle>();
public new MarginPadding Padding
{
get => base.Padding;
set => base.Padding = value;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
scaleHandles = new Container<SelectionBoxScaleHandle>
{
RelativeSizeAxes = Axes.Both,
},
rotationHandles = new Container<SelectionBoxRotationHandle>
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-12.5f),
},
};
}
public void AddScaleHandle(SelectionBoxScaleHandle handle)
{
bindDragHandle(handle);
scaleHandles.Add(handle);
}
public void AddRotationHandle(SelectionBoxRotationHandle handle)
{
handle.Alpha = 0;
handle.AlwaysPresent = true;
bindDragHandle(handle);
rotationHandles.Add(handle);
}
private void bindDragHandle(SelectionBoxDragHandle handle)
{
handle.HoverGained += updateRotationHandlesVisibility;
handle.HoverLost += updateRotationHandlesVisibility;
handle.MouseDown += updateRotationHandlesVisibility;
handle.MouseUp += updateRotationHandlesVisibility;
allDragHandles.Add(handle);
}
private SelectionBoxRotationHandle displayedRotationHandle;
private SelectionBoxDragHandle activeHandle;
private void updateRotationHandlesVisibility()
{
// if the active handle is a rotation handle and is held or hovered,
// then no need to perform any updates to the rotation handles visibility.
if (activeHandle is SelectionBoxRotationHandle && (activeHandle?.IsHeld == true || activeHandle?.IsHovered == true))
return;
displayedRotationHandle?.FadeOut(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
displayedRotationHandle = null;
// if the active handle is not a rotation handle but is held, then keep the rotation handle hidden.
if (activeHandle?.IsHeld == true)
return;
activeHandle = rotationHandles.SingleOrDefault(h => h.IsHeld || h.IsHovered);
activeHandle ??= allDragHandles.SingleOrDefault(h => h.IsHovered);
if (activeHandle != null)
{
displayedRotationHandle = getCorrespondingRotationHandle(activeHandle, rotationHandles);
displayedRotationHandle?.FadeIn(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
}
}
/// <summary>
/// Gets the rotation handle corresponding to the given handle.
/// </summary>
[CanBeNull]
private static SelectionBoxRotationHandle getCorrespondingRotationHandle(SelectionBoxDragHandle handle, IEnumerable<SelectionBoxRotationHandle> rotationHandles)
{
if (handle is SelectionBoxRotationHandle rotationHandle)
return rotationHandle;
return rotationHandles.SingleOrDefault(r => r.Anchor == handle.Anchor);
}
}
}

View File

@ -0,0 +1,42 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components
{
public class SelectionBoxRotationHandle : SelectionBoxDragHandle
{
private SpriteIcon icon;
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(15f);
AddInternal(icon = new SpriteIcon
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Redo,
Scale = new Vector2
{
X = Anchor.HasFlagFast(Anchor.x0) ? 1f : -1f,
Y = Anchor.HasFlagFast(Anchor.y0) ? 1f : -1f
}
});
}
protected override void UpdateHoverState()
{
base.UpdateHoverState();
icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
{
public class SelectionBoxScaleHandle : SelectionBoxDragHandle
{
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(10);
}
}
}