diff --git a/osu.Android.props b/osu.Android.props index c5714caf4c..43c1302e54 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index da7708081b..6b8daa531f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -14,6 +15,7 @@ using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; using osuTK; using osuTK.Graphics; @@ -25,6 +27,11 @@ namespace osu.Game.Rulesets.Osu.Tests private const double beat_length = 100; private static readonly Vector2 grid_position = new Vector2(512, 384); + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CircularDistanceSnapGrid) + }; + [Cached(typeof(IEditorBeatmap))] private readonly EditorBeatmap editorBeatmap; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 6a4201f84d..4893ebfdd4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -111,6 +111,21 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Distance Overflow 1 Repeat", () => SetContents(() => testDistanceOverflow(1))); } + [Test] + public void TestChangeStackHeight() + { + DrawableSlider slider = null; + + AddStep("create slider", () => + { + slider = (DrawableSlider)createSlider(repeats: 1); + Add(slider); + }); + + AddStep("change stack height", () => slider.HitObject.StackHeight = 10); + AddAssert("body positioned correctly", () => slider.Position == slider.HitObject.StackedPosition); + } + private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index ec23ec31b2..5df0b70f12 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -78,6 +78,13 @@ namespace osu.Game.Rulesets.Osu.Tests checkPositions(); } + [Test] + public void TestStackedHitObject() + { + AddStep("set stacking", () => slider.StackHeight = 5); + checkPositions(); + } + private void moveHitObject() { AddStep("move hitobject", () => @@ -88,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void checkPositions() { - AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.Position); + AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition); AddAssert("head positioned correctly", () => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre)); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 2fb18bf8ba..b7b8d0af88 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; @@ -28,6 +29,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private readonly List segments = new List(); private Vector2 cursor; + private InputManager inputManager; private PlacementState state; @@ -52,6 +54,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders setState(PlacementState.Initial); } + protected override void LoadComplete() + { + base.LoadComplete(); + inputManager = GetContainingInputManager(); + } + public override void UpdatePosition(Vector2 screenSpacePosition) { switch (state) @@ -61,7 +69,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders break; case PlacementState.Body: - cursor = ToLocalSpace(screenSpacePosition) - HitObject.Position; + // The given screen-space position may have been externally snapped, but the unsnapped position from the input manager + // is used instead since snapping control points doesn't make much sense + cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position; break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 6d45bb9ac4..433d29f2e4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -34,6 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Slider slider; private readonly IBindable positionBindable = new Bindable(); + private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); private readonly IBindable pathBindable = new Bindable(); @@ -72,6 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut); positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); + stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); scaleBindable.BindValueChanged(scale => { updatePathRadius(); @@ -79,6 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }); positionBindable.BindTo(HitObject.PositionBindable); + stackHeightBindable.BindTo(HitObject.StackHeightBindable); scaleBindable.BindTo(HitObject.ScaleBindable); pathBindable.BindTo(slider.PathBindable); diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index b506c1f918..0ba712a83f 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; @@ -98,6 +99,15 @@ namespace osu.Game.Rulesets.Osu.Objects set => LastInComboBindable.Value = value; } + protected OsuHitObject() + { + StackHeightBindable.BindValueChanged(height => + { + foreach (var nested in NestedHitObjects.OfType()) + nested.StackHeight = height.NewValue; + }); + } + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index d98d72331a..010bf072e8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -163,6 +163,7 @@ namespace osu.Game.Rulesets.Osu.Objects { StartTime = e.Time, Position = Position, + StackHeight = StackHeight, Samples = getNodeSamples(0), SampleControlPoint = SampleControlPoint, }); @@ -176,6 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects { StartTime = e.Time, Position = EndPosition, + StackHeight = StackHeight }); break; diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json index b994cbd85a..004e7940d1 100644 --- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json @@ -143,14 +143,14 @@ "Objects": [{ "StartTime": 34989, "EndTime": 34989, - "X": 163, - "Y": 138 + "X": 156.597382, + "Y": 131.597382 }, { "StartTime": 35018, "EndTime": 35018, - "X": 188, - "Y": 138 + "X": 181.597382, + "Y": 131.597382 } ] }, @@ -159,14 +159,14 @@ "Objects": [{ "StartTime": 35106, "EndTime": 35106, - "X": 163, - "Y": 138 + "X": 159.798691, + "Y": 134.798691 }, { "StartTime": 35135, "EndTime": 35135, - "X": 188, - "Y": 138 + "X": 184.798691, + "Y": 134.798691 } ] }, @@ -191,20 +191,20 @@ "Objects": [{ "StartTime": 35695, "EndTime": 35695, - "X": 166, - "Y": 76 + "X": 162.798691, + "Y": 72.79869 }, { "StartTime": 35871, "EndTime": 35871, - "X": 240.99855, - "Y": 75.53417 + "X": 237.797241, + "Y": 72.33286 }, { "StartTime": 36011, "EndTime": 36011, - "X": 315.9971, - "Y": 75.0683441 + "X": 312.795776, + "Y": 71.8670349 } ] }, @@ -235,20 +235,20 @@ "Objects": [{ "StartTime": 36518, "EndTime": 36518, - "X": 166, - "Y": 76 + "X": 169.201309, + "Y": 79.20131 }, { "StartTime": 36694, "EndTime": 36694, - "X": 240.99855, - "Y": 75.53417 + "X": 244.19986, + "Y": 78.73548 }, { "StartTime": 36834, "EndTime": 36834, - "X": 315.9971, - "Y": 75.0683441 + "X": 319.198425, + "Y": 78.26965 } ] }, @@ -257,20 +257,20 @@ "Objects": [{ "StartTime": 36929, "EndTime": 36929, - "X": 315, - "Y": 75 + "X": 324.603943, + "Y": 84.6039352 }, { "StartTime": 37105, "EndTime": 37105, - "X": 240.001526, - "Y": 75.47769 + "X": 249.605469, + "Y": 85.08163 }, { "StartTime": 37245, "EndTime": 37245, - "X": 165.003052, - "Y": 75.95539 + "X": 174.607, + "Y": 85.5593262 } ] } diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index c01674f5b4..8b87ddaa20 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -159,8 +159,15 @@ namespace osu.Game.Graphics.Containers Height = Parent.Parent.DrawSize.Y * 1.5f; } - protected override void PopIn() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show); - protected override void PopOut() => this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide); + protected override void PopIn() => Schedule(() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show)); + + protected override void PopOut() + { + double duration = IsLoaded ? DISAPPEAR_DURATION : 0; + + // scheduling is required as parent may not be present at the time this is called. + Schedule(() => this.MoveToY(Parent.Parent.DrawSize.Y, duration, easing_hide)); + } } } } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 5c706781e6..11aba80d76 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -17,7 +17,7 @@ using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface { public class OsuSliderBar : SliderBar, IHasTooltip, IHasAccentColour - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, IEquatable, IComparable, IConvertible { /// /// Maximum number of decimal digits to be displayed in the tooltip. diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index e136fc1403..6de14c51ee 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -138,18 +138,13 @@ namespace osu.Game.Overlays.AccountCreation passwordTextBox.Current.ValueChanged += password => { characterCheckText.ForEach(s => s.Colour = password.NewValue.Length == 0 ? Color4.White : Interpolation.ValueAt(password.NewValue.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); }; } - protected override void Update() - { - base.Update(); - - if (host?.OnScreenKeyboardOverlapsGameWindow != true && !textboxes.Any(t => t.HasFocus)) - focusNextTextbox(); - } - public override void OnEntering(IScreen last) { base.OnEntering(last); processingOverlay.Hide(); + + if (host?.OnScreenKeyboardOverlapsGameWindow != true) + focusNextTextbox(); } private void performRegistration() diff --git a/osu.Game/Overlays/Settings/SettingsSlider.cs b/osu.Game/Overlays/Settings/SettingsSlider.cs index fd96ea972a..20e08c0cd8 100644 --- a/osu.Game/Overlays/Settings/SettingsSlider.cs +++ b/osu.Game/Overlays/Settings/SettingsSlider.cs @@ -8,12 +8,12 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { public class SettingsSlider : SettingsSlider> - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, IEquatable, IComparable, IConvertible { } public class SettingsSlider : SettingsItem - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, IEquatable, IComparable, IConvertible where U : OsuSliderBar, new() { protected override Drawable CreateControl() => new U diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 51155ce3fd..6396301add 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -162,11 +162,13 @@ namespace osu.Game.Rulesets.Edit inputManager = GetContainingInputManager(); } + private double lastGridUpdateTime; + protected override void Update() { base.Update(); - if (EditorClock.ElapsedFrameTime != 0 && blueprintContainer.CurrentTool != null) + if (EditorClock.CurrentTime != lastGridUpdateTime && blueprintContainer.CurrentTool != null) showGridFor(Enumerable.Empty()); } @@ -213,6 +215,8 @@ namespace osu.Game.Rulesets.Edit distanceSnapGridContainer.Child = distanceSnapGrid; distanceSnapGridContainer.Show(); } + + lastGridUpdateTime = EditorClock.CurrentTime; } private ScheduledDelegate scheduledUpdate; 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/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index 2aeb1ef04b..a724f354e7 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -10,7 +10,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit { - public class BindableBeatDivisor : BindableNumber + public class BindableBeatDivisor : BindableInt { public static readonly int[] VALID_DIVISORS = { 1, 2, 3, 4, 6, 8, 12, 16 }; 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/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 3cbf926d4f..a644e51c13 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osuTK; @@ -18,10 +19,32 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void CreateContent(Vector2 centrePosition) { + const float crosshair_thickness = 1; + const float crosshair_max_size = 10; + + AddRangeInternal(new[] + { + new Box + { + Origin = Anchor.Centre, + Position = centrePosition, + Width = crosshair_thickness, + EdgeSmoothness = new Vector2(1), + Height = Math.Min(crosshair_max_size, DistanceSpacing * 2), + }, + new Box + { + Origin = Anchor.Centre, + Position = centrePosition, + EdgeSmoothness = new Vector2(1), + Width = Math.Min(crosshair_max_size, DistanceSpacing * 2), + Height = crosshair_thickness, + } + }); + float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y); float maxDistance = new Vector2(dx, dy).Length; - int requiredCircles = (int)(maxDistance / DistanceSpacing); for (int i = 0; i < requiredCircles; i++) diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 299e78b7c0..096ff0a6dd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -36,15 +36,15 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected readonly Vector2 CentrePosition; + [Resolved] + protected OsuColour Colours { get; private set; } + [Resolved] private IEditorBeatmap beatmap { get; set; } [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - [Resolved] - private OsuColour colours { get; set; } - private readonly Cached gridCache = new Cached(); private readonly HitObject hitObject; @@ -136,7 +136,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected ColourInfo GetColourForBeatIndex(int index) { int beat = (index + 1) % beatDivisor.Value; - ColourInfo colour = colours.Gray5; + ColourInfo colour = Colours.Gray5; for (int i = 0; i < BindableBeatDivisor.VALID_DIVISORS.Length; i++) { @@ -144,7 +144,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if ((beat * divisor) % beatDivisor.Value == 0) { - colour = BindableBeatDivisor.GetColourFor(divisor, colours); + colour = BindableBeatDivisor.GetColourFor(divisor, Colours); break; } } 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/Play/PlayerSettings/PlayerSliderBar.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs index 28fe1f35ca..c8e281195a 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Play.PlayerSettings { public class PlayerSliderBar : SettingsSlider - where T : struct, IEquatable, IComparable, IConvertible + where T : struct, IEquatable, IComparable, IConvertible { public OsuSliderBar Bar => (OsuSliderBar)Control; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 64172a0954..e898a001de 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 5d384888d2..656c60543e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + +