mirror of
https://github.com/osukey/osukey.git
synced 2025-06-05 12:57:39 +09:00
Merge pull request #12570 from frenzibyte/corner-rotation-controls
Add rotation controls to editor selection box corners
This commit is contained in:
commit
6bb52ebcf8
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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]);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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; }
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user