diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 3076ad081a..44f38acfd4 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -6,8 +6,6 @@ using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; -using osu.Framework.Input.Events; -using osu.Framework.Input.States; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -29,27 +27,11 @@ namespace osu.Game.Rulesets.Edit /// public event Action Deselected; - /// - /// Invoked when this has requested selection. - /// Will fire even if already selected. Does not actually perform selection. - /// - public event Action SelectionRequested; - - /// - /// Invoked when this has requested drag. - /// - public event Action DragRequested; - /// /// The which this applies to. /// public readonly DrawableHitObject DrawableObject; - /// - /// The screen-space position of prior to handling a movement event. - /// - internal Vector2 ScreenSpaceMovementStartPosition { get; private set; } - protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || State == SelectionState.Selected; public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; @@ -109,45 +91,6 @@ namespace osu.Game.Rulesets.Edit public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); - private bool selectionRequested; - - protected override bool OnMouseDown(MouseDownEvent e) - { - selectionRequested = false; - - if (State == SelectionState.NotSelected) - { - SelectionRequested?.Invoke(this, e.CurrentState); - selectionRequested = true; - } - - return IsSelected; - } - - protected override bool OnClick(ClickEvent e) - { - if (State == SelectionState.Selected && !selectionRequested) - { - selectionRequested = true; - SelectionRequested?.Invoke(this, e.CurrentState); - return true; - } - - return base.OnClick(e); - } - - protected override bool OnDragStart(DragStartEvent e) - { - ScreenSpaceMovementStartPosition = DrawableObject.ToScreenSpace(DrawableObject.OriginPosition); - return true; - } - - protected override bool OnDrag(DragEvent e) - { - DragRequested?.Invoke(this, e); - return true; - } - /// /// The screen-space point that causes this to be selected. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 4001a0f33a..30f0f94128 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Framework.Input.Events; -using osu.Framework.Input.States; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; @@ -23,6 +23,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { public event Action> SelectionChanged; + private DragBox dragBox; private SelectionBlueprintContainer selectionBlueprints; private Container placementBlueprintContainer; private PlacementBlueprint currentPlacement; @@ -46,12 +47,9 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionHandler = composer.CreateSelectionHandler(); selectionHandler.DeselectAll = deselectAll; - var dragBox = new DragBox(select); - dragBox.DragEnd += () => selectionHandler.UpdateVisibility(); - InternalChildren = new[] { - dragBox, + dragBox = new DragBox(select), selectionHandler, selectionBlueprints = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }, placementBlueprintContainer = new Container { RelativeSizeAxes = Axes.Both }, @@ -91,6 +89,86 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + protected override bool OnMouseDown(MouseDownEvent e) + { + beginClickSelection(e); + return true; + } + + protected override bool OnClick(ClickEvent e) + { + // Deselection should only occur if no selected blueprints are hovered + // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection + if (endClickSelection() || selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) + return true; + + deselectAll(); + return true; + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + // Special case for when a drag happened instead of a click + Schedule(() => endClickSelection()); + return true; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (currentPlacement != null) + { + updatePlacementPosition(e.ScreenSpaceMousePosition); + return true; + } + + return base.OnMouseMove(e); + } + + protected override bool OnDragStart(DragStartEvent e) + { + if (!beginSelectionMovement()) + { + dragBox.UpdateDrag(e); + dragBox.FadeIn(250, Easing.OutQuint); + } + + return true; + } + + protected override bool OnDrag(DragEvent e) + { + if (!moveCurrentSelection(e)) + dragBox.UpdateDrag(e); + + return true; + } + + protected override bool OnDragEnd(DragEndEvent e) + { + if (!finishSelectionMovement()) + { + dragBox.FadeOut(250, Easing.OutQuint); + selectionHandler.UpdateVisibility(); + } + + return true; + } + + protected override void Update() + { + base.Update(); + + if (currentPlacement != null) + { + if (composer.CursorInPlacementArea) + currentPlacement.State = PlacementState.Shown; + else if (currentPlacement?.PlacementBegun == false) + currentPlacement.State = PlacementState.Hidden; + } + } + + #region Blueprint Addition/Removal + private void addBlueprintFor(HitObject hitObject) { var drawable = composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject); @@ -110,8 +188,6 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Selected -= onBlueprintSelected; blueprint.Deselected -= onBlueprintDeselected; - blueprint.SelectionRequested -= onSelectionRequested; - blueprint.DragRequested -= onDragRequested; selectionBlueprints.Remove(blueprint); } @@ -126,43 +202,13 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Selected += onBlueprintSelected; blueprint.Deselected += onBlueprintDeselected; - blueprint.SelectionRequested += onSelectionRequested; - blueprint.DragRequested += onDragRequested; selectionBlueprints.Add(blueprint); } - private void removeBlueprintFor(DrawableHitObject hitObject) => removeBlueprintFor(hitObject.HitObject); + #endregion - protected override bool OnClick(ClickEvent e) - { - deselectAll(); - return true; - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - if (currentPlacement != null) - { - updatePlacementPosition(e.ScreenSpaceMousePosition); - return true; - } - - return base.OnMouseMove(e); - } - - protected override void Update() - { - base.Update(); - - if (currentPlacement != null) - { - if (composer.CursorInPlacementArea) - currentPlacement.State = PlacementState.Shown; - else if (currentPlacement?.PlacementBegun == false) - currentPlacement.State = PlacementState.Hidden; - } - } + #region Placement /// /// Refreshes the current placement tool. @@ -191,6 +237,47 @@ namespace osu.Game.Screens.Edit.Compose.Components currentPlacement.UpdatePosition(snappedScreenSpacePosition); } + #endregion + + #region Selection + + /// + /// Whether a blueprint was selected by a previous click event. + /// + private bool clickSelectionBegan; + + /// + /// Attempts to select any hovered blueprints. + /// + /// The input event that triggered this selection. + private void beginClickSelection(UIEvent e) + { + Debug.Assert(!clickSelectionBegan); + + foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) + { + if (blueprint.IsHovered) + { + selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); + clickSelectionBegan = true; + break; + } + } + } + + /// + /// Finishes the current blueprint selection. + /// + /// Whether a click selection was active. + private bool endClickSelection() + { + if (!clickSelectionBegan) + return false; + + clickSelectionBegan = false; + return true; + } + /// /// Select all masks in a given rectangle selection area. /// @@ -227,24 +314,80 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionChanged?.Invoke(selectionHandler.SelectedHitObjects); } - private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state); + #endregion - private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) + #region Selection Movement + + private Vector2? screenSpaceMovementStartPosition; + private SelectionBlueprint movementBlueprint; + + /// + /// Attempts to begin the movement of any selected blueprints. + /// + /// Whether movement began. + private bool beginSelectionMovement() { - HitObject draggedObject = blueprint.DrawableObject.HitObject; + Debug.Assert(movementBlueprint == null); - Vector2 movePosition = blueprint.ScreenSpaceMovementStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition; + // Any selected blueprint that is hovered can begin the movement of the group, however only the earliest hitobject is used for movement + // A special case is added for when a click selection occurred before the drag + if (!clickSelectionBegan && !selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) + return false; + + // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject + movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.DrawableObject.HitObject.StartTime).First(); + screenSpaceMovementStartPosition = movementBlueprint.DrawableObject.ToScreenSpace(movementBlueprint.DrawableObject.OriginPosition); + + return true; + } + + /// + /// Moves the current selected blueprints. + /// + /// The defining the movement event. + /// Whether a movement was active. + private bool moveCurrentSelection(DragEvent e) + { + if (movementBlueprint == null) + return false; + + Debug.Assert(screenSpaceMovementStartPosition != null); + + Vector2 startPosition = screenSpaceMovementStartPosition.Value; + HitObject draggedObject = movementBlueprint.DrawableObject.HitObject; + + // The final movement position, relative to screenSpaceMovementStartPosition + Vector2 movePosition = startPosition + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; Vector2 snappedPosition = composer.GetSnappedPosition(ToLocalSpace(movePosition)); // Move the hitobjects - selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceMovementStartPosition, ToScreenSpace(snappedPosition))); + selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition))); // Apply the start time at the newly snapped-to position double offset = composer.GetSnappedTime(draggedObject.StartTime, snappedPosition) - draggedObject.StartTime; foreach (HitObject obj in selectionHandler.SelectedHitObjects) obj.StartTime += offset; + + return true; } + /// + /// Finishes the current movement of selected blueprints. + /// + /// Whether a movement was active. + private bool finishSelectionMovement() + { + if (movementBlueprint == null) + return false; + + screenSpaceMovementStartPosition = null; + movementBlueprint = null; + + return true; + } + + #endregion + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -258,6 +401,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private class SelectionBlueprintContainer : Container { + public IEnumerable AliveBlueprints => AliveInternalChildren.Cast(); + protected override int Compare(Drawable x, Drawable y) { if (!(x is SelectionBlueprint xBlueprint) || !(y is SelectionBlueprint yBlueprint)) diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index 143615148a..2a510e74fd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -19,11 +19,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { private readonly Action performSelection; - /// - /// Invoked when the drag selection has finished. - /// - public event Action DragEnd; - private Drawable box; /// @@ -55,13 +50,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }; } - protected override bool OnDragStart(DragStartEvent e) - { - this.FadeIn(250, Easing.OutQuint); - return true; - } - - protected override bool OnDrag(DragEvent e) + public void UpdateDrag(MouseButtonEvent e) { var dragPosition = e.ScreenSpaceMousePosition; var dragStartPosition = e.ScreenSpaceMouseDownPosition; @@ -78,14 +67,6 @@ namespace osu.Game.Screens.Edit.Compose.Components box.Size = bottomRight - topLeft; performSelection?.Invoke(dragRectangle); - return true; - } - - protected override bool OnDragEnd(DragEndEvent e) - { - this.FadeOut(250, Easing.OutQuint); - DragEnd?.Invoke(); - return true; } } } diff --git a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 7c35742ff6..56ae069a26 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -253,9 +253,7 @@ namespace osu.Game.Screens.Ranking.Pages { this.date = date; - AutoSizeAxes = Axes.Y; - - Width = 140; + AutoSizeAxes = Axes.Both; Masking = true; CornerRadius = 5; @@ -271,22 +269,26 @@ namespace osu.Game.Screens.Ranking.Pages RelativeSizeAxes = Axes.Both, Colour = colours.Gray6, }, - new OsuSpriteText + new FillFlowContainer { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Text = date.ToShortDateString(), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, - Colour = Color4.White, + Spacing = new Vector2(10), + Children = new[] + { + new OsuSpriteText + { + Text = date.ToShortDateString(), + Colour = Color4.White, + }, + new OsuSpriteText + { + Text = date.ToShortTimeString(), + Colour = Color4.White, + } + } }, - new OsuSpriteText - { - Origin = Anchor.CentreRight, - Anchor = Anchor.CentreRight, - Text = date.ToShortTimeString(), - Padding = new MarginPadding { Horizontal = 10, Vertical = 5 }, - Colour = Color4.White, - } }; } }