mirror of
https://github.com/ppy/osu.git
synced 2025-01-14 20:32:55 +08: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.
|
||||
// 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.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneComposeSelectBox : OsuTestScene
|
||||
public class TestSceneComposeSelectBox : OsuManualInputManagerTestScene
|
||||
{
|
||||
private Container selectionArea;
|
||||
private SelectionBox selectionBox;
|
||||
|
||||
public TestSceneComposeSelectBox()
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
SelectionBox selectionBox = null;
|
||||
|
||||
AddStep("create box", () =>
|
||||
Child = selectionArea = new Container
|
||||
Child = selectionArea = new Container
|
||||
{
|
||||
Size = new Vector2(400),
|
||||
Position = -new Vector2(150),
|
||||
Anchor = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Size = new Vector2(400),
|
||||
Position = -new Vector2(150),
|
||||
Anchor = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
selectionBox = new SelectionBox
|
||||
{
|
||||
selectionBox = new SelectionBox
|
||||
{
|
||||
CanRotate = true,
|
||||
CanScaleX = true,
|
||||
CanScaleY = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
||||
OnRotation = handleRotation,
|
||||
OnScale = handleScale
|
||||
}
|
||||
CanRotate = true,
|
||||
CanScaleX = true,
|
||||
CanScaleY = true,
|
||||
|
||||
OnRotation = handleRotation,
|
||||
OnScale = handleScale
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AddToggleStep("toggle rotation", state => selectionBox.CanRotate = state);
|
||||
AddToggleStep("toggle x", state => selectionBox.CanScaleX = state);
|
||||
AddToggleStep("toggle y", state => selectionBox.CanScaleY = state);
|
||||
}
|
||||
InputManager.MoveMouseTo(selectionBox);
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
private bool handleScale(Vector2 amount, Anchor reference)
|
||||
{
|
||||
@ -68,5 +73,99 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
selectionArea.Rotation += angle;
|
||||
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[]
|
||||
{
|
||||
new HitCircle { StartTime = 100 },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(50) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(150) },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(200) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(300) },
|
||||
}));
|
||||
|
||||
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
|
||||
@ -95,9 +95,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
var addedObjects = new[]
|
||||
{
|
||||
new HitCircle { StartTime = 100 },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(50) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(150) },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(200) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(300) },
|
||||
};
|
||||
|
||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
|
||||
@ -131,9 +131,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
|
||||
{
|
||||
new HitCircle { StartTime = 100 },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(50) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(150) },
|
||||
new HitCircle { StartTime = 200, Position = new Vector2(100) },
|
||||
new HitCircle { StartTime = 300, Position = new Vector2(200) },
|
||||
new HitCircle { StartTime = 400, Position = new Vector2(300) },
|
||||
}));
|
||||
|
||||
moveMouseToObject(() => addedObjects[0]);
|
||||
|
@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
}
|
||||
}
|
||||
|
||||
private Container dragHandles;
|
||||
private SelectionBoxDragHandleContainer dragHandles;
|
||||
private FillFlowContainer buttons;
|
||||
|
||||
private OsuSpriteText selectionDetailsText;
|
||||
@ -195,7 +195,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
},
|
||||
}
|
||||
},
|
||||
dragHandles = new Container
|
||||
dragHandles = new SelectionBoxDragHandleContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
// 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()
|
||||
{
|
||||
const float separation = 40;
|
||||
|
||||
addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90));
|
||||
addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90));
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
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
|
||||
}
|
||||
});
|
||||
addRotateHandle(Anchor.TopLeft);
|
||||
addRotateHandle(Anchor.TopRight);
|
||||
addRotateHandle(Anchor.BottomLeft);
|
||||
addRotateHandle(Anchor.BottomRight);
|
||||
}
|
||||
|
||||
private void addYScaleComponents()
|
||||
{
|
||||
addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical));
|
||||
|
||||
addDragHandle(Anchor.TopCentre);
|
||||
addDragHandle(Anchor.BottomCentre);
|
||||
addScaleHandle(Anchor.TopCentre);
|
||||
addScaleHandle(Anchor.BottomCentre);
|
||||
}
|
||||
|
||||
private void addFullScaleComponents()
|
||||
{
|
||||
addDragHandle(Anchor.TopLeft);
|
||||
addDragHandle(Anchor.TopRight);
|
||||
addDragHandle(Anchor.BottomLeft);
|
||||
addDragHandle(Anchor.BottomRight);
|
||||
addScaleHandle(Anchor.TopLeft);
|
||||
addScaleHandle(Anchor.TopRight);
|
||||
addScaleHandle(Anchor.BottomLeft);
|
||||
addScaleHandle(Anchor.BottomRight);
|
||||
}
|
||||
|
||||
private void addXScaleComponents()
|
||||
{
|
||||
addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal));
|
||||
|
||||
addDragHandle(Anchor.CentreLeft);
|
||||
addDragHandle(Anchor.CentreRight);
|
||||
addScaleHandle(Anchor.CentreLeft);
|
||||
addScaleHandle(Anchor.CentreRight);
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
};
|
||||
|
||||
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,
|
||||
HandleDrag = e => OnScale?.Invoke(e.Delta, anchor),
|
||||
OperationStarted = operationStarted,
|
||||
OperationEnded = operationEnded
|
||||
});
|
||||
var handle = new SelectionBoxScaleHandle
|
||||
{
|
||||
Anchor = anchor,
|
||||
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;
|
||||
|
||||
|
@ -12,10 +12,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A drag "handle" which shares the visual appearance but behaves more like a clickable button.
|
||||
/// </summary>
|
||||
public sealed class SelectionBoxDragHandleButton : SelectionBoxDragHandle, IHasTooltip
|
||||
public sealed class SelectionBoxButton : SelectionBoxControl, IHasTooltip
|
||||
{
|
||||
private SpriteIcon icon;
|
||||
|
||||
@ -23,7 +20,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
public Action Action;
|
||||
|
||||
public SelectionBoxDragHandleButton(IconUsage iconUsage, string tooltip)
|
||||
public SelectionBoxButton(IconUsage iconUsage, string tooltip)
|
||||
{
|
||||
this.iconUsage = iconUsage;
|
||||
|
||||
@ -36,7 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size *= 2;
|
||||
Size = new Vector2(20);
|
||||
AddInternal(icon = new SpriteIcon
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -49,16 +46,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
OperationStarted?.Invoke();
|
||||
TriggerOperationStarted();
|
||||
Action?.Invoke();
|
||||
OperationEnded?.Invoke();
|
||||
TriggerOperatoinEnded();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void 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; }
|
@ -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.
|
||||
|
||||
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;
|
||||
using osuTK;
|
||||
|
||||
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; }
|
||||
|
||||
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)
|
||||
{
|
||||
OperationStarted?.Invoke();
|
||||
TriggerOperationStarted();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -82,24 +24,45 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
HandlingMouse = false;
|
||||
OperationEnded?.Invoke();
|
||||
TriggerOperatoinEnded();
|
||||
|
||||
UpdateHoverState();
|
||||
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;
|
||||
UpdateHoverState();
|
||||
base.OnMouseUp(e);
|
||||
bool result = base.OnHover(e);
|
||||
HoverGained?.Invoke();
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual void UpdateHoverState()
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
circle.Colour = HandlingMouse ? colours.GrayF : (IsHovered ? colours.Red : colours.YellowDark);
|
||||
this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint);
|
||||
base.OnHoverLost(e);
|
||||
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…
Reference in New Issue
Block a user