From eea87090fb613538e670611bb9e2ad830f23fd83 Mon Sep 17 00:00:00 2001 From: B3nn1 Date: Sat, 6 Jan 2024 19:25:49 +0100 Subject: [PATCH 001/103] Make `changeHandler` save changes to `PathTypes` --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 24e2210b45..0cef93fbb5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -410,8 +410,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components var item = new TernaryStateRadioMenuItem(type?.Description ?? "Inherit", MenuItemType.Standard, _ => { + changeHandler?.BeginChange(); foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); + changeHandler?.EndChange(); }); if (countOfState == totalCount) From 26c0d1077a7a4d00fb9ae22bcb16dde08365b987 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 00:22:53 +0100 Subject: [PATCH 002/103] Refactor scale handling in editor to facilitate reuse --- .../Edit/OsuSelectionHandler.cs | 142 +----------- .../Edit/OsuSelectionScaleHandler.cs | 205 ++++++++++++++++++ .../Edit/Compose/Components/SelectionBox.cs | 2 - .../Components/SelectionBoxScaleHandle.cs | 94 +++++++- .../Compose/Components/SelectionHandler.cs | 10 +- .../Components/SelectionScaleHandler.cs | 88 ++++++++ osu.Game/Utils/GeometryUtils.cs | 9 + 7 files changed, 402 insertions(+), 148 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index cea2adc6e2..c36b535bfa 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; @@ -25,15 +24,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuSelectionHandler : EditorSelectionHandler { - [Resolved(CanBeNull = true)] - private IDistanceSnapProvider? snapProvider { get; set; } - - /// - /// During a transform, the initial path types of a single selected slider are stored so they - /// can be maintained throughout the operation. - /// - private List? referencePathTypes; - protected override void OnSelectionChanged() { base.OnSelectionChanged(); @@ -46,12 +36,6 @@ namespace osu.Game.Rulesets.Osu.Edit SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); } - protected override void OnOperationEnded() - { - base.OnOperationEnded(); - referencePathTypes = null; - } - protected override bool OnKeyDown(KeyDownEvent e) { if (e.Key == Key.M && e.ControlPressed && e.ShiftPressed) @@ -135,96 +119,9 @@ namespace osu.Game.Rulesets.Osu.Edit return didFlip; } - public override bool HandleScale(Vector2 scale, Anchor reference) - { - adjustScaleFromAnchor(ref scale, reference); - - var hitObjects = selectedMovableObjects; - - // for the time being, allow resizing of slider paths only if the slider is - // the only hit object selected. with a group selection, it's likely the user - // is not looking to change the duration of the slider but expand the whole pattern. - if (hitObjects.Length == 1 && hitObjects.First() is Slider slider) - scaleSlider(slider, scale); - else - scaleHitObjects(hitObjects, reference, scale); - - moveSelectionInBounds(); - return true; - } - - private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) - { - // cancel out scale in axes we don't care about (based on which drag handle was used). - if ((reference & Anchor.x1) > 0) scale.X = 0; - if ((reference & Anchor.y1) > 0) scale.Y = 0; - - // reverse the scale direction if dragging from top or left. - if ((reference & Anchor.x0) > 0) scale.X = -scale.X; - if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; - } - public override SelectionRotationHandler CreateRotationHandler() => new OsuSelectionRotationHandler(); - private void scaleSlider(Slider slider, Vector2 scale) - { - referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type).ToList(); - - Quad sliderQuad = GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)); - - // Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0. - scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size; - - Vector2 pathRelativeDeltaScale = new Vector2( - sliderQuad.Width == 0 ? 0 : 1 + scale.X / sliderQuad.Width, - sliderQuad.Height == 0 ? 0 : 1 + scale.Y / sliderQuad.Height); - - Queue oldControlPoints = new Queue(); - - foreach (var point in slider.Path.ControlPoints) - { - oldControlPoints.Enqueue(point.Position); - point.Position *= pathRelativeDeltaScale; - } - - // Maintain the path types in case they were defaulted to bezier at some point during scaling - for (int i = 0; i < slider.Path.ControlPoints.Count; ++i) - slider.Path.ControlPoints[i].Type = referencePathTypes[i]; - - // Snap the slider's length to the current beat divisor - // to calculate the final resulting duration / bounding box before the final checks. - slider.SnapTo(snapProvider); - - //if sliderhead or sliderend end up outside playfield, revert scaling. - Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider }); - (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); - - if (xInBounds && yInBounds && slider.Path.HasValidLength) - return; - - foreach (var point in slider.Path.ControlPoints) - point.Position = oldControlPoints.Dequeue(); - - // Snap the slider's length again to undo the potentially-invalid length applied by the previous snap. - slider.SnapTo(snapProvider); - } - - private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) - { - scale = getClampedScale(hitObjects, reference, scale); - Quad selectionQuad = GeometryUtils.GetSurroundingQuad(hitObjects); - - foreach (var h in hitObjects) - h.Position = GeometryUtils.GetScaledPosition(reference, scale, selectionQuad, h.Position); - } - - private (bool X, bool Y) isQuadInBounds(Quad quad) - { - bool xInBounds = (quad.TopLeft.X >= 0) && (quad.BottomRight.X <= DrawWidth); - bool yInBounds = (quad.TopLeft.Y >= 0) && (quad.BottomRight.Y <= DrawHeight); - - return (xInBounds, yInBounds); - } + public override SelectionScaleHandler CreateScaleHandler() => new OsuSelectionScaleHandler(); private void moveSelectionInBounds() { @@ -248,43 +145,6 @@ namespace osu.Game.Rulesets.Osu.Edit h.Position += delta; } - /// - /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. - /// - /// The hitobjects to be scaled - /// The anchor from which the scale operation is performed - /// The scale to be clamped - /// The clamped scale vector - private Vector2 getClampedScale(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) - { - float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; - float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; - - Quad selectionQuad = GeometryUtils.GetSurroundingQuad(hitObjects); - - //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. - Quad scaledQuad = new Quad(selectionQuad.TopLeft.X + xOffset, selectionQuad.TopLeft.Y + yOffset, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); - - //max Size -> playfield bounds - if (scaledQuad.TopLeft.X < 0) - scale.X += scaledQuad.TopLeft.X; - if (scaledQuad.TopLeft.Y < 0) - scale.Y += scaledQuad.TopLeft.Y; - - if (scaledQuad.BottomRight.X > DrawWidth) - scale.X -= scaledQuad.BottomRight.X - DrawWidth; - if (scaledQuad.BottomRight.Y > DrawHeight) - scale.Y -= scaledQuad.BottomRight.Y - DrawHeight; - - //min Size -> almost 0. Less than 0 causes the quad to flip, exactly 0 causes scaling to get stuck at minimum scale. - Vector2 scaledSize = selectionQuad.Size + scale; - Vector2 minSize = new Vector2(Precision.FLOAT_EPSILON); - - scale = Vector2.ComponentMax(minSize, scaledSize) - selectionQuad.Size; - - return scale; - } - /// /// All osu! hitobjects which can be moved/rotated/scaled. /// diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs new file mode 100644 index 0000000000..8068c73131 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -0,0 +1,205 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class OsuSelectionScaleHandler : SelectionScaleHandler + { + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + [Resolved(CanBeNull = true)] + private IDistanceSnapProvider? snapProvider { get; set; } + + private BindableList selectedItems { get; } = new BindableList(); + + [BackgroundDependencyLoader] + private void load(EditorBeatmap editorBeatmap) + { + selectedItems.BindTo(editorBeatmap.SelectedHitObjects); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedItems.CollectionChanged += (_, __) => updateState(); + updateState(); + } + + private void updateState() + { + var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); + CanScale.Value = quad.Width > 0 || quad.Height > 0; + } + + private OsuHitObject[]? objectsInScale; + + private Vector2? defaultOrigin; + private Dictionary? originalPositions; + private Dictionary? originalPathControlPointPositions; + private Dictionary? originalPathControlPointTypes; + + public override void Begin() + { + if (objectsInScale != null) + throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!"); + + changeHandler?.BeginChange(); + + objectsInScale = selectedMovableObjects.ToArray(); + OriginalSurroundingQuad = objectsInScale.Length == 1 && objectsInScale.First() is Slider slider + ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)) + : GeometryUtils.GetSurroundingQuad(objectsInScale); + defaultOrigin = OriginalSurroundingQuad.Value.Centre; + originalPositions = objectsInScale.ToDictionary(obj => obj, obj => obj.Position); + originalPathControlPointPositions = objectsInScale.OfType().ToDictionary( + obj => obj, + obj => obj.Path.ControlPoints.Select(point => point.Position).ToArray()); + originalPathControlPointTypes = objectsInScale.OfType().ToDictionary( + obj => obj, + obj => obj.Path.ControlPoints.Select(p => p.Type).ToArray()); + } + + public override void Update(Vector2 scale, Vector2? origin = null) + { + if (objectsInScale == null) + throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); + + Debug.Assert(originalPositions != null && originalPathControlPointPositions != null && defaultOrigin != null && originalPathControlPointTypes != null && OriginalSurroundingQuad != null); + + Vector2 actualOrigin = origin ?? defaultOrigin.Value; + + // for the time being, allow resizing of slider paths only if the slider is + // the only hit object selected. with a group selection, it's likely the user + // is not looking to change the duration of the slider but expand the whole pattern. + if (objectsInScale.Length == 1 && objectsInScale.First() is Slider slider) + scaleSlider(slider, scale, originalPathControlPointPositions[slider], originalPathControlPointTypes[slider]); + else + { + scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale); + + foreach (var ho in objectsInScale) + { + ho.Position = GeometryUtils.GetScaledPositionMultiply(scale, actualOrigin, originalPositions[ho]); + } + } + + moveSelectionInBounds(); + } + + public override void Commit() + { + if (objectsInScale == null) + throw new InvalidOperationException($"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!"); + + changeHandler?.EndChange(); + + objectsInScale = null; + OriginalSurroundingQuad = null; + originalPositions = null; + originalPathControlPointPositions = null; + originalPathControlPointTypes = null; + defaultOrigin = null; + } + + private IEnumerable selectedMovableObjects => selectedItems.Cast() + .Where(h => h is not Spinner); + + private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes) + { + // Maintain the path types in case they were defaulted to bezier at some point during scaling + for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + { + slider.Path.ControlPoints[i].Position = originalPathPositions[i] * scale; + slider.Path.ControlPoints[i].Type = originalPathTypes[i]; + } + + // Snap the slider's length to the current beat divisor + // to calculate the final resulting duration / bounding box before the final checks. + slider.SnapTo(snapProvider); + + //if sliderhead or sliderend end up outside playfield, revert scaling. + Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider }); + (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); + + if (xInBounds && yInBounds && slider.Path.HasValidLength) + return; + + for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + slider.Path.ControlPoints[i].Position = originalPathPositions[i]; + + // Snap the slider's length again to undo the potentially-invalid length applied by the previous snap. + slider.SnapTo(snapProvider); + } + + private (bool X, bool Y) isQuadInBounds(Quad quad) + { + bool xInBounds = (quad.TopLeft.X >= 0) && (quad.BottomRight.X <= OsuPlayfield.BASE_SIZE.X); + bool yInBounds = (quad.TopLeft.Y >= 0) && (quad.BottomRight.Y <= OsuPlayfield.BASE_SIZE.Y); + + return (xInBounds, yInBounds); + } + + /// + /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. + /// + /// The quad surrounding the hitobjects + /// The origin from which the scale operation is performed + /// The scale to be clamped + /// The clamped scale vector + private Vector2 getClampedScale(Quad selectionQuad, Vector2 origin, Vector2 scale) + { + //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. + + var tl1 = Vector2.Divide(-origin, selectionQuad.TopLeft - origin); + var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.TopLeft - origin); + var br1 = Vector2.Divide(-origin, selectionQuad.BottomRight - origin); + var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.BottomRight - origin); + + scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); + scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); + scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); + scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); + + return scale; + } + + private void moveSelectionInBounds() + { + Quad quad = GeometryUtils.GetSurroundingQuad(objectsInScale!); + + Vector2 delta = Vector2.Zero; + + if (quad.TopLeft.X < 0) + delta.X -= quad.TopLeft.X; + if (quad.TopLeft.Y < 0) + delta.Y -= quad.TopLeft.Y; + + if (quad.BottomRight.X > OsuPlayfield.BASE_SIZE.X) + delta.X -= quad.BottomRight.X - OsuPlayfield.BASE_SIZE.X; + if (quad.BottomRight.Y > OsuPlayfield.BASE_SIZE.Y) + delta.Y -= quad.BottomRight.Y - OsuPlayfield.BASE_SIZE.Y; + + foreach (var h in objectsInScale!) + h.Position += delta; + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 0b16941bc4..e8b3e430eb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -27,7 +27,6 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private SelectionRotationHandler? rotationHandler { get; set; } - public Func? OnScale; public Func? OnFlip; public Func? OnReverse; @@ -353,7 +352,6 @@ namespace osu.Game.Screens.Edit.Compose.Components var handle = new SelectionBoxScaleHandle { Anchor = anchor, - HandleScale = (delta, a) => OnScale?.Invoke(delta, a) }; handle.OperationStarted += operationStarted; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 7943065c82..56c5585ae7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -1,19 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { public partial class SelectionBoxScaleHandle : SelectionBoxDragHandle { - public Action HandleScale { get; set; } + [Resolved] + private SelectionBox selectionBox { get; set; } = null!; + + [Resolved] + private SelectionScaleHandler? scaleHandler { get; set; } [BackgroundDependencyLoader] private void load() @@ -21,10 +24,93 @@ namespace osu.Game.Screens.Edit.Compose.Components Size = new Vector2(10); } + protected override bool OnDragStart(DragStartEvent e) + { + if (e.Button != MouseButton.Left) + return false; + + if (scaleHandler == null) return false; + + scaleHandler.Begin(); + return true; + } + + private Vector2 getOriginPosition() + { + var quad = scaleHandler!.OriginalSurroundingQuad!.Value; + Vector2 origin = quad.TopLeft; + + if ((Anchor & Anchor.x0) > 0) + origin.X += quad.Width; + + if ((Anchor & Anchor.y0) > 0) + origin.Y += quad.Height; + + return origin; + } + + private Vector2 rawScale; + protected override void OnDrag(DragEvent e) { - HandleScale?.Invoke(e.Delta, Anchor); base.OnDrag(e); + + if (scaleHandler == null) return; + + rawScale = convertDragEventToScaleMultiplier(e); + + applyScale(shouldKeepAspectRatio: e.ShiftPressed); + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) + { + applyScale(shouldKeepAspectRatio: true); + return true; + } + + return base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyUpEvent e) + { + base.OnKeyUp(e); + + if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) + applyScale(shouldKeepAspectRatio: false); + } + + protected override void OnDragEnd(DragEndEvent e) + { + scaleHandler?.Commit(); + } + + private Vector2 convertDragEventToScaleMultiplier(DragEvent e) + { + Vector2 scale = e.MousePosition - e.MouseDownPosition; + adjustScaleFromAnchor(ref scale); + return Vector2.Divide(scale, scaleHandler!.OriginalSurroundingQuad!.Value.Size) + Vector2.One; + } + + private void adjustScaleFromAnchor(ref Vector2 scale) + { + // cancel out scale in axes we don't care about (based on which drag handle was used). + if ((Anchor & Anchor.x1) > 0) scale.X = 1; + if ((Anchor & Anchor.y1) > 0) scale.Y = 1; + + // reverse the scale direction if dragging from top or left. + if ((Anchor & Anchor.x0) > 0) scale.X = -scale.X; + if ((Anchor & Anchor.y0) > 0) scale.Y = -scale.Y; + } + + private void applyScale(bool shouldKeepAspectRatio) + { + var newScale = shouldKeepAspectRatio + ? new Vector2(MathF.Max(rawScale.X, rawScale.Y)) + : rawScale; + + scaleHandler!.Update(newScale, getOriginPosition()); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 3c859c65ff..dd6bd43f4d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -57,6 +57,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public SelectionRotationHandler RotationHandler { get; private set; } + public SelectionScaleHandler ScaleHandler { get; private set; } + protected SelectionHandler() { selectedBlueprints = new List>(); @@ -69,6 +71,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(RotationHandler = CreateRotationHandler()); + dependencies.CacheAs(ScaleHandler = CreateScaleHandler()); return dependencies; } @@ -78,6 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components AddRangeInternal(new Drawable[] { RotationHandler, + ScaleHandler, SelectionBox = CreateSelectionBox(), }); @@ -93,7 +97,6 @@ namespace osu.Game.Screens.Edit.Compose.Components OperationStarted = OnOperationBegan, OperationEnded = OnOperationEnded, - OnScale = HandleScale, OnFlip = HandleFlip, OnReverse = HandleReverse, }; @@ -157,6 +160,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any items could be scaled. public virtual bool HandleScale(Vector2 scale, Anchor anchor) => false; + /// + /// Creates the handler to use for scale operations. + /// + public virtual SelectionScaleHandler CreateScaleHandler() => new SelectionScaleHandler(); + /// /// Handles the selected items being flipped. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs new file mode 100644 index 0000000000..b7c8f16a02 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -0,0 +1,88 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// Base handler for editor scale operations. + /// + public partial class SelectionScaleHandler : Component + { + /// + /// Whether the scale can currently be performed. + /// + public Bindable CanScale { get; private set; } = new BindableBool(); + + public Quad? OriginalSurroundingQuad { get; protected set; } + + /// + /// Performs a single, instant, atomic scale operation. + /// + /// + /// This method is intended to be used in atomic contexts (such as when pressing a single button). + /// For continuous operations, see the -- flow. + /// + /// The scale to apply, as multiplier. + /// + /// The origin point to scale from. + /// If the default value is supplied, a sane implementation-defined default will be used. + /// + public void ScaleSelection(Vector2 scale, Vector2? origin = null) + { + Begin(); + Update(scale, origin); + Commit(); + } + + /// + /// Begins a continuous scale operation. + /// + /// + /// This flow is intended to be used when a scale operation is made incrementally (such as when dragging a scale handle or slider). + /// For instantaneous, atomic operations, use the convenience method. + /// + public virtual void Begin() + { + } + + /// + /// Updates a continuous scale operation. + /// Must be preceded by a call. + /// + /// + /// + /// This flow is intended to be used when a scale operation is made incrementally (such as when dragging a scale handle or slider). + /// As such, the values of and supplied should be relative to the state of the objects being scaled + /// when was called, rather than instantaneous deltas. + /// + /// + /// For instantaneous, atomic operations, use the convenience method. + /// + /// + /// The Scale to apply, as multiplier. + /// + /// The origin point to scale from. + /// If the default value is supplied, a sane implementation-defined default will be used. + /// + public virtual void Update(Vector2 scale, Vector2? origin = null) + { + } + + /// + /// Ends a continuous scale operation. + /// Must be preceded by a call. + /// + /// + /// This flow is intended to be used when a scale operation is made incrementally (such as when dragging a scale handle or slider). + /// For instantaneous, atomic operations, use the convenience method. + /// + public virtual void Commit() + { + } + } +} diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 725e93d098..ef362d8223 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -79,6 +79,15 @@ namespace osu.Game.Utils return position; } + /// + /// Given a scale multiplier, an origin, and a position, + /// will return the scaled position in screen space coordinates. + /// + public static Vector2 GetScaledPositionMultiply(Vector2 scale, Vector2 origin, Vector2 position) + { + return origin + (position - origin) * scale; + } + /// /// Returns a quad surrounding the provided points. /// From a4f771ec089baff91ddea3d4714355e64a8237dd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 01:13:01 +0100 Subject: [PATCH 003/103] refactor CanScale properties --- .../Edit/OsuSelectionHandler.cs | 5 +- .../Edit/OsuSelectionScaleHandler.cs | 7 +- .../SkinEditor/SkinSelectionHandler.cs | 3 - .../Edit/Compose/Components/SelectionBox.cs | 76 +++++-------------- .../Components/SelectionScaleHandler.cs | 18 ++++- osu.Game/Utils/GeometryUtils.cs | 2 +- 6 files changed, 44 insertions(+), 67 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index c36b535bfa..00c90cdbd6 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -30,9 +30,8 @@ namespace osu.Game.Rulesets.Osu.Edit Quad quad = selectedMovableObjects.Length > 0 ? GeometryUtils.GetSurroundingQuad(selectedMovableObjects) : new Quad(); - SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 0; - SelectionBox.CanFlipY = SelectionBox.CanScaleY = quad.Height > 0; - SelectionBox.CanScaleDiagonally = SelectionBox.CanScaleX && SelectionBox.CanScaleY; + SelectionBox.CanFlipX = quad.Width > 0; + SelectionBox.CanFlipY = quad.Height > 0; SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 8068c73131..7b0ae947e7 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -47,7 +47,10 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateState() { var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); - CanScale.Value = quad.Width > 0 || quad.Height > 0; + + CanScaleX.Value = quad.Width > 0; + CanScaleY.Value = quad.Height > 0; + CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; } private OsuHitObject[]? objectsInScale; @@ -98,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var ho in objectsInScale) { - ho.Position = GeometryUtils.GetScaledPositionMultiply(scale, actualOrigin, originalPositions[ho]); + ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalPositions[ho]); } } diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index cf6fb60636..efca6f0080 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -218,9 +218,6 @@ namespace osu.Game.Overlays.SkinEditor { base.OnSelectionChanged(); - SelectionBox.CanScaleX = allSelectedSupportManualSizing(Axes.X); - SelectionBox.CanScaleY = allSelectedSupportManualSizing(Axes.Y); - SelectionBox.CanScaleDiagonally = true; SelectionBox.CanFlipX = true; SelectionBox.CanFlipY = true; SelectionBox.CanReverse = false; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index e8b3e430eb..2329a466fe 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -27,6 +27,9 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private SelectionRotationHandler? rotationHandler { get; set; } + [Resolved] + private SelectionScaleHandler? scaleHandler { get; set; } + public Func? OnFlip; public Func? OnReverse; @@ -56,60 +59,11 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly IBindable canRotate = new BindableBool(); - private bool canScaleX; + private readonly IBindable canScaleX = new BindableBool(); - /// - /// Whether horizontal scaling (from the left or right edge) support should be enabled. - /// - public bool CanScaleX - { - get => canScaleX; - set - { - if (canScaleX == value) return; + private readonly IBindable canScaleY = new BindableBool(); - canScaleX = value; - recreate(); - } - } - - private bool canScaleY; - - /// - /// Whether vertical scaling (from the top or bottom edge) support should be enabled. - /// - public bool CanScaleY - { - get => canScaleY; - set - { - if (canScaleY == value) return; - - canScaleY = value; - recreate(); - } - } - - private bool canScaleDiagonally; - - /// - /// Whether diagonal scaling (from a corner) support should be enabled. - /// - /// - /// There are some cases where we only want to allow proportional resizing, and not allow - /// one or both explicit directions of scale. - /// - public bool CanScaleDiagonally - { - get => canScaleDiagonally; - set - { - if (canScaleDiagonally == value) return; - - canScaleDiagonally = value; - recreate(); - } - } + private readonly IBindable canScaleDiagonally = new BindableBool(); private bool canFlipX; @@ -175,7 +129,17 @@ namespace osu.Game.Screens.Edit.Compose.Components if (rotationHandler != null) canRotate.BindTo(rotationHandler.CanRotate); - canRotate.BindValueChanged(_ => recreate(), true); + if (scaleHandler != null) + { + canScaleX.BindTo(scaleHandler.CanScaleX); + canScaleY.BindTo(scaleHandler.CanScaleY); + canScaleDiagonally.BindTo(scaleHandler.CanScaleDiagonally); + } + + canRotate.BindValueChanged(_ => recreate()); + canScaleX.BindValueChanged(_ => recreate()); + canScaleY.BindValueChanged(_ => recreate()); + canScaleDiagonally.BindValueChanged(_ => recreate(), true); } protected override bool OnKeyDown(KeyDownEvent e) @@ -264,9 +228,9 @@ namespace osu.Game.Screens.Edit.Compose.Components } }; - if (CanScaleX) addXScaleComponents(); - if (CanScaleDiagonally) addFullScaleComponents(); - if (CanScaleY) addYScaleComponents(); + if (canScaleX.Value) addXScaleComponents(); + if (canScaleDiagonally.Value) addFullScaleComponents(); + if (canScaleY.Value) addYScaleComponents(); if (CanFlipX) addXFlipComponents(); if (CanFlipY) addYFlipComponents(); if (canRotate.Value) addRotationComponents(); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index b7c8f16a02..59406b3184 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -14,9 +14,23 @@ namespace osu.Game.Screens.Edit.Compose.Components public partial class SelectionScaleHandler : Component { /// - /// Whether the scale can currently be performed. + /// Whether horizontal scaling (from the left or right edge) support should be enabled. /// - public Bindable CanScale { get; private set; } = new BindableBool(); + public Bindable CanScaleX { get; private set; } = new BindableBool(); + + /// + /// Whether vertical scaling (from the top or bottom edge) support should be enabled. + /// + public Bindable CanScaleY { get; private set; } = new BindableBool(); + + /// + /// Whether diagonal scaling (from a corner) support should be enabled. + /// + /// + /// There are some cases where we only want to allow proportional resizing, and not allow + /// one or both explicit directions of scale. + /// + public Bindable CanScaleDiagonally { get; private set; } = new BindableBool(); public Quad? OriginalSurroundingQuad { get; protected set; } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index ef362d8223..6d8237ea34 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -83,7 +83,7 @@ namespace osu.Game.Utils /// Given a scale multiplier, an origin, and a position, /// will return the scaled position in screen space coordinates. /// - public static Vector2 GetScaledPositionMultiply(Vector2 scale, Vector2 origin, Vector2 position) + public static Vector2 GetScaledPosition(Vector2 scale, Vector2 origin, Vector2 position) { return origin + (position - origin) * scale; } From bc0e6baba70cd9c69dbf9f87180c24c8a47dcff9 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 01:13:05 +0100 Subject: [PATCH 004/103] fix test --- .../Editing/TestSceneComposeSelectBox.cs | 77 ++++++++++++------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index f6637d0e80..680a76f9b8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -10,9 +10,11 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -26,9 +28,13 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(SelectionRotationHandler))] private TestSelectionRotationHandler rotationHandler; + [Cached(typeof(SelectionScaleHandler))] + private TestSelectionScaleHandler scaleHandler; + public TestSceneComposeSelectBox() { rotationHandler = new TestSelectionRotationHandler(() => selectionArea); + scaleHandler = new TestSelectionScaleHandler(() => selectionArea); } [SetUp] @@ -45,13 +51,8 @@ namespace osu.Game.Tests.Visual.Editing { RelativeSizeAxes = Axes.Both, - CanScaleX = true, - CanScaleY = true, - CanScaleDiagonally = true, CanFlipX = true, CanFlipY = true, - - OnScale = handleScale } } }; @@ -60,27 +61,6 @@ namespace osu.Game.Tests.Visual.Editing InputManager.ReleaseButton(MouseButton.Left); }); - private bool handleScale(Vector2 amount, Anchor reference) - { - if ((reference & Anchor.y1) == 0) - { - int directionY = (reference & Anchor.y0) > 0 ? -1 : 1; - if (directionY < 0) - selectionArea.Y += amount.Y; - selectionArea.Height += directionY * amount.Y; - } - - if ((reference & Anchor.x1) == 0) - { - int directionX = (reference & Anchor.x0) > 0 ? -1 : 1; - if (directionX < 0) - selectionArea.X += amount.X; - selectionArea.Width += directionX * amount.X; - } - - return true; - } - private partial class TestSelectionRotationHandler : SelectionRotationHandler { private readonly Func getTargetContainer; @@ -125,6 +105,51 @@ namespace osu.Game.Tests.Visual.Editing } } + private partial class TestSelectionScaleHandler : SelectionScaleHandler + { + private readonly Func getTargetContainer; + + public TestSelectionScaleHandler(Func getTargetContainer) + { + this.getTargetContainer = getTargetContainer; + + CanScaleX.Value = true; + CanScaleY.Value = true; + CanScaleDiagonally.Value = true; + } + + [CanBeNull] + private Container targetContainer; + + public override void Begin() + { + if (targetContainer != null) + throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!"); + + targetContainer = getTargetContainer(); + OriginalSurroundingQuad = new Quad(targetContainer!.X, targetContainer.Y, targetContainer.Width, targetContainer.Height); + } + + public override void Update(Vector2 scale, Vector2? origin = null) + { + if (targetContainer == null) + throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); + + Vector2 actualOrigin = origin ?? Vector2.Zero; + + targetContainer.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, OriginalSurroundingQuad!.Value.TopLeft); + targetContainer.Size = OriginalSurroundingQuad!.Value.Size * scale; + } + + public override void Commit() + { + if (targetContainer == null) + throw new InvalidOperationException($"Cannot {nameof(Commit)} a scale operation without calling {nameof(Begin)} first!"); + + targetContainer = null; + } + } + [Test] public void TestRotationHandleShownOnHover() { From ed430a3df4bbcacf5860db8f21ac625d2a176bbc Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 02:49:56 +0100 Subject: [PATCH 005/103] refactor skin editor scale --- .../SkinEditor/SkinSelectionHandler.cs | 157 +------------- .../SkinEditor/SkinSelectionScaleHandler.cs | 198 ++++++++++++++++++ 2 files changed, 204 insertions(+), 151 deletions(-) create mode 100644 osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index efca6f0080..2d8db61ee7 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -7,10 +7,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; @@ -31,148 +29,16 @@ namespace osu.Game.Overlays.SkinEditor UpdatePosition = updateDrawablePosition }; - private bool allSelectedSupportManualSizing(Axes axis) => SelectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false); - - public override bool HandleScale(Vector2 scale, Anchor anchor) + public override SelectionScaleHandler CreateScaleHandler() { - Axes adjustAxis; - - switch (anchor) + var scaleHandler = new SkinSelectionScaleHandler { - // for corners, adjust scale. - case Anchor.TopLeft: - case Anchor.TopRight: - case Anchor.BottomLeft: - case Anchor.BottomRight: - adjustAxis = Axes.Both; - break; + UpdatePosition = updateDrawablePosition + }; - // for edges, adjust size. - // autosize elements can't be easily handled so just disable sizing for now. - case Anchor.TopCentre: - case Anchor.BottomCentre: - if (!allSelectedSupportManualSizing(Axes.Y)) - return false; + scaleHandler.PerformFlipFromScaleHandles += a => SelectionBox.PerformFlipFromScaleHandles(a); - adjustAxis = Axes.Y; - break; - - case Anchor.CentreLeft: - case Anchor.CentreRight: - if (!allSelectedSupportManualSizing(Axes.X)) - return false; - - adjustAxis = Axes.X; - break; - - default: - throw new ArgumentOutOfRangeException(nameof(anchor), anchor, null); - } - - // convert scale to screen space - scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero); - - adjustScaleFromAnchor(ref scale, anchor); - - // the selection quad is always upright, so use an AABB rect to make mutating the values easier. - var selectionRect = getSelectionQuad().AABBFloat; - - // If the selection has no area we cannot scale it - if (selectionRect.Area == 0) - return false; - - // copy to mutate, as we will need to compare to the original later on. - var adjustedRect = selectionRect; - bool isRotated = false; - - // for now aspect lock scale adjustments that occur at corners.. - if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) - { - // project scale vector along diagonal - Vector2 diag = (selectionRect.TopLeft - selectionRect.BottomRight).Normalized(); - scale = Vector2.Dot(scale, diag) * diag; - } - // ..or if any of the selection have been rotated. - // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). - else if (SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation % 90, 0))) - { - isRotated = true; - if (anchor.HasFlagFast(Anchor.x1)) - // if dragging from the horizontal centre, only a vertical component is available. - scale.X = scale.Y / selectionRect.Height * selectionRect.Width; - else - // in all other cases (arbitrarily) use the horizontal component for aspect lock. - scale.Y = scale.X / selectionRect.Width * selectionRect.Height; - } - - if (anchor.HasFlagFast(Anchor.x0)) adjustedRect.X -= scale.X; - if (anchor.HasFlagFast(Anchor.y0)) adjustedRect.Y -= scale.Y; - - // Maintain the selection's centre position if dragging from the centre anchors and selection is rotated. - if (isRotated && anchor.HasFlagFast(Anchor.x1)) adjustedRect.X -= scale.X / 2; - if (isRotated && anchor.HasFlagFast(Anchor.y1)) adjustedRect.Y -= scale.Y / 2; - - adjustedRect.Width += scale.X; - adjustedRect.Height += scale.Y; - - if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0) - { - Axes toFlip = Axes.None; - - if (adjustedRect.Width <= 0) toFlip |= Axes.X; - if (adjustedRect.Height <= 0) toFlip |= Axes.Y; - - SelectionBox.PerformFlipFromScaleHandles(toFlip); - return true; - } - - // scale adjust applied to each individual item should match that of the quad itself. - var scaledDelta = new Vector2( - adjustedRect.Width / selectionRect.Width, - adjustedRect.Height / selectionRect.Height - ); - - foreach (var b in SelectedBlueprints) - { - var drawableItem = (Drawable)b.Item; - - // each drawable's relative position should be maintained in the scaled quad. - var screenPosition = b.ScreenSpaceSelectionPoint; - - var relativePositionInOriginal = - new Vector2( - (screenPosition.X - selectionRect.TopLeft.X) / selectionRect.Width, - (screenPosition.Y - selectionRect.TopLeft.Y) / selectionRect.Height - ); - - var newPositionInAdjusted = new Vector2( - adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X, - adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y - ); - - updateDrawablePosition(drawableItem, newPositionInAdjusted); - - var currentScaledDelta = scaledDelta; - if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90)) - currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); - - switch (adjustAxis) - { - case Axes.X: - drawableItem.Width *= currentScaledDelta.X; - break; - - case Axes.Y: - drawableItem.Height *= currentScaledDelta.Y; - break; - - case Axes.Both: - drawableItem.Scale *= currentScaledDelta; - break; - } - } - - return true; + return scaleHandler; } public override bool HandleFlip(Direction direction, bool flipOverOrigin) @@ -410,16 +276,5 @@ namespace osu.Game.Overlays.SkinEditor drawable.Anchor = anchor; drawable.Position -= drawable.AnchorPosition - previousAnchor; } - - private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) - { - // cancel out scale in axes we don't care about (based on which drag handle was used). - if ((reference & Anchor.x1) > 0) scale.X = 0; - if ((reference & Anchor.y1) > 0) scale.Y = 0; - - // reverse the scale direction if dragging from top or left. - if ((reference & Anchor.x0) > 0) scale.X = -scale.X; - if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; - } } } diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs new file mode 100644 index 0000000000..46b39645b2 --- /dev/null +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -0,0 +1,198 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Skinning; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Overlays.SkinEditor +{ + public partial class SkinSelectionScaleHandler : SelectionScaleHandler + { + public Action UpdatePosition { get; init; } = null!; + + public event Action? PerformFlipFromScaleHandles; + + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + private BindableList selectedItems { get; } = new BindableList(); + + [BackgroundDependencyLoader] + private void load(SkinEditor skinEditor) + { + selectedItems.BindTo(skinEditor.SelectedComponents); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedItems.CollectionChanged += (_, __) => updateState(); + updateState(); + } + + private void updateState() + { + CanScaleX.Value = allSelectedSupportManualSizing(Axes.X); + CanScaleY.Value = allSelectedSupportManualSizing(Axes.Y); + CanScaleDiagonally.Value = true; + } + + private bool allSelectedSupportManualSizing(Axes axis) => selectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false); + + private Drawable[]? objectsInScale; + + private Vector2? defaultOrigin; + private Dictionary? originalWidths; + private Dictionary? originalHeights; + private Dictionary? originalScales; + private Dictionary? originalPositions; + + public override void Begin() + { + if (objectsInScale != null) + throw new InvalidOperationException($"Cannot {nameof(Begin)} a scale operation while another is in progress!"); + + changeHandler?.BeginChange(); + + objectsInScale = selectedItems.Cast().ToArray(); + originalWidths = objectsInScale.ToDictionary(d => d, d => d.Width); + originalHeights = objectsInScale.ToDictionary(d => d, d => d.Height); + originalScales = objectsInScale.ToDictionary(d => d, d => d.Scale); + originalPositions = objectsInScale.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition)); + OriginalSurroundingQuad = GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray())); + defaultOrigin = OriginalSurroundingQuad.Value.Centre; + } + + public override void Update(Vector2 scale, Vector2? origin = null) + { + if (objectsInScale == null) + throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); + + Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); + + var actualOrigin = origin ?? defaultOrigin.Value; + + Axes adjustAxis = scale.X == 0 ? Axes.Y : scale.Y == 0 ? Axes.X : Axes.Both; + + if ((adjustAxis == Axes.Y && !allSelectedSupportManualSizing(Axes.Y)) || + (adjustAxis == Axes.X && !allSelectedSupportManualSizing(Axes.X))) + return; + + // the selection quad is always upright, so use an AABB rect to make mutating the values easier. + var selectionRect = OriginalSurroundingQuad.Value.AABBFloat; + + // If the selection has no area we cannot scale it + if (selectionRect.Area == 0) + return; + + // copy to mutate, as we will need to compare to the original later on. + var adjustedRect = selectionRect; + + // for now aspect lock scale adjustments that occur at corners.. + if (adjustAxis == Axes.Both) + { + // project scale vector along diagonal + Vector2 diag = new Vector2(1, 1).Normalized(); + scale = Vector2.Dot(scale, diag) * diag; + } + // ..or if any of the selection have been rotated. + // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). + else if (objectsInScale.Any(b => !Precision.AlmostEquals(b.Rotation % 90, 0))) + { + if (adjustAxis == Axes.Y) + // if dragging from the horizontal centre, only a vertical component is available. + scale.X = scale.Y / selectionRect.Height * selectionRect.Width; + else + // in all other cases (arbitrarily) use the horizontal component for aspect lock. + scale.Y = scale.X / selectionRect.Width * selectionRect.Height; + } + + adjustedRect.Location = GeometryUtils.GetScaledPosition(scale, actualOrigin, OriginalSurroundingQuad!.Value.TopLeft); + adjustedRect.Size = OriginalSurroundingQuad!.Value.Size * scale; + + if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0) + { + Axes toFlip = Axes.None; + + if (adjustedRect.Width <= 0) toFlip |= Axes.X; + if (adjustedRect.Height <= 0) toFlip |= Axes.Y; + + PerformFlipFromScaleHandles?.Invoke(toFlip); + return; + } + + // scale adjust applied to each individual item should match that of the quad itself. + var scaledDelta = new Vector2( + adjustedRect.Width / selectionRect.Width, + adjustedRect.Height / selectionRect.Height + ); + + foreach (var b in objectsInScale) + { + // each drawable's relative position should be maintained in the scaled quad. + var screenPosition = originalPositions[b]; + + var relativePositionInOriginal = + new Vector2( + (screenPosition.X - selectionRect.TopLeft.X) / selectionRect.Width, + (screenPosition.Y - selectionRect.TopLeft.Y) / selectionRect.Height + ); + + var newPositionInAdjusted = new Vector2( + adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X, + adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y + ); + + UpdatePosition(b, newPositionInAdjusted); + + var currentScaledDelta = scaledDelta; + if (Precision.AlmostEquals(MathF.Abs(b.Rotation) % 180, 90)) + currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); + + switch (adjustAxis) + { + case Axes.X: + b.Width = originalWidths[b] * currentScaledDelta.X; + break; + + case Axes.Y: + b.Height = originalHeights[b] * currentScaledDelta.Y; + break; + + case Axes.Both: + b.Scale = originalScales[b] * currentScaledDelta; + break; + } + } + } + + public override void Commit() + { + if (objectsInScale == null) + throw new InvalidOperationException($"Cannot {nameof(Commit)} a scale operation without calling {nameof(Begin)} first!"); + + changeHandler?.EndChange(); + + objectsInScale = null; + originalPositions = null; + originalWidths = null; + originalHeights = null; + originalScales = null; + defaultOrigin = null; + } + } +} From 6a57be0a50c8ddc20356f237dd80bc219226ba59 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 13:04:05 +0100 Subject: [PATCH 006/103] clean up code and fix flipping --- .../SkinEditor/SkinSelectionScaleHandler.cs | 74 ++++++++----------- .../Components/SelectionBoxScaleHandle.cs | 19 +++-- 2 files changed, 43 insertions(+), 50 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index 46b39645b2..c2f788a9e8 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -61,6 +61,9 @@ namespace osu.Game.Overlays.SkinEditor private Dictionary? originalScales; private Dictionary? originalPositions; + private bool isFlippedX; + private bool isFlippedY; + public override void Begin() { if (objectsInScale != null) @@ -75,6 +78,9 @@ namespace osu.Game.Overlays.SkinEditor originalPositions = objectsInScale.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition)); OriginalSurroundingQuad = GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray())); defaultOrigin = OriginalSurroundingQuad.Value.Centre; + + isFlippedX = false; + isFlippedY = false; } public override void Update(Vector2 scale, Vector2? origin = null) @@ -85,29 +91,21 @@ namespace osu.Game.Overlays.SkinEditor Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); var actualOrigin = origin ?? defaultOrigin.Value; - Axes adjustAxis = scale.X == 0 ? Axes.Y : scale.Y == 0 ? Axes.X : Axes.Both; if ((adjustAxis == Axes.Y && !allSelectedSupportManualSizing(Axes.Y)) || (adjustAxis == Axes.X && !allSelectedSupportManualSizing(Axes.X))) return; - // the selection quad is always upright, so use an AABB rect to make mutating the values easier. - var selectionRect = OriginalSurroundingQuad.Value.AABBFloat; - // If the selection has no area we cannot scale it - if (selectionRect.Area == 0) + if (OriginalSurroundingQuad.Value.Width == 0 || OriginalSurroundingQuad.Value.Height == 0) return; - // copy to mutate, as we will need to compare to the original later on. - var adjustedRect = selectionRect; - // for now aspect lock scale adjustments that occur at corners.. if (adjustAxis == Axes.Both) { // project scale vector along diagonal - Vector2 diag = new Vector2(1, 1).Normalized(); - scale = Vector2.Dot(scale, diag) * diag; + scale = new Vector2((scale.X + scale.Y) * 0.5f); } // ..or if any of the selection have been rotated. // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). @@ -115,66 +113,54 @@ namespace osu.Game.Overlays.SkinEditor { if (adjustAxis == Axes.Y) // if dragging from the horizontal centre, only a vertical component is available. - scale.X = scale.Y / selectionRect.Height * selectionRect.Width; + scale.X = scale.Y; else // in all other cases (arbitrarily) use the horizontal component for aspect lock. - scale.Y = scale.X / selectionRect.Width * selectionRect.Height; + scale.Y = scale.X; } - adjustedRect.Location = GeometryUtils.GetScaledPosition(scale, actualOrigin, OriginalSurroundingQuad!.Value.TopLeft); - adjustedRect.Size = OriginalSurroundingQuad!.Value.Size * scale; + bool flippedX = scale.X < 0; + bool flippedY = scale.Y < 0; + Axes toFlip = Axes.None; - if (adjustedRect.Width <= 0 || adjustedRect.Height <= 0) + if (flippedX != isFlippedX) { - Axes toFlip = Axes.None; + isFlippedX = flippedX; + toFlip |= Axes.X; + } - if (adjustedRect.Width <= 0) toFlip |= Axes.X; - if (adjustedRect.Height <= 0) toFlip |= Axes.Y; + if (flippedY != isFlippedY) + { + isFlippedY = flippedY; + toFlip |= Axes.Y; + } + if (toFlip != Axes.None) + { PerformFlipFromScaleHandles?.Invoke(toFlip); return; } - // scale adjust applied to each individual item should match that of the quad itself. - var scaledDelta = new Vector2( - adjustedRect.Width / selectionRect.Width, - adjustedRect.Height / selectionRect.Height - ); - foreach (var b in objectsInScale) { - // each drawable's relative position should be maintained in the scaled quad. - var screenPosition = originalPositions[b]; + UpdatePosition(b, GeometryUtils.GetScaledPosition(scale, actualOrigin, originalPositions[b])); - var relativePositionInOriginal = - new Vector2( - (screenPosition.X - selectionRect.TopLeft.X) / selectionRect.Width, - (screenPosition.Y - selectionRect.TopLeft.Y) / selectionRect.Height - ); - - var newPositionInAdjusted = new Vector2( - adjustedRect.TopLeft.X + adjustedRect.Width * relativePositionInOriginal.X, - adjustedRect.TopLeft.Y + adjustedRect.Height * relativePositionInOriginal.Y - ); - - UpdatePosition(b, newPositionInAdjusted); - - var currentScaledDelta = scaledDelta; + var currentScale = scale; if (Precision.AlmostEquals(MathF.Abs(b.Rotation) % 180, 90)) - currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); + currentScale = new Vector2(scale.Y, scale.X); switch (adjustAxis) { case Axes.X: - b.Width = originalWidths[b] * currentScaledDelta.X; + b.Width = originalWidths[b] * currentScale.X; break; case Axes.Y: - b.Height = originalHeights[b] * currentScaledDelta.Y; + b.Height = originalHeights[b] * currentScale.Y; break; case Axes.Both: - b.Scale = originalScales[b] * currentScaledDelta; + b.Scale = originalScales[b] * currentScale; break; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 56c5585ae7..6179be1d4f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osuTK; using osuTK.Input; @@ -24,6 +25,8 @@ namespace osu.Game.Screens.Edit.Compose.Components Size = new Vector2(10); } + private Anchor originalAnchor; + protected override bool OnDragStart(DragStartEvent e) { if (e.Button != MouseButton.Left) @@ -31,6 +34,8 @@ namespace osu.Game.Screens.Edit.Compose.Components if (scaleHandler == null) return false; + originalAnchor = Anchor; + scaleHandler.Begin(); return true; } @@ -40,10 +45,10 @@ namespace osu.Game.Screens.Edit.Compose.Components var quad = scaleHandler!.OriginalSurroundingQuad!.Value; Vector2 origin = quad.TopLeft; - if ((Anchor & Anchor.x0) > 0) + if ((originalAnchor & Anchor.x0) > 0) origin.X += quad.Width; - if ((Anchor & Anchor.y0) > 0) + if ((originalAnchor & Anchor.y0) > 0) origin.Y += quad.Height; return origin; @@ -89,6 +94,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private Vector2 convertDragEventToScaleMultiplier(DragEvent e) { Vector2 scale = e.MousePosition - e.MouseDownPosition; + Logger.Log($"Raw scale {scale}"); adjustScaleFromAnchor(ref scale); return Vector2.Divide(scale, scaleHandler!.OriginalSurroundingQuad!.Value.Size) + Vector2.One; } @@ -96,12 +102,12 @@ namespace osu.Game.Screens.Edit.Compose.Components private void adjustScaleFromAnchor(ref Vector2 scale) { // cancel out scale in axes we don't care about (based on which drag handle was used). - if ((Anchor & Anchor.x1) > 0) scale.X = 1; - if ((Anchor & Anchor.y1) > 0) scale.Y = 1; + if ((originalAnchor & Anchor.x1) > 0) scale.X = 1; + if ((originalAnchor & Anchor.y1) > 0) scale.Y = 1; // reverse the scale direction if dragging from top or left. - if ((Anchor & Anchor.x0) > 0) scale.X = -scale.X; - if ((Anchor & Anchor.y0) > 0) scale.Y = -scale.Y; + if ((originalAnchor & Anchor.x0) > 0) scale.X = -scale.X; + if ((originalAnchor & Anchor.y0) > 0) scale.Y = -scale.Y; } private void applyScale(bool shouldKeepAspectRatio) @@ -110,6 +116,7 @@ namespace osu.Game.Screens.Edit.Compose.Components ? new Vector2(MathF.Max(rawScale.X, rawScale.Y)) : rawScale; + Logger.Log($"Raw scale adjusted {newScale}, origin {getOriginPosition()}"); scaleHandler!.Update(newScale, getOriginPosition()); } } From fcaa5ec20e3fe43948bb1bd9d898d45dcf9b50cf Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 13:26:08 +0100 Subject: [PATCH 007/103] remove debug logs --- .../Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 6179be1d4f..e0b41fd8e2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; -using osu.Framework.Logging; using osuTK; using osuTK.Input; @@ -94,7 +93,6 @@ namespace osu.Game.Screens.Edit.Compose.Components private Vector2 convertDragEventToScaleMultiplier(DragEvent e) { Vector2 scale = e.MousePosition - e.MouseDownPosition; - Logger.Log($"Raw scale {scale}"); adjustScaleFromAnchor(ref scale); return Vector2.Divide(scale, scaleHandler!.OriginalSurroundingQuad!.Value.Size) + Vector2.One; } @@ -116,7 +114,6 @@ namespace osu.Game.Screens.Edit.Compose.Components ? new Vector2(MathF.Max(rawScale.X, rawScale.Y)) : rawScale; - Logger.Log($"Raw scale adjusted {newScale}, origin {getOriginPosition()}"); scaleHandler!.Update(newScale, getOriginPosition()); } } From e1f3f7d988194e2f48df3aba184f095f59d2623b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 14:49:47 +0100 Subject: [PATCH 008/103] fix possible NaN in clamped scale --- .../Edit/OsuSelectionScaleHandler.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 7b0ae947e7..3c4818a533 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -177,10 +177,14 @@ namespace osu.Game.Rulesets.Osu.Edit var br1 = Vector2.Divide(-origin, selectionQuad.BottomRight - origin); var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.BottomRight - origin); - scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); - scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); - scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); - scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); + if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - origin.X, 0)) + scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); + if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - origin.Y, 0)) + scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); + if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - origin.X, 0)) + scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); + if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0)) + scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); return scale; } From 6a4129dad880e839b033d77ac2bdb00f22dc1c0d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:11:35 +0100 Subject: [PATCH 009/103] fix aspect ratio transform --- .../Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index e0b41fd8e2..ea98ac573c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void applyScale(bool shouldKeepAspectRatio) { var newScale = shouldKeepAspectRatio - ? new Vector2(MathF.Max(rawScale.X, rawScale.Y)) + ? new Vector2((rawScale.X + rawScale.Y) * 0.5f) : rawScale; scaleHandler!.Update(newScale, getOriginPosition()); From 0fc448f4f3a31643903017a9881b81749561e0eb Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:12:48 +0100 Subject: [PATCH 010/103] fix adjusting scale from anchor --- osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs | 2 +- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index c2f788a9e8..bf75469d7a 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.SkinEditor Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); var actualOrigin = origin ?? defaultOrigin.Value; - Axes adjustAxis = scale.X == 0 ? Axes.Y : scale.Y == 0 ? Axes.X : Axes.Both; + Axes adjustAxis = scale.X == 1 ? Axes.Y : scale.Y == 1 ? Axes.X : Axes.Both; if ((adjustAxis == Axes.Y && !allSelectedSupportManualSizing(Axes.Y)) || (adjustAxis == Axes.X && !allSelectedSupportManualSizing(Axes.X))) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index ea98ac573c..60fbeb9fff 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -100,8 +100,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private void adjustScaleFromAnchor(ref Vector2 scale) { // cancel out scale in axes we don't care about (based on which drag handle was used). - if ((originalAnchor & Anchor.x1) > 0) scale.X = 1; - if ((originalAnchor & Anchor.y1) > 0) scale.Y = 1; + if ((originalAnchor & Anchor.x1) > 0) scale.X = 0; + if ((originalAnchor & Anchor.y1) > 0) scale.Y = 0; // reverse the scale direction if dragging from top or left. if ((originalAnchor & Anchor.x0) > 0) scale.X = -scale.X; From 1596776a81b91db1850bf3325b6d2992ed5eaf6c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:15:49 +0100 Subject: [PATCH 011/103] fix imports --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 1 + .../Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 3c4818a533..1e3e22e34a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Primitives; +using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 60fbeb9fff..3dde97657f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . 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.Input.Events; From 9b9485f656807570afd91bd3b25923147a2075f2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:39:38 +0100 Subject: [PATCH 012/103] fix adjust axes detection --- .../Edit/OsuSelectionScaleHandler.cs | 3 +- .../SkinEditor/SkinSelectionScaleHandler.cs | 3 +- .../Components/SelectionBoxScaleHandle.cs | 47 +++++++++++++------ .../Components/SelectionScaleHandler.cs | 8 ++-- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 1e3e22e34a..7d5240fb69 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Utils; using osu.Game.Rulesets.Edit; @@ -82,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Edit obj => obj.Path.ControlPoints.Select(p => p.Type).ToArray()); } - public override void Update(Vector2 scale, Vector2? origin = null) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) { if (objectsInScale == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index bf75469d7a..0bd146a0a1 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.SkinEditor isFlippedY = false; } - public override void Update(Vector2 scale, Vector2? origin = null) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) { if (objectsInScale == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); @@ -91,7 +91,6 @@ namespace osu.Game.Overlays.SkinEditor Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); var actualOrigin = origin ?? defaultOrigin.Value; - Axes adjustAxis = scale.X == 1 ? Axes.Y : scale.Y == 1 ? Axes.X : Axes.Both; if ((adjustAxis == Axes.Y && !allSelectedSupportManualSizing(Axes.Y)) || (adjustAxis == Axes.X && !allSelectedSupportManualSizing(Axes.X))) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 3dde97657f..d433e4e860 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -38,20 +38,6 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } - private Vector2 getOriginPosition() - { - var quad = scaleHandler!.OriginalSurroundingQuad!.Value; - Vector2 origin = quad.TopLeft; - - if ((originalAnchor & Anchor.x0) > 0) - origin.X += quad.Width; - - if ((originalAnchor & Anchor.y0) > 0) - origin.Y += quad.Height; - - return origin; - } - private Vector2 rawScale; protected override void OnDrag(DragEvent e) @@ -113,7 +99,38 @@ namespace osu.Game.Screens.Edit.Compose.Components ? new Vector2((rawScale.X + rawScale.Y) * 0.5f) : rawScale; - scaleHandler!.Update(newScale, getOriginPosition()); + scaleHandler!.Update(newScale, getOriginPosition(), getAdjustAxis()); + } + + private Vector2 getOriginPosition() + { + var quad = scaleHandler!.OriginalSurroundingQuad!.Value; + Vector2 origin = quad.TopLeft; + + if ((originalAnchor & Anchor.x0) > 0) + origin.X += quad.Width; + + if ((originalAnchor & Anchor.y0) > 0) + origin.Y += quad.Height; + + return origin; + } + + private Axes getAdjustAxis() + { + switch (originalAnchor) + { + case Anchor.TopCentre: + case Anchor.BottomCentre: + return Axes.Y; + + case Anchor.CentreLeft: + case Anchor.CentreRight: + return Axes.X; + + default: + return Axes.Both; + } } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index 59406b3184..a96f627e56 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -46,10 +46,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The origin point to scale from. /// If the default value is supplied, a sane implementation-defined default will be used. /// - public void ScaleSelection(Vector2 scale, Vector2? origin = null) + /// The axes to adjust the scale in. + public void ScaleSelection(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) { Begin(); - Update(scale, origin); + Update(scale, origin, adjustAxis); Commit(); } @@ -83,7 +84,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The origin point to scale from. /// If the default value is supplied, a sane implementation-defined default will be used. /// - public virtual void Update(Vector2 scale, Vector2? origin = null) + /// The axes to adjust the scale in. + public virtual void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) { } From ac76af5cc8f894dfb87ae4d4987172b9f5a85934 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:43:47 +0100 Subject: [PATCH 013/103] fix skin scale coordinate system --- osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index 0bd146a0a1..e87952efa0 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.SkinEditor originalHeights = objectsInScale.ToDictionary(d => d, d => d.Height); originalScales = objectsInScale.ToDictionary(d => d, d => d.Scale); originalPositions = objectsInScale.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition)); - OriginalSurroundingQuad = GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray())); + OriginalSurroundingQuad = ToLocalSpace(GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray()))); defaultOrigin = OriginalSurroundingQuad.Value.Centre; isFlippedX = false; @@ -90,7 +90,7 @@ namespace osu.Game.Overlays.SkinEditor Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); - var actualOrigin = origin ?? defaultOrigin.Value; + var actualOrigin = ToScreenSpace(origin ?? defaultOrigin.Value); if ((adjustAxis == Axes.Y && !allSelectedSupportManualSizing(Axes.Y)) || (adjustAxis == Axes.X && !allSelectedSupportManualSizing(Axes.X))) From 9459c66981a022905283b603f9bfb0d7e3cf6e77 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 15:53:08 +0100 Subject: [PATCH 014/103] fix test --- osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 680a76f9b8..4c60ecf5db 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Editing OriginalSurroundingQuad = new Quad(targetContainer!.X, targetContainer.Y, targetContainer.Width, targetContainer.Height); } - public override void Update(Vector2 scale, Vector2? origin = null) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) { if (targetContainer == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); From a155b315bf8ad9060ae214ccc8763e4deebdae6c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 16:10:17 +0100 Subject: [PATCH 015/103] Fix negative width or height skin drawables --- osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index e87952efa0..8daf0043da 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -151,11 +151,11 @@ namespace osu.Game.Overlays.SkinEditor switch (adjustAxis) { case Axes.X: - b.Width = originalWidths[b] * currentScale.X; + b.Width = MathF.Abs(originalWidths[b] * currentScale.X); break; case Axes.Y: - b.Height = originalHeights[b] * currentScale.Y; + b.Height = MathF.Abs(originalHeights[b] * currentScale.Y); break; case Axes.Both: From 5f40d3aed9ca859535c75d8f9e927d5cc7ad1581 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 16:29:26 +0100 Subject: [PATCH 016/103] rename variable --- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index d433e4e860..74629a5384 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -48,14 +48,14 @@ namespace osu.Game.Screens.Edit.Compose.Components rawScale = convertDragEventToScaleMultiplier(e); - applyScale(shouldKeepAspectRatio: e.ShiftPressed); + applyScale(shouldLockAspectRatio: e.ShiftPressed); } protected override bool OnKeyDown(KeyDownEvent e) { if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) { - applyScale(shouldKeepAspectRatio: true); + applyScale(shouldLockAspectRatio: true); return true; } @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components base.OnKeyUp(e); if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) - applyScale(shouldKeepAspectRatio: false); + applyScale(shouldLockAspectRatio: false); } protected override void OnDragEnd(DragEndEvent e) @@ -93,9 +93,9 @@ namespace osu.Game.Screens.Edit.Compose.Components if ((originalAnchor & Anchor.y0) > 0) scale.Y = -scale.Y; } - private void applyScale(bool shouldKeepAspectRatio) + private void applyScale(bool shouldLockAspectRatio) { - var newScale = shouldKeepAspectRatio + var newScale = shouldLockAspectRatio ? new Vector2((rawScale.X + rawScale.Y) * 0.5f) : rawScale; From 2f924b33686ff7d7cb3080c2cc16f891d27cbc2e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 16:33:03 +0100 Subject: [PATCH 017/103] fix skewed single axis scale --- .../SkinEditor/SkinSelectionScaleHandler.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index 8daf0043da..0c2ee6aae3 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -100,23 +100,15 @@ namespace osu.Game.Overlays.SkinEditor if (OriginalSurroundingQuad.Value.Width == 0 || OriginalSurroundingQuad.Value.Height == 0) return; - // for now aspect lock scale adjustments that occur at corners.. + // for now aspect lock scale adjustments that occur at corners. if (adjustAxis == Axes.Both) { // project scale vector along diagonal scale = new Vector2((scale.X + scale.Y) * 0.5f); } - // ..or if any of the selection have been rotated. - // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). - else if (objectsInScale.Any(b => !Precision.AlmostEquals(b.Rotation % 90, 0))) - { - if (adjustAxis == Axes.Y) - // if dragging from the horizontal centre, only a vertical component is available. - scale.X = scale.Y; - else - // in all other cases (arbitrarily) use the horizontal component for aspect lock. - scale.Y = scale.X; - } + // If any of the selection have been rotated and the adjust axis is not both, + // we would require skew logic to achieve a correct image editor-like scale. + // For now we just ignore, because it would likely not be the user's expected transform anyway. bool flippedX = scale.X < 0; bool flippedY = scale.Y < 0; From 78e87d379b760b9ebd5d567610423b607013b16d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 20 Jan 2024 16:49:10 +0100 Subject: [PATCH 018/103] fix divide by zero --- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 74629a5384..a1f6a1732a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osuTK; using osuTK.Input; @@ -79,7 +80,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { Vector2 scale = e.MousePosition - e.MouseDownPosition; adjustScaleFromAnchor(ref scale); - return Vector2.Divide(scale, scaleHandler!.OriginalSurroundingQuad!.Value.Size) + Vector2.One; + + var surroundingQuad = scaleHandler!.OriginalSurroundingQuad!.Value; + scale.X = Precision.AlmostEquals(surroundingQuad.Width, 0) ? 0 : scale.X / surroundingQuad.Width; + scale.Y = Precision.AlmostEquals(surroundingQuad.Height, 0) ? 0 : scale.Y / surroundingQuad.Height; + + return scale + Vector2.One; } private void adjustScaleFromAnchor(ref Vector2 scale) From a77db5d837bb41f57b533c7dff1b893eaa5148a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 13:36:50 +0900 Subject: [PATCH 019/103] Add failing test coverage of editor metadata not saving --- .../Editing/TestSceneMetadataSection.cs | 105 ++++++++++++++++-- 1 file changed, 94 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs index a9f8e19e30..f767d9f7a3 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs @@ -3,17 +3,22 @@ #nullable disable +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; +using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { - public partial class TestSceneMetadataSection : OsuTestScene + public partial class TestSceneMetadataSection : OsuManualInputManagerTestScene { [Cached] private EditorBeatmap editorBeatmap = new EditorBeatmap(new Beatmap @@ -26,6 +31,81 @@ namespace osu.Game.Tests.Visual.Editing private TestMetadataSection metadataSection; + [Test] + public void TestUpdateViaTextBoxOnFocusLoss() + { + AddStep("set metadata", () => + { + editorBeatmap.Metadata.Artist = "Example Artist"; + editorBeatmap.Metadata.ArtistUnicode = string.Empty; + }); + + createSection(); + + TextBox textbox = null!; + + AddStep("focus first textbox", () => + { + textbox = metadataSection.ChildrenOfType().First(); + InputManager.MoveMouseTo(textbox); + InputManager.Click(MouseButton.Left); + }); + + AddStep("simulate changing textbox", () => + { + // Can't simulate text input but this should work. + InputManager.Keys(PlatformAction.SelectAll); + InputManager.Keys(PlatformAction.Copy); + InputManager.Keys(PlatformAction.Paste); + InputManager.Keys(PlatformAction.Paste); + }); + + assertArtistMetadata("Example Artist"); + + // It's important values are committed immediately on focus loss so the editor exit sequence detects them. + AddAssert("value immediately changed on focus loss", () => + { + InputManager.TriggerFocusContention(metadataSection); + return editorBeatmap.Metadata.Artist; + }, () => Is.EqualTo("Example ArtistExample Artist")); + } + + [Test] + public void TestUpdateViaTextBoxOnCommit() + { + AddStep("set metadata", () => + { + editorBeatmap.Metadata.Artist = "Example Artist"; + editorBeatmap.Metadata.ArtistUnicode = string.Empty; + }); + + createSection(); + + TextBox textbox = null!; + + AddStep("focus first textbox", () => + { + textbox = metadataSection.ChildrenOfType().First(); + InputManager.MoveMouseTo(textbox); + InputManager.Click(MouseButton.Left); + }); + + AddStep("simulate changing textbox", () => + { + // Can't simulate text input but this should work. + InputManager.Keys(PlatformAction.SelectAll); + InputManager.Keys(PlatformAction.Copy); + InputManager.Keys(PlatformAction.Paste); + InputManager.Keys(PlatformAction.Paste); + }); + + assertArtistMetadata("Example Artist"); + + AddStep("commit", () => InputManager.Key(Key.Enter)); + + assertArtistMetadata("Example ArtistExample Artist"); + } + [Test] public void TestMinimalMetadata() { @@ -40,7 +120,7 @@ namespace osu.Game.Tests.Visual.Editing createSection(); - assertArtist("Example Artist"); + assertArtistTextBox("Example Artist"); assertRomanisedArtist("Example Artist", false); assertTitle("Example Title"); @@ -61,7 +141,7 @@ namespace osu.Game.Tests.Visual.Editing createSection(); - assertArtist("*なみりん"); + assertArtistTextBox("*なみりん"); assertRomanisedArtist(string.Empty, true); assertTitle("コイシテイク・プラネット"); @@ -82,7 +162,7 @@ namespace osu.Game.Tests.Visual.Editing createSection(); - assertArtist("*なみりん"); + assertArtistTextBox("*なみりん"); assertRomanisedArtist("*namirin", true); assertTitle("コイシテイク・プラネット"); @@ -104,11 +184,11 @@ namespace osu.Game.Tests.Visual.Editing createSection(); AddStep("set romanised artist name", () => metadataSection.ArtistTextBox.Current.Value = "*namirin"); - assertArtist("*namirin"); + assertArtistTextBox("*namirin"); assertRomanisedArtist("*namirin", false); AddStep("set native artist name", () => metadataSection.ArtistTextBox.Current.Value = "*なみりん"); - assertArtist("*なみりん"); + assertArtistTextBox("*なみりん"); assertRomanisedArtist("*namirin", true); AddStep("set romanised title", () => metadataSection.TitleTextBox.Current.Value = "Hitokoto no kyori"); @@ -123,21 +203,24 @@ namespace osu.Game.Tests.Visual.Editing private void createSection() => AddStep("create metadata section", () => Child = metadataSection = new TestMetadataSection()); - private void assertArtist(string expected) - => AddAssert($"artist is {expected}", () => metadataSection.ArtistTextBox.Current.Value == expected); + private void assertArtistMetadata(string expected) + => AddAssert($"artist metadata is {expected}", () => editorBeatmap.Metadata.Artist, () => Is.EqualTo(expected)); + + private void assertArtistTextBox(string expected) + => AddAssert($"artist textbox is {expected}", () => metadataSection.ArtistTextBox.Current.Value, () => Is.EqualTo(expected)); private void assertRomanisedArtist(string expected, bool editable) { - AddAssert($"romanised artist is {expected}", () => metadataSection.RomanisedArtistTextBox.Current.Value == expected); + AddAssert($"romanised artist is {expected}", () => metadataSection.RomanisedArtistTextBox.Current.Value, () => Is.EqualTo(expected)); AddAssert($"romanised artist is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedArtistTextBox.ReadOnly == !editable); } private void assertTitle(string expected) - => AddAssert($"title is {expected}", () => metadataSection.TitleTextBox.Current.Value == expected); + => AddAssert($"title is {expected}", () => metadataSection.TitleTextBox.Current.Value, () => Is.EqualTo(expected)); private void assertRomanisedTitle(string expected, bool editable) { - AddAssert($"romanised title is {expected}", () => metadataSection.RomanisedTitleTextBox.Current.Value == expected); + AddAssert($"romanised title is {expected}", () => metadataSection.RomanisedTitleTextBox.Current.Value, () => Is.EqualTo(expected)); AddAssert($"romanised title is {(editable ? "" : "not ")}editable", () => metadataSection.RomanisedTitleTextBox.ReadOnly == !editable); } From e54502eef1bd0d99e3a3b5c29476b0addc7a5c13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 13:59:28 +0900 Subject: [PATCH 020/103] Add full editor save test coverage --- .../TestSceneBeatmapEditorNavigation.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 9930349b1b..370c40222e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -17,6 +18,7 @@ using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.GameplayTest; +using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -27,6 +29,59 @@ namespace osu.Game.Tests.Visual.Navigation { public partial class TestSceneBeatmapEditorNavigation : OsuGameTestScene { + [Test] + public void TestChangeMetadataExitWhileTextboxFocusedPromptsSave() + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); + + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + + AddStep("change to song setup", () => InputManager.Key(Key.F4)); + + TextBox textbox = null!; + + AddUntilStep("wait for metadata section", () => + { + var t = Game.ChildrenOfType().SingleOrDefault().ChildrenOfType().FirstOrDefault(); + + if (t == null) + return false; + + textbox = t; + return true; + }); + + AddStep("focus textbox", () => + { + InputManager.MoveMouseTo(textbox); + InputManager.Click(MouseButton.Left); + }); + + AddStep("simulate changing textbox", () => + { + // Can't simulate text input but this should work. + InputManager.Keys(PlatformAction.SelectAll); + InputManager.Keys(PlatformAction.Copy); + InputManager.Keys(PlatformAction.Paste); + InputManager.Keys(PlatformAction.Paste); + }); + + AddStep("exit", () => Game.ChildrenOfType().Single().Exit()); + + AddAssert("save dialog displayed", () => Game.ChildrenOfType().Single().CurrentDialog is PromptForSaveDialog); + } + [Test] public void TestEditorGameplayTestAlwaysUsesOriginalRuleset() { From 45f2980dc099ba8c421395dd4258cdf3df4a2e55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jan 2024 13:23:02 +0900 Subject: [PATCH 021/103] Fix potential editor data loss if exiting while a textbox is focused --- osu.Game/Screens/Edit/Editor.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/MetadataSection.cs | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c1f6c02301..bc376f6165 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -719,6 +719,12 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(ScreenExitEvent e) { + // Before exiting, trigger a focus loss. + // + // This is important to ensure that if the user is still editing a textbox, it will commit + // (and potentially block the exit procedure for save). + GetContainingInputManager().TriggerFocusContention(this); + if (!ExitConfirmed) { // dialog overlay may not be available in visual tests. diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 752f590308..b51c03b796 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -53,9 +53,6 @@ namespace osu.Game.Screens.Edit.Setup sourceTextBox = createTextBox(BeatmapsetsStrings.ShowInfoSource, metadata.Source), tagsTextBox = createTextBox(BeatmapsetsStrings.ShowInfoTags, metadata.Tags) }; - - foreach (var item in Children.OfType()) - item.OnCommit += onCommit; } private TTextBox createTextBox(LocalisableString label, string initialValue) @@ -77,6 +74,10 @@ namespace osu.Game.Screens.Edit.Setup ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox)); TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox)); + + foreach (var item in Children.OfType()) + item.OnCommit += onCommit; + updateReadOnlyState(); } @@ -86,7 +87,6 @@ namespace osu.Game.Screens.Edit.Setup target.Current.Value = value; updateReadOnlyState(); - Scheduler.AddOnce(updateMetadata); } private void updateReadOnlyState() @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Edit.Setup // for now, update on commit rather than making BeatmapMetadata bindables. // after switching database engines we can reconsider if switching to bindables is a good direction. - Scheduler.AddOnce(updateMetadata); + updateMetadata(); } private void updateMetadata() From fe0433e6ecd1a69132fee41626562590c4b688ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Feb 2024 13:24:59 +0100 Subject: [PATCH 022/103] Remove redundant default value assignments --- osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs index f767d9f7a3..5930c077a4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Editing createSection(); - TextBox textbox = null!; + TextBox textbox; AddStep("focus first textbox", () => { @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Editing createSection(); - TextBox textbox = null!; + TextBox textbox; AddStep("focus first textbox", () => { From dbd4397bef2419ea4d9b1b8fdc44068358d59159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Feb 2024 13:32:16 +0100 Subject: [PATCH 023/103] Attempt to salvage test by using until step --- .../Visual/Navigation/TestSceneBeatmapEditorNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 370c40222e..e3a8e575f8 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("exit", () => Game.ChildrenOfType().Single().Exit()); - AddAssert("save dialog displayed", () => Game.ChildrenOfType().Single().CurrentDialog is PromptForSaveDialog); + AddUntilStep("save dialog displayed", () => Game.ChildrenOfType().SingleOrDefault()?.CurrentDialog is PromptForSaveDialog); } [Test] From 0113fce02f5888dd775402ed4c15550826a5e999 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 11:27:12 +0100 Subject: [PATCH 024/103] Add osu!taiko `Constant Speed` mod --- .../Mods/TaikoModConstantSpeed.cs | 31 +++++++++++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + 2 files changed, 32 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs new file mode 100644 index 0000000000..28de360eee --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Beatmaps; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModConstantSpeed : Mod, IApplicableToBeatmap + { + public override string Name => "Constant Speed"; + public override string Acronym => "CS"; + public override double ScoreMultiplier => 0.8; + public override LocalisableString Description => "No more tricky speed changes!"; + public override IconUsage? Icon => FontAwesome.Solid.Equals; + public override ModType Type => ModType.Conversion; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + var taikoBeatmap = (TaikoBeatmap)beatmap; + + foreach (var effectControlPoint in taikoBeatmap.ControlPointInfo.EffectPoints) + { + effectControlPoint.ScrollSpeed = 1; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 24b0ec5d57..b701d3c25a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -150,6 +150,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModClassic(), new TaikoModSwap(), new TaikoModSingleTap(), + new TaikoModConstantSpeed(), }; case ModType.Automation: From 1cbc2f07ab039c54d3788063e5028ab34577937a Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 14:01:12 +0100 Subject: [PATCH 025/103] use more correct implementation --- .../Mods/TaikoModConstantSpeed.cs | 20 +++++++++---------- .../UI/DrawableTaikoRuleset.cs | 7 ++++++- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs index 28de360eee..4ecb94467e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs @@ -1,15 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Localisation; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModConstantSpeed : Mod, IApplicableToBeatmap + public class TaikoModConstantSpeed : Mod, IApplicableToDrawableRuleset { public override string Name => "Constant Speed"; public override string Acronym => "CS"; @@ -18,14 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Mods public override IconUsage? Icon => FontAwesome.Solid.Equals; public override ModType Type => ModType.Conversion; - public void ApplyToBeatmap(IBeatmap beatmap) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - var taikoBeatmap = (TaikoBeatmap)beatmap; - - foreach (var effectControlPoint in taikoBeatmap.ControlPointInfo.EffectPoints) - { - effectControlPoint.ScrollSpeed = 1; - } + var taikoRuleset = (DrawableTaikoRuleset)drawableRuleset; + taikoRuleset.VisualisationMethod = ScrollVisualisationMethod.Constant; } } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 77b2b06c0e..a476634fb8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -69,7 +69,12 @@ namespace osu.Game.Rulesets.Taiko.UI TimeRange.Value = ComputeTimeRange(); } - protected virtual double ComputeTimeRange() => PlayfieldAdjustmentContainer.ComputeTimeRange(); + protected virtual double ComputeTimeRange() + { + // Adjust when we're using constant algorithm to not be sluggish. + double multiplier = VisualisationMethod == ScrollVisualisationMethod.Overlapping ? 1 : 4; + return PlayfieldAdjustmentContainer.ComputeTimeRange() / multiplier; + } protected override void UpdateAfterChildren() { From 14b0c41937f39906ddaf86d4919baadf2d5c2174 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 14:22:56 +0100 Subject: [PATCH 026/103] remove unnecessary `ComputeTimeRange` override --- osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs b/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs index 3c7a97c864..217bb8139c 100644 --- a/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs +++ b/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs @@ -26,12 +26,5 @@ namespace osu.Game.Rulesets.Taiko.Edit ShowSpeedChanges.BindValueChanged(showChanges => VisualisationMethod = showChanges.NewValue ? ScrollVisualisationMethod.Overlapping : ScrollVisualisationMethod.Constant, true); } - - protected override double ComputeTimeRange() - { - // Adjust when we're using constant algorithm to not be sluggish. - double multiplier = ShowSpeedChanges.Value ? 1 : 4; - return base.ComputeTimeRange() / multiplier; - } } } From 7762d2469b59c4a349d5a05119f0c7a007cd0c5e Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 14:24:26 +0100 Subject: [PATCH 027/103] exclude EZ/HR for now --- osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs index 4ecb94467e..117dc0ebd2 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Configuration; @@ -19,6 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override LocalisableString Description => "No more tricky speed changes!"; public override IconUsage? Icon => FontAwesome.Solid.Equals; public override ModType Type => ModType.Conversion; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModEasy), typeof(TaikoModHardRock) }).ToArray(); public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From 65c0b73dd5c0b8a74deeab5aa737fb5d90dce82f Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 17:55:49 +0100 Subject: [PATCH 028/103] mark `TaikoModConstantSpeed` as incompatible with EZ/HR --- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 3 +++ osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index 009f2854f8..59d0563f1f 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModEasy : ModEasy { public override LocalisableString Description => @"Beats move slower, and less accuracy required!"; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModConstantSpeed) }).ToArray(); /// /// Multiplier factor added to the scrolling speed. diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index ba41175461..fa948507c8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -8,6 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModHardRock : ModHardRock { + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModConstantSpeed) }).ToArray(); public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; /// From 46a1f5267f40f03b566c4feb713de3a08a135ee8 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 21:15:18 +0100 Subject: [PATCH 029/103] account for beatmap base scroll speed in constant visualisation method --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index a476634fb8..c88bbec9bc 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected virtual double ComputeTimeRange() { // Adjust when we're using constant algorithm to not be sluggish. - double multiplier = VisualisationMethod == ScrollVisualisationMethod.Overlapping ? 1 : 4; + double multiplier = VisualisationMethod == ScrollVisualisationMethod.Overlapping ? 1 : 4 * Beatmap.Difficulty.SliderMultiplier; return PlayfieldAdjustmentContainer.ComputeTimeRange() / multiplier; } From 4ea9519db839762a088b2ef15fc8f770b0c87905 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 23 Feb 2024 21:15:52 +0100 Subject: [PATCH 030/103] revert changes to `IncompatibleMods` --- osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs | 3 --- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 3 --- osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs | 3 --- 3 files changed, 9 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs index 117dc0ebd2..4ecb94467e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Linq; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Configuration; @@ -21,7 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Mods public override LocalisableString Description => "No more tricky speed changes!"; public override IconUsage? Icon => FontAwesome.Solid.Equals; public override ModType Type => ModType.Conversion; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModEasy), typeof(TaikoModHardRock) }).ToArray(); public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index 59d0563f1f..009f2854f8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Linq; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModEasy : ModEasy { public override LocalisableString Description => @"Beats move slower, and less accuracy required!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModConstantSpeed) }).ToArray(); /// /// Multiplier factor added to the scrolling speed. diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index fa948507c8..ba41175461 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -10,7 +8,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModHardRock : ModHardRock { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModConstantSpeed) }).ToArray(); public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; /// From 48c83195677736a41d1bb3b8e8d563247481de72 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 8 Mar 2024 16:01:57 +0100 Subject: [PATCH 031/103] change multiplier to 0.9x --- osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs index 4ecb94467e..81973e65cc 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModConstantSpeed.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override string Name => "Constant Speed"; public override string Acronym => "CS"; - public override double ScoreMultiplier => 0.8; + public override double ScoreMultiplier => 0.9; public override LocalisableString Description => "No more tricky speed changes!"; public override IconUsage? Icon => FontAwesome.Solid.Equals; public override ModType Type => ModType.Conversion; From a8792b35850c53a5946e6e20176282dc33292075 Mon Sep 17 00:00:00 2001 From: Hivie Date: Fri, 8 Mar 2024 16:02:17 +0100 Subject: [PATCH 032/103] better assertion --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index c88bbec9bc..ee7acec65c 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected virtual double ComputeTimeRange() { // Adjust when we're using constant algorithm to not be sluggish. - double multiplier = VisualisationMethod == ScrollVisualisationMethod.Overlapping ? 1 : 4 * Beatmap.Difficulty.SliderMultiplier; + double multiplier = VisualisationMethod == ScrollVisualisationMethod.Constant ? 4 * Beatmap.Difficulty.SliderMultiplier : 1; return PlayfieldAdjustmentContainer.ComputeTimeRange() / multiplier; } From f534c4aadab0a274674bd5b077e4acea797f177e Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Thu, 2 May 2024 18:42:35 +0200 Subject: [PATCH 033/103] Initial implementation --- .../Input/Bindings/GlobalActionContainer.cs | 8 ++ .../GlobalActionKeyBindingStrings.cs | 10 ++ osu.Game/Screens/Select/SongSelect.cs | 97 ++++++++++++++++++- 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 296232d9ea..ff7d9f0a6d 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -134,6 +134,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions), new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods), + new KeyBinding(InputKey.PageUp, GlobalAction.IncreaseSpeed), // Not working with minus and other keys.... + new KeyBinding(InputKey.PageDown, GlobalAction.DecreaseSpeed), }; public IEnumerable AudioControlKeyBindings => new[] @@ -364,5 +366,11 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))] EditorToggleRotateControl, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseSpeed))] + IncreaseSpeed, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseSpeed))] + DecreaseSpeed, } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 8356c480dd..40fe4064ed 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -349,6 +349,16 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control"); + /// + /// "Increase Speed" + /// + public static LocalisableString IncreaseSpeed => new TranslatableString(getKey(@"increase_speed"), @"Increase Speed"); + + /// + /// "Decrease Speed" + /// + public static LocalisableString DecreaseSpeed => new TranslatableString(getKey(@"decrease_speed"), @"Decrease Speed"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 34ee0ae4e8..f2036f017d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -755,7 +755,95 @@ namespace osu.Game.Screens.Select return false; } - + public void IncreaseSpeed() + { + // find way of grabbing all types of ModSpeedAdjust + var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod.Acronym == "DT" || pair.Mod.Acronym == "NC" || pair.Mod.Acronym == "HT" || pair.Mod.Acronym == "DC"); + var stateDoubleTime = ModSelect.AllAvailableMods.First(pair => pair.Mod.Acronym == "DT"); + bool oneActive = false; + double newRate = 1.05d; + foreach (var state in rateAdjustStates) + { + ModRateAdjust mod = (ModRateAdjust)state.Mod; + if (state.Active.Value) + { + oneActive = true; + newRate = mod.SpeedChange.Value + 0.05d; + if (mod.Acronym == "DT" || mod.Acronym == "NC") + { + mod.SpeedChange.Value = newRate; + return; + } + else + { + if (newRate == 1.0d) + { + state.Active.Value = false; + } + if (newRate > 1d) + { + state.Active.Value = false; + stateDoubleTime.Active.Value = true; + ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; + return; + } + if (newRate < 1d) + { + mod.SpeedChange.Value = newRate; + } + } + } + } + if (!oneActive) + { + stateDoubleTime.Active.Value = true; + ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; + } + } + public void DecreaseSpeed() + { + var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod.Acronym == "DT" || pair.Mod.Acronym == "NC" || pair.Mod.Acronym == "HT" || pair.Mod.Acronym == "DC"); + var stateHalfTime = ModSelect.AllAvailableMods.First(pair => pair.Mod.Acronym == "HT"); + bool oneActive = false; + double newRate = 0.95d; + foreach (var state in rateAdjustStates) + { + ModRateAdjust mod = (ModRateAdjust)state.Mod; + if (state.Active.Value) + { + oneActive = true; + newRate = mod.SpeedChange.Value - 0.05d; + if (mod.Acronym == "HT" || mod.Acronym == "DC") + { + mod.SpeedChange.Value = newRate; + return; + } + else + { + if (newRate == 1.0d) + { + state.Active.Value = false; + } + if (newRate < 1d) + { + state.Active.Value = false; + stateHalfTime.Active.Value = true; + ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; + return; + } + if (newRate > 1d) + { + mod.SpeedChange.Value = newRate; + } + } + } + } + if (!oneActive) + { + stateHalfTime.Active.Value = true; + ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; + } + } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -955,12 +1043,17 @@ namespace osu.Game.Screens.Select return false; if (!this.IsCurrentScreen()) return false; - switch (e.Action) { case GlobalAction.Select: FinaliseSelection(); return true; + case GlobalAction.IncreaseSpeed: + IncreaseSpeed(); + return true; + case GlobalAction.DecreaseSpeed: + DecreaseSpeed(); + return true; } return false; From 5c21a0330addcf81db97e7ec157510d894cbd6a0 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Thu, 2 May 2024 19:05:07 +0200 Subject: [PATCH 034/103] F1 also does not work with minus in song select, same behaviour --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index ff7d9f0a6d..204ecb9061 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -134,7 +134,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions), new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods), - new KeyBinding(InputKey.PageUp, GlobalAction.IncreaseSpeed), // Not working with minus and other keys.... + new KeyBinding(InputKey.PageUp, GlobalAction.IncreaseSpeed), new KeyBinding(InputKey.PageDown, GlobalAction.DecreaseSpeed), }; From 7527ddbc6865c35e26a7c4deef850e0d4e93d786 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Thu, 2 May 2024 19:05:43 +0200 Subject: [PATCH 035/103] Comment, make code more readable, functions are now private --- osu.Game/Screens/Select/SongSelect.cs | 133 ++++++++++++-------------- 1 file changed, 60 insertions(+), 73 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f2036f017d..5de4a7a9a3 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -755,93 +755,80 @@ namespace osu.Game.Screens.Select return false; } - public void IncreaseSpeed() + private void increaseSpeed() { - // find way of grabbing all types of ModSpeedAdjust - var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod.Acronym == "DT" || pair.Mod.Acronym == "NC" || pair.Mod.Acronym == "HT" || pair.Mod.Acronym == "DC"); - var stateDoubleTime = ModSelect.AllAvailableMods.First(pair => pair.Mod.Acronym == "DT"); - bool oneActive = false; - double newRate = 1.05d; - foreach (var state in rateAdjustStates) - { - ModRateAdjust mod = (ModRateAdjust)state.Mod; - if (state.Active.Value) - { - oneActive = true; - newRate = mod.SpeedChange.Value + 0.05d; - if (mod.Acronym == "DT" || mod.Acronym == "NC") - { - mod.SpeedChange.Value = newRate; - return; - } - else - { - if (newRate == 1.0d) - { - state.Active.Value = false; - } - if (newRate > 1d) - { - state.Active.Value = false; - stateDoubleTime.Active.Value = true; - ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; - return; - } - if (newRate < 1d) - { - mod.SpeedChange.Value = newRate; - } - } - } - } - if (!oneActive) + var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); + var stateDoubleTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModDoubleTime); + bool rateModActive = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust && pair.Active.Value).Count() > 0; + double stepSize = 0.05d; + double newRate = 1d + stepSize; + // If no mod rateAdjust mod is currently active activate DoubleTime with speed newRate + if (!rateModActive) { stateDoubleTime.Active.Value = true; ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; + return; } - } - public void DecreaseSpeed() - { - var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod.Acronym == "DT" || pair.Mod.Acronym == "NC" || pair.Mod.Acronym == "HT" || pair.Mod.Acronym == "DC"); - var stateHalfTime = ModSelect.AllAvailableMods.First(pair => pair.Mod.Acronym == "HT"); - bool oneActive = false; - double newRate = 0.95d; + // Find current active rateAdjust mod and modify speed, enable DoubleTime if necessary foreach (var state in rateAdjustStates) { ModRateAdjust mod = (ModRateAdjust)state.Mod; - if (state.Active.Value) + if (!state.Active.Value) continue; + newRate = mod.SpeedChange.Value + stepSize; + if (mod.Acronym == "DT" || mod.Acronym == "NC") + mod.SpeedChange.Value = newRate; + else { - oneActive = true; - newRate = mod.SpeedChange.Value - 0.05d; - if (mod.Acronym == "HT" || mod.Acronym == "DC") + if (newRate == 1.0d) + state.Active.Value = false; + if (newRate > 1d) { + state.Active.Value = false; + stateDoubleTime.Active.Value = true; + ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; + break; + } + if (newRate < 1d) mod.SpeedChange.Value = newRate; - return; - } - else - { - if (newRate == 1.0d) - { - state.Active.Value = false; - } - if (newRate < 1d) - { - state.Active.Value = false; - stateHalfTime.Active.Value = true; - ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; - return; - } - if (newRate > 1d) - { - mod.SpeedChange.Value = newRate; - } - } } } - if (!oneActive) + } + private void decreaseSpeed() + { + var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); + var stateHalfTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModHalfTime); + bool rateModActive = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust && pair.Active.Value).Count() > 0; + double stepSize = 0.05d; + double newRate = 1d - stepSize; + // If no mod rateAdjust mod is currently active activate HalfTime with speed newRate + if (!rateModActive) { stateHalfTime.Active.Value = true; ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; + return; + } + // Find current active rateAdjust mod and modify speed, enable HalfTime if necessary + foreach (var state in rateAdjustStates) + { + ModRateAdjust mod = (ModRateAdjust)state.Mod; + if (!state.Active.Value) continue; + newRate = mod.SpeedChange.Value - stepSize; + if (mod.Acronym == "HT" || mod.Acronym == "DC") + mod.SpeedChange.Value = newRate; + else + { + if (newRate == 1.0d) + state.Active.Value = false; + if (newRate < 1d) + { + state.Active.Value = false; + stateHalfTime.Active.Value = true; + ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; + break; + } + if (newRate > 1d) + mod.SpeedChange.Value = newRate; + } } } protected override void Dispose(bool isDisposing) @@ -1049,10 +1036,10 @@ namespace osu.Game.Screens.Select FinaliseSelection(); return true; case GlobalAction.IncreaseSpeed: - IncreaseSpeed(); + increaseSpeed(); return true; case GlobalAction.DecreaseSpeed: - DecreaseSpeed(); + decreaseSpeed(); return true; } From 588badf29216782699ae359700d6449e82638187 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Thu, 2 May 2024 19:22:39 +0200 Subject: [PATCH 036/103] Fix Formatting --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index f6bc002e32..5dacb6db4d 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -416,7 +416,7 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseSpeed))] DecreaseSpeed, - + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))] IncreaseOffset, From 4b5ea6bd0bc46cf37612efa0de3c81f3d4fe52bc Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Thu, 2 May 2024 19:41:00 +0200 Subject: [PATCH 037/103] Fix Code Inspection --- osu.Game/Screens/Select/SongSelect.cs | 36 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 73ce029d3f..de0f24aa90 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -808,13 +808,15 @@ namespace osu.Game.Screens.Select return false; } + private void increaseSpeed() { var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); var stateDoubleTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModDoubleTime); - bool rateModActive = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust && pair.Active.Value).Count() > 0; - double stepSize = 0.05d; - double newRate = 1d + stepSize; + bool rateModActive = ModSelect.AllAvailableMods.Count(pair => pair.Mod is ModRateAdjust && pair.Active.Value) > 0; + const double stepsize = 0.05d; + double newRate = 1d + stepsize; + // If no mod rateAdjust mod is currently active activate DoubleTime with speed newRate if (!rateModActive) { @@ -822,18 +824,23 @@ namespace osu.Game.Screens.Select ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; return; } + // Find current active rateAdjust mod and modify speed, enable DoubleTime if necessary foreach (var state in rateAdjustStates) { ModRateAdjust mod = (ModRateAdjust)state.Mod; + if (!state.Active.Value) continue; - newRate = mod.SpeedChange.Value + stepSize; + + newRate = mod.SpeedChange.Value + stepsize; + if (mod.Acronym == "DT" || mod.Acronym == "NC") mod.SpeedChange.Value = newRate; else { if (newRate == 1.0d) state.Active.Value = false; + if (newRate > 1d) { state.Active.Value = false; @@ -841,18 +848,21 @@ namespace osu.Game.Screens.Select ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; break; } + if (newRate < 1d) mod.SpeedChange.Value = newRate; } } } + private void decreaseSpeed() { var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); var stateHalfTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModHalfTime); - bool rateModActive = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust && pair.Active.Value).Count() > 0; - double stepSize = 0.05d; - double newRate = 1d - stepSize; + bool rateModActive = ModSelect.AllAvailableMods.Count(pair => pair.Mod is ModRateAdjust && pair.Active.Value) > 0; + const double stepsize = 0.05d; + double newRate = 1d - stepsize; + // If no mod rateAdjust mod is currently active activate HalfTime with speed newRate if (!rateModActive) { @@ -860,18 +870,23 @@ namespace osu.Game.Screens.Select ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; return; } + // Find current active rateAdjust mod and modify speed, enable HalfTime if necessary foreach (var state in rateAdjustStates) { ModRateAdjust mod = (ModRateAdjust)state.Mod; + if (!state.Active.Value) continue; - newRate = mod.SpeedChange.Value - stepSize; + + newRate = mod.SpeedChange.Value - stepsize; + if (mod.Acronym == "HT" || mod.Acronym == "DC") mod.SpeedChange.Value = newRate; else { if (newRate == 1.0d) state.Active.Value = false; + if (newRate < 1d) { state.Active.Value = false; @@ -879,11 +894,13 @@ namespace osu.Game.Screens.Select ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; break; } + if (newRate > 1d) mod.SpeedChange.Value = newRate; } } } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -1086,14 +1103,17 @@ namespace osu.Game.Screens.Select return false; if (!this.IsCurrentScreen()) return false; + switch (e.Action) { case GlobalAction.Select: FinaliseSelection(); return true; + case GlobalAction.IncreaseSpeed: increaseSpeed(); return true; + case GlobalAction.DecreaseSpeed: decreaseSpeed(); return true; From c4ac6d20a09b5704dd484b633142f517b527e2c2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 15 May 2024 23:40:51 +0200 Subject: [PATCH 038/103] fix code quality --- .../Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index a1f6a1732a..c188d23a58 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -12,9 +12,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class SelectionBoxScaleHandle : SelectionBoxDragHandle { - [Resolved] - private SelectionBox selectionBox { get; set; } = null!; - [Resolved] private SelectionScaleHandler? scaleHandler { get; set; } From 61a415fed2ae74c50223015d957bcc55b8a11c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 May 2024 10:32:39 +0200 Subject: [PATCH 039/103] Add client/server models & operations for "daily challenge" feature --- osu.Game/Online/Metadata/DailyChallengeInfo.cs | 16 ++++++++++++++++ osu.Game/Online/Metadata/IMetadataClient.cs | 6 ++++++ osu.Game/Online/Metadata/IMetadataServer.cs | 7 ++++++- osu.Game/Online/Metadata/MetadataClient.cs | 9 +++++++++ osu.Game/Online/Metadata/OnlineMetadataClient.cs | 11 +++++++++++ osu.Game/Online/Rooms/RoomCategory.cs | 3 +++ .../Tests/Visual/Metadata/TestMetadataClient.cs | 9 +++++++++ 7 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Metadata/DailyChallengeInfo.cs diff --git a/osu.Game/Online/Metadata/DailyChallengeInfo.cs b/osu.Game/Online/Metadata/DailyChallengeInfo.cs new file mode 100644 index 0000000000..7c49556653 --- /dev/null +++ b/osu.Game/Online/Metadata/DailyChallengeInfo.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using MessagePack; + +namespace osu.Game.Online.Metadata +{ + [MessagePackObject] + [Serializable] + public struct DailyChallengeInfo + { + [Key(0)] + public long RoomID { get; set; } + } +} diff --git a/osu.Game/Online/Metadata/IMetadataClient.cs b/osu.Game/Online/Metadata/IMetadataClient.cs index 7102554ae9..ee7a726bfc 100644 --- a/osu.Game/Online/Metadata/IMetadataClient.cs +++ b/osu.Game/Online/Metadata/IMetadataClient.cs @@ -20,5 +20,11 @@ namespace osu.Game.Online.Metadata /// Delivers an update of the of the user with the supplied . /// Task UserPresenceUpdated(int userId, UserPresence? status); + + /// + /// Delivers an update of the current "daily challenge" status. + /// Null value means there is no "daily challenge" currently active. + /// + Task DailyChallengeUpdated(DailyChallengeInfo? info); } } diff --git a/osu.Game/Online/Metadata/IMetadataServer.cs b/osu.Game/Online/Metadata/IMetadataServer.cs index 9780045333..8bf3f8f56b 100644 --- a/osu.Game/Online/Metadata/IMetadataServer.cs +++ b/osu.Game/Online/Metadata/IMetadataServer.cs @@ -7,7 +7,12 @@ using osu.Game.Users; namespace osu.Game.Online.Metadata { /// - /// Metadata server is responsible for keeping the osu! client up-to-date with any changes. + /// Metadata server is responsible for keeping the osu! client up-to-date with various real-time happenings, such as: + /// + /// beatmap updates via BSS, + /// online user activity/status updates, + /// other real-time happenings, such as current "daily challenge" status. + /// /// public interface IMetadataServer { diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs index 8e99a9b2cb..b619970494 100644 --- a/osu.Game/Online/Metadata/MetadataClient.cs +++ b/osu.Game/Online/Metadata/MetadataClient.cs @@ -59,6 +59,15 @@ namespace osu.Game.Online.Metadata #endregion + #region Daily Challenge + + public abstract IBindable DailyChallengeInfo { get; } + + /// + public abstract Task DailyChallengeUpdated(DailyChallengeInfo? info); + + #endregion + #region Disconnection handling public event Action? Disconnecting; diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 3805d12688..b94f26a71d 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -26,6 +26,9 @@ namespace osu.Game.Online.Metadata public override IBindableDictionary UserStates => userStates; private readonly BindableDictionary userStates = new BindableDictionary(); + public override IBindable DailyChallengeInfo => dailyChallengeInfo; + private readonly Bindable dailyChallengeInfo = new Bindable(); + private readonly string endpoint; private IHubClientConnector? connector; @@ -58,6 +61,7 @@ namespace osu.Game.Online.Metadata // https://github.com/dotnet/aspnetcore/issues/15198 connection.On(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated); connection.On(nameof(IMetadataClient.UserPresenceUpdated), ((IMetadataClient)this).UserPresenceUpdated); + connection.On(nameof(IMetadataClient.DailyChallengeUpdated), ((IMetadataClient)this).DailyChallengeUpdated); connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IMetadataClient)this).DisconnectRequested); }; @@ -101,6 +105,7 @@ namespace osu.Game.Online.Metadata { isWatchingUserPresence.Value = false; userStates.Clear(); + dailyChallengeInfo.Value = null; }); return; } @@ -229,6 +234,12 @@ namespace osu.Game.Online.Metadata } } + public override Task DailyChallengeUpdated(DailyChallengeInfo? info) + { + Schedule(() => dailyChallengeInfo.Value = info); + return Task.CompletedTask; + } + public override async Task DisconnectRequested() { await base.DisconnectRequested().ConfigureAwait(false); diff --git a/osu.Game/Online/Rooms/RoomCategory.cs b/osu.Game/Online/Rooms/RoomCategory.cs index 17afb0dc7f..4534e7de59 100644 --- a/osu.Game/Online/Rooms/RoomCategory.cs +++ b/osu.Game/Online/Rooms/RoomCategory.cs @@ -13,5 +13,8 @@ namespace osu.Game.Online.Rooms [Description("Featured Artist")] FeaturedArtist, + + [Description("Daily Challenge")] + DailyChallenge, } } diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs index 16cbf879df..b589e66d8b 100644 --- a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs +++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs @@ -21,6 +21,9 @@ namespace osu.Game.Tests.Visual.Metadata public override IBindableDictionary UserStates => userStates; private readonly BindableDictionary userStates = new BindableDictionary(); + public override IBindable DailyChallengeInfo => dailyChallengeInfo; + private readonly Bindable dailyChallengeInfo = new Bindable(); + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -77,5 +80,11 @@ namespace osu.Game.Tests.Visual.Metadata => Task.FromResult(new BeatmapUpdates(Array.Empty(), queueId)); public override Task BeatmapSetsUpdated(BeatmapUpdates updates) => Task.CompletedTask; + + public override Task DailyChallengeUpdated(DailyChallengeInfo? info) + { + dailyChallengeInfo.Value = info; + return Task.CompletedTask; + } } } From a4f8ed2a0ef48d8acf77877606b9d733e4170e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 May 2024 10:39:24 +0200 Subject: [PATCH 040/103] Add button to access daily challenge playlist from main menu --- .../UserInterface/TestSceneMainMenuButton.cs | 83 +++++++ .../UpdateableOnlineBeatmapSetCover.cs | 6 +- osu.Game/Graphics/OsuIcon.cs | 4 + osu.Game/Localisation/ButtonSystemStrings.cs | 7 +- osu.Game/Screens/Menu/ButtonSystem.cs | 57 +++-- osu.Game/Screens/Menu/DailyChallengeButton.cs | 209 ++++++++++++++++++ osu.Game/Screens/Menu/MainMenu.cs | 6 + osu.Game/Screens/Menu/MainMenuButton.cs | 183 +++++++++------ .../Screens/OnlinePlay/Playlists/Playlists.cs | 3 + 9 files changed, 476 insertions(+), 82 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs create mode 100644 osu.Game/Screens/Menu/DailyChallengeButton.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs new file mode 100644 index 0000000000..921e28d607 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -0,0 +1,83 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Localisation; +using osu.Game.Online.API; +using osu.Game.Online.Metadata; +using osu.Game.Online.Rooms; +using osu.Game.Screens.Menu; +using osuTK.Input; +using Color4 = osuTK.Graphics.Color4; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public partial class TestSceneMainMenuButton : OsuTestScene + { + [Resolved] + private MetadataClient metadataClient { get; set; } = null!; + + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + [Test] + public void TestStandardButton() + { + AddStep("add button", () => Child = new MainMenuButton( + ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), _ => { }, 0, Key.P) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ButtonSystemState = ButtonSystemState.TopLevel, + }); + } + + [Test] + public void TestDailyChallengeButton() + { + AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ButtonSystemState = ButtonSystemState.TopLevel, + }); + + AddStep("set up API", () => dummyAPI.HandleRequest = req => + { + switch (req) + { + case GetRoomRequest getRoomRequest: + if (getRoomRequest.RoomId != 1234) + return false; + + var beatmap = CreateAPIBeatmap(); + beatmap.OnlineID = 1001; + getRoomRequest.TriggerSuccess(new Room + { + RoomID = { Value = 1234 }, + Playlist = + { + new PlaylistItem(beatmap) + }, + EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) } + }); + return true; + + default: + return false; + } + }); + + AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo + { + RoomID = 1234, + })); + + AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs index 93b0dd5c15..2a6b6f90e3 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs @@ -43,7 +43,11 @@ namespace osu.Game.Beatmaps.Drawables protected override double TransformDuration => 400; protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) - => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad); + => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; protected override Drawable CreateDrawable(IBeatmapSetOnlineInfo model) { diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index 32e780f11c..9879ef5d14 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -120,6 +120,7 @@ namespace osu.Game.Graphics public static IconUsage Cross => get(OsuIconMapping.Cross); public static IconUsage CrossCircle => get(OsuIconMapping.CrossCircle); public static IconUsage Crown => get(OsuIconMapping.Crown); + public static IconUsage DailyChallenge => get(OsuIconMapping.DailyChallenge); public static IconUsage Debug => get(OsuIconMapping.Debug); public static IconUsage Delete => get(OsuIconMapping.Delete); public static IconUsage Details => get(OsuIconMapping.Details); @@ -218,6 +219,9 @@ namespace osu.Game.Graphics [Description(@"crown")] Crown, + [Description(@"daily-challenge")] + DailyChallenge, + [Description(@"debug")] Debug, diff --git a/osu.Game/Localisation/ButtonSystemStrings.cs b/osu.Game/Localisation/ButtonSystemStrings.cs index ba4abf63a6..b0a205eebe 100644 --- a/osu.Game/Localisation/ButtonSystemStrings.cs +++ b/osu.Game/Localisation/ButtonSystemStrings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Localisation; @@ -54,6 +54,11 @@ namespace osu.Game.Localisation /// public static LocalisableString Exit => new TranslatableString(getKey(@"exit"), @"exit"); + /// + /// "daily challenge" + /// + public static LocalisableString DailyChallenge => new TranslatableString(getKey(@"daily_challenge"), @"daily challenge"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 15a2740160..33d2a8d348 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -24,6 +24,7 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Online.API; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -46,6 +47,7 @@ namespace osu.Game.Screens.Menu public Action? OnSettings; public Action? OnMultiplayer; public Action? OnPlaylists; + public Action? OnDailyChallenge; private readonly IBindable isIdle = new BindableBool(); @@ -102,10 +104,13 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new Drawable[] { - new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O, Key.S), - backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, - -WEDGE_WIDTH) + new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), _ => OnSettings?.Invoke(), Key.O, Key.S) { + Padding = new MarginPadding { Right = WEDGE_WIDTH }, + }, + backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), _ => State = ButtonSystemState.TopLevel) + { + Padding = new MarginPadding { Right = WEDGE_WIDTH }, VisibleStateMin = ButtonSystemState.Play, VisibleStateMax = ButtonSystemState.Edit, }, @@ -127,21 +132,31 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(AudioManager audio, IdleTracker? idleTracker, GameHost host) { - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), _ => OnSolo?.Invoke(), Key.P) + { + Padding = new MarginPadding { Left = WEDGE_WIDTH }, + }); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, Key.M)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, Key.L)); + buttonsPlay.Add(new DailyChallengeButton(@"button-default-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); - buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B, Key.E)); - buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S)); + buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), _ => OnEditBeatmap?.Invoke(), Key.B, Key.E) + { + Padding = new MarginPadding { Left = WEDGE_WIDTH}, + }); + buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), _ => OnEditSkin?.Invoke(), Key.S)); buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P, Key.M, Key.L)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), _ => State = ButtonSystemState.Play, Key.P, Key.M, Key.L) + { + Padding = new MarginPadding { Left = WEDGE_WIDTH }, + }); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), _ => State = ButtonSystemState.Edit, Key.E)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), _ => OnBeatmapListing?.Invoke(), Key.B, Key.D)); if (host.CanExit) - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), _ => OnExit?.Invoke(), Key.Q)); buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsEdit); @@ -164,7 +179,7 @@ namespace osu.Game.Screens.Menu sampleLogoSwoosh = audio.Samples.Get(@"Menu/osu-logo-swoosh"); } - private void onMultiplayer() + private void onMultiplayer(MainMenuButton _) { if (api.State.Value != APIState.Online) { @@ -175,7 +190,7 @@ namespace osu.Game.Screens.Menu OnMultiplayer?.Invoke(); } - private void onPlaylists() + private void onPlaylists(MainMenuButton _) { if (api.State.Value != APIState.Online) { @@ -186,6 +201,20 @@ namespace osu.Game.Screens.Menu OnPlaylists?.Invoke(); } + private void onDailyChallenge(MainMenuButton button) + { + if (api.State.Value != APIState.Online) + { + loginOverlay?.Show(); + return; + } + + var dailyChallengeButton = (DailyChallengeButton)button; + + if (dailyChallengeButton.Room != null) + OnDailyChallenge?.Invoke(dailyChallengeButton.Room); + } + private void updateIdleState(bool isIdle) { if (!ReturnToTopOnIdle) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs new file mode 100644 index 0000000000..907fd04148 --- /dev/null +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -0,0 +1,209 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Metadata; +using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Screens.Menu +{ + public partial class DailyChallengeButton : MainMenuButton, IHasCustomTooltip + { + public Room? Room { get; private set; } + + private readonly OsuSpriteText countdown; + private ScheduledDelegate? scheduledCountdownUpdate; + + private UpdateableOnlineBeatmapSetCover cover = null!; + private IBindable info = null!; + private BufferedContainer background = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + public DailyChallengeButton(string sampleName, Color4 colour, Action? clickAction = null, params Key[] triggerKeys) + : base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys) + { + BaseSize = new Vector2(ButtonSystem.BUTTON_WIDTH * 1.3f, ButtonArea.BUTTON_AREA_HEIGHT); + + Content.Add(countdown = new OsuSpriteText + { + Shadow = true, + AllowMultiline = false, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding + { + Left = -3, + Bottom = 22, + }, + Font = OsuFont.Default.With(size: 12), + Alpha = 0, + }); + } + + protected override Drawable CreateBackground(Colour4 accentColour) => background = new BufferedContainer + { + Children = new Drawable[] + { + cover = new UpdateableOnlineBeatmapSetCover + { + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = -0.5f, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(accentColour.Opacity(0), accentColour), + Blending = BlendingParameters.Additive, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = accentColour.Opacity(0.7f) + }, + }, + }; + + [BackgroundDependencyLoader] + private void load(MetadataClient metadataClient) + { + info = metadataClient.DailyChallengeInfo.GetBoundCopy(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + info.BindValueChanged(updateDisplay, true); + FinishTransforms(true); + + cover.MoveToX(-0.5f, 10000, Easing.InOutSine) + .Then().MoveToX(0.5f, 10000, Easing.InOutSine) + .Loop(); + } + + protected override void Update() + { + base.Update(); + + cover.Width = 2 * background.DrawWidth; + } + + private void updateDisplay(ValueChangedEvent info) + { + UpdateState(); + + scheduledCountdownUpdate?.Cancel(); + scheduledCountdownUpdate = null; + + if (info.NewValue == null) + { + Room = null; + cover.OnlineInfo = TooltipContent = null; + } + else + { + var roomRequest = new GetRoomRequest(info.NewValue.Value.RoomID); + + roomRequest.Success += room => + { + Room = room; + cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; + + updateCountdown(); + Scheduler.AddDelayed(updateCountdown, 1000, true); + }; + api.Queue(roomRequest); + } + } + + private void updateCountdown() + { + if (Room == null) + return; + + var remaining = (Room.EndDate.Value - DateTimeOffset.Now) ?? TimeSpan.Zero; + + if (remaining <= TimeSpan.Zero) + { + countdown.FadeOut(250, Easing.OutQuint); + } + else + { + if (countdown.Alpha == 0) + countdown.FadeIn(250, Easing.OutQuint); + + countdown.Text = remaining.ToString(@"hh\:mm\:ss"); + } + } + + protected override void UpdateState() + { + if (info.IsNotNull() && info.Value == null) + { + ContractStyle = 0; + State = ButtonState.Contracted; + return; + } + + base.UpdateState(); + } + + public ITooltip GetCustomTooltip() => new DailyChallengeTooltip(); + + public APIBeatmapSet? TooltipContent { get; private set; } + + internal partial class DailyChallengeTooltip : CompositeDrawable, ITooltip + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private APIBeatmapSet? lastContent; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + } + + public void Move(Vector2 pos) => Position = pos; + + public void SetContent(APIBeatmapSet? content) + { + if (content == lastContent) + return; + + lastContent = content; + + ClearInternal(); + if (content != null) + AddInternal(new BeatmapCardNano(content)); + } + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 235c5d5c56..722e776e3d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -147,6 +147,12 @@ namespace osu.Game.Screens.Menu OnSolo = loadSoloSongSelect, OnMultiplayer = () => this.Push(new Multiplayer()), OnPlaylists = () => this.Push(new Playlists()), + OnDailyChallenge = room => + { + Playlists playlistsScreen; + this.Push(playlistsScreen = new Playlists()); + playlistsScreen.Join(room); + }, OnExit = () => { exitConfirmedViaHoldOrClick = true; diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index 1dc79e9b1a..fe8fb91766 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -38,11 +38,8 @@ namespace osu.Game.Screens.Menu public readonly Key[] TriggerKeys; - private readonly Container iconText; - private readonly Container box; - private readonly Box boxHoverLayer; - private readonly SpriteIcon icon; - private readonly string sampleName; + protected override Container Content => content; + private readonly Container content; /// /// The menu state for which we are visible for (assuming only one). @@ -59,7 +56,24 @@ namespace osu.Game.Screens.Menu public ButtonSystemState VisibleStateMin = ButtonSystemState.TopLevel; public ButtonSystemState VisibleStateMax = ButtonSystemState.TopLevel; - private readonly Action? clickAction; + public new MarginPadding Padding + { + get => Content.Padding; + set => Content.Padding = value; + } + + protected Vector2 BaseSize { get; init; } = new Vector2(ButtonSystem.BUTTON_WIDTH, ButtonArea.BUTTON_AREA_HEIGHT); + + private readonly Action? clickAction; + + private readonly Container background; + private readonly Drawable backgroundContent; + private readonly Box boxHoverLayer; + private readonly SpriteIcon icon; + + private Vector2 initialSize => BaseSize + Padding.Total; + + private readonly string sampleName; private Sample? sampleClick; private Sample? sampleHover; private SampleChannel? sampleChannel; @@ -68,9 +82,9 @@ namespace osu.Game.Screens.Menu // Allow keyboard interaction based on state rather than waiting for delayed animations. || state == ButtonState.Expanded; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => background.ReceivePositionalInputAt(screenSpacePos); - public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action? clickAction = null, float extraWidth = 0, params Key[] triggerKeys) + public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action? clickAction = null, params Key[] triggerKeys) { this.sampleName = sampleName; this.clickAction = clickAction; @@ -79,11 +93,9 @@ namespace osu.Game.Screens.Menu AutoSizeAxes = Axes.Both; Alpha = 0; - Vector2 boxSize = new Vector2(ButtonSystem.BUTTON_WIDTH + Math.Abs(extraWidth), ButtonArea.BUTTON_AREA_HEIGHT); - - Children = new Drawable[] + AddRangeInternal(new Drawable[] { - box = new Container + background = new Container { // box needs to be always present to ensure the button is always sized correctly for flow AlwaysPresent = true, @@ -98,35 +110,46 @@ namespace osu.Game.Screens.Menu }, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Scale = new Vector2(0, 1), - Size = boxSize, - Shear = new Vector2(ButtonSystem.WEDGE_WIDTH / boxSize.Y, 0), Children = new[] { - new Box + backgroundContent = CreateBackground(colour).With(bg => { - EdgeSmoothness = new Vector2(1.5f, 0), - RelativeSizeAxes = Axes.Both, - Colour = colour, - }, + bg.RelativeSizeAxes = Axes.Y; + bg.X = -ButtonSystem.WEDGE_WIDTH; + bg.Anchor = Anchor.Centre; + bg.Origin = Anchor.Centre; + }), boxHoverLayer = new Box { EdgeSmoothness = new Vector2(1.5f, 0), RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, Colour = Color4.White, + Depth = float.MinValue, Alpha = 0, }, } }, - iconText = new Container + content = new Container { - AutoSizeAxes = Axes.Both, - Position = new Vector2(extraWidth / 2, 0), + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, Children = new Drawable[] { + new OsuSpriteText + { + Shadow = true, + AllowMultiline = false, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding + { + Left = -3, + Bottom = 7, + }, + Text = text + }, icon = new SpriteIcon { Shadow = true, @@ -136,20 +159,36 @@ namespace osu.Game.Screens.Menu Position = new Vector2(0, 0), Margin = new MarginPadding { Top = -4 }, Icon = symbol - }, - new OsuSpriteText - { - Shadow = true, - AllowMultiline = false, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Position = new Vector2(0, 35), - Margin = new MarginPadding { Left = -3 }, - Text = text } } } - }; + }); + } + + protected virtual Drawable CreateBackground(Colour4 accentColour) => new Container + { + Child = new Box + { + EdgeSmoothness = new Vector2(1.5f, 0), + RelativeSizeAxes = Axes.Both, + Colour = accentColour, + } + }; + + protected override void LoadComplete() + { + base.LoadComplete(); + + background.Size = initialSize; + background.Shear = new Vector2(ButtonSystem.WEDGE_WIDTH / initialSize.Y, 0); + + // for whatever reason, attempting to size the background "just in time" to cover the visible width + // results in gaps when the width changes are quick (only visible when testing menu at 100% speed, not visible slowed down). + // to ensure there's no missing backdrop, just use a ballpark that should be enough to always cover the width and then some. + // note that while on a code inspections it would seem that `1.5 * initialSize.X` would be enough, elastic usings are used in this button + // (which can exceed the [0;1] range during interpolation). + backgroundContent.Width = 2 * initialSize.X; + backgroundContent.Shear = -background.Shear; } private bool rightward; @@ -179,15 +218,15 @@ namespace osu.Game.Screens.Menu { if (State != ButtonState.Expanded) return true; - sampleHover?.Play(); - - box.ScaleTo(new Vector2(1.5f, 1), 500, Easing.OutElastic); - double duration = TimeUntilNextBeat; icon.ClearTransforms(); icon.RotateTo(rightward ? -BOUNCE_ROTATION : BOUNCE_ROTATION, duration, Easing.InOutSine); icon.ScaleTo(new Vector2(HOVER_SCALE, HOVER_SCALE * BOUNCE_COMPRESSION), duration, Easing.Out); + + sampleHover?.Play(); + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(1.5f, 1)), 500, Easing.OutElastic); + return true; } @@ -199,7 +238,7 @@ namespace osu.Game.Screens.Menu icon.ScaleTo(Vector2.One, 200, Easing.Out); if (State == ButtonState.Expanded) - box.ScaleTo(new Vector2(1, 1), 500, Easing.OutElastic); + background.ResizeTo(initialSize, 500, Easing.OutElastic); } [BackgroundDependencyLoader] @@ -246,7 +285,7 @@ namespace osu.Game.Screens.Menu sampleChannel = sampleClick?.GetChannel(); sampleChannel?.Play(); - clickAction?.Invoke(); + clickAction?.Invoke(this); boxHoverLayer.ClearTransforms(); boxHoverLayer.Alpha = 0.9f; @@ -254,13 +293,13 @@ namespace osu.Game.Screens.Menu } public override bool HandleNonPositionalInput => state == ButtonState.Expanded; - public override bool HandlePositionalInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f; + public override bool HandlePositionalInput => state != ButtonState.Exploded && background.Width / initialSize.X >= 0.8f; public void StopSamplePlayback() => sampleChannel?.Stop(); protected override void Update() { - iconText.Alpha = Math.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); + content.Alpha = Math.Clamp((background.Width / initialSize.X - 0.5f) / 0.3f, 0, 1); base.Update(); } @@ -285,12 +324,12 @@ namespace osu.Game.Screens.Menu switch (ContractStyle) { default: - box.ScaleTo(new Vector2(0, 1), 500, Easing.OutExpo); + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 500, Easing.OutExpo); this.FadeOut(500); break; case 1: - box.ScaleTo(new Vector2(0, 1), 400, Easing.InSine); + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 400, Easing.InSine); this.FadeOut(800); break; } @@ -299,13 +338,13 @@ namespace osu.Game.Screens.Menu case ButtonState.Expanded: const int expand_duration = 500; - box.ScaleTo(new Vector2(1, 1), expand_duration, Easing.OutExpo); + background.ResizeTo(initialSize, expand_duration, Easing.OutExpo); this.FadeIn(expand_duration / 6f); break; case ButtonState.Exploded: const int explode_duration = 200; - box.ScaleTo(new Vector2(2, 1), explode_duration, Easing.OutExpo); + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(2, 1)), explode_duration, Easing.OutExpo); this.FadeOut(explode_duration / 4f * 3); break; } @@ -314,32 +353,44 @@ namespace osu.Game.Screens.Menu } } + private ButtonSystemState buttonSystemState; + public ButtonSystemState ButtonSystemState { + get => buttonSystemState; set { - ContractStyle = 0; + if (buttonSystemState == value) + return; - switch (value) - { - case ButtonSystemState.Initial: + buttonSystemState = value; + UpdateState(); + } + } + + protected virtual void UpdateState() + { + ContractStyle = 0; + + switch (ButtonSystemState) + { + case ButtonSystemState.Initial: + State = ButtonState.Contracted; + break; + + case ButtonSystemState.EnteringMode: + ContractStyle = 1; + State = ButtonState.Contracted; + break; + + default: + if (ButtonSystemState <= VisibleStateMax && ButtonSystemState >= VisibleStateMin) + State = ButtonState.Expanded; + else if (ButtonSystemState < VisibleStateMin) State = ButtonState.Contracted; - break; - - case ButtonSystemState.EnteringMode: - ContractStyle = 1; - State = ButtonState.Contracted; - break; - - default: - if (value <= VisibleStateMax && value >= VisibleStateMin) - State = ButtonState.Expanded; - else if (value < VisibleStateMin) - State = ButtonState.Contracted; - else - State = ButtonState.Exploded; - break; - } + else + State = ButtonState.Exploded; + break; } } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs b/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs index f1d2384c2f..9e615ffa98 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/Playlists.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 osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Lounge; namespace osu.Game.Screens.OnlinePlay.Playlists @@ -10,5 +11,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override string ScreenTitle => "Playlists"; protected override LoungeSubScreen CreateLounge() => new PlaylistsLoungeSubScreen(); + + public void Join(Room room) => Schedule(() => Lounge.Join(room, string.Empty)); } } From c9b7aaf442c89fcf4f27cfea3171ed4b4a90d8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 May 2024 11:50:23 +0200 Subject: [PATCH 041/103] Fix formatting --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 33d2a8d348..e9fff9bb07 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Menu buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), _ => OnEditBeatmap?.Invoke(), Key.B, Key.E) { - Padding = new MarginPadding { Left = WEDGE_WIDTH}, + Padding = new MarginPadding { Left = WEDGE_WIDTH }, }); buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), _ => OnEditSkin?.Invoke(), Key.S)); buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit); From a4142937e75ec240b91f930614354dae5c63d9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 May 2024 12:53:25 +0200 Subject: [PATCH 042/103] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f91995feff..821a7f1fab 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 0af32c5d4b0092f737bb91c3cac3498aebd40df8 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Sat, 18 May 2024 07:45:01 +0200 Subject: [PATCH 043/103] Added a minimum value for the scale calculated by the CS value. --- osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs b/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs index 2a5a11161b..1d3416f494 100644 --- a/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs +++ b/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Objects.Legacy // It works out to under 1 game pixel and is generally not meaningful to gameplay, but is to replay playback accuracy. const float broken_gamefield_rounding_allowance = 1.00041f; - return (float)(1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1); + return (float)Math.Max(0.02, (1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1)); } public static int CalculateDifficultyPeppyStars(BeatmapDifficulty difficulty, int objectCount, int drainLength) From a12a20e8b503bdb2a5647bebefe05de33b0553be Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Sat, 18 May 2024 18:37:44 +0200 Subject: [PATCH 044/103] Change Inputkeys to Ctrl+Up/Ctrl+Down --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 5dacb6db4d..b0a1684512 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -182,8 +182,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions), new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods), - new KeyBinding(InputKey.PageUp, GlobalAction.IncreaseSpeed), - new KeyBinding(InputKey.PageDown, GlobalAction.DecreaseSpeed), + new KeyBinding(new[] { InputKey.Control, InputKey.Up }, GlobalAction.IncreaseSpeed), + new KeyBinding(new[] { InputKey.Control, InputKey.Down }, GlobalAction.DecreaseSpeed), }; private static IEnumerable audioControlKeyBindings => new[] From 80064c4b98d955431ac423578b3a9c55b3f4ae7e Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Sat, 18 May 2024 18:38:23 +0200 Subject: [PATCH 045/103] Speedchange now also works in Modselect --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 25293e8e20..572379ea2c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -27,6 +27,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select; using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -63,6 +64,9 @@ namespace osu.Game.Overlays.Mods private Func isValidMod = _ => true; + [Resolved] + private SongSelect? songSelect { get; set; } + /// /// A function determining whether each mod in the column should be displayed. /// A return value of means that the mod is not filtered and therefore its corresponding panel should be displayed. @@ -752,6 +756,14 @@ namespace osu.Game.Overlays.Mods return true; } + + case GlobalAction.IncreaseSpeed: + songSelect!.ChangeSpeed(0.05); + return true; + + case GlobalAction.DecreaseSpeed: + songSelect!.ChangeSpeed(-0.05); + return true; } return base.OnPressed(e); From 99f30d92c84789670b40ae3989cd52b2dc3a3dc2 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Sat, 18 May 2024 18:38:31 +0200 Subject: [PATCH 046/103] Add Unit Tests --- .../SongSelect/TestScenePlaySongSelect.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index e03ffd48f1..938b858110 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -87,6 +87,84 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("delete all beatmaps", () => manager.Delete()); } + [Test] + public void TestSpeedChange() + { + createSongSelect(); + changeMods(); + + AddStep("decreasing speed without mods", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.95); + + AddStep("decreasing speed with halftime", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("halftime at 0.9", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.9); + + AddStep("increasing speed with halftime", () => songSelect?.ChangeSpeed(+0.05)); + AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.95); + + AddStep("increasing speed with halftime to nomod", () => songSelect?.ChangeSpeed(+0.05)); + AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); + + AddStep("increasing speed without mods", () => songSelect?.ChangeSpeed(+0.05)); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.05); + + AddStep("increasing speed with doubletime", () => songSelect?.ChangeSpeed(+0.05)); + AddAssert("doubletime at 1.1", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.1); + + AddStep("decreasing speed with doubletime", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.05); + + OsuModNightcore nc = new OsuModNightcore + { + SpeedChange = { Value = 1.05 } + }; + changeMods(nc); + AddStep("increasing speed with nightcore", () => songSelect?.ChangeSpeed(+0.05)); + AddAssert("nightcore at 1.1", () => songSelect!.Mods.Value.Single() is ModNightcore mod && mod.SpeedChange.Value == 1.1); + + AddStep("decreasing speed with nightcore", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModNightcore mod && mod.SpeedChange.Value == 1.05); + + AddStep("decreasing speed with nightcore to nomod", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); + + AddStep("decreasing speed nomod, nightcore was selected", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.95); + + AddStep("decreasing speed with daycore", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("daycore at 0.9", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.9); + + AddStep("increasing speed with daycore", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.95); + + OsuModDoubleTime dt = new OsuModDoubleTime + { + SpeedChange = { Value = 1.02 }, + AdjustPitch = { Value = true }, + }; + changeMods(dt); + AddStep("decreasing speed from doubletime 1.02 with adjustpitch enabled", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("halftime at 0.97 with adjustpitch enabled", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.97 && mod.AdjustPitch.Value); + + OsuModHalfTime ht = new OsuModHalfTime + { + SpeedChange = { Value = 0.97 }, + AdjustPitch = { Value = true }, + }; + Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() }; + changeMods(modlist); + AddStep("decreasing speed from halftime 0.97 with adjustpitch enabled, HDHR enabled", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("doubletime at 1.02 with adjustpitch enabled, HDHR still enabled", () => songSelect!.Mods.Value.Count(mod => (mod is ModDoubleTime modDt && modDt.AdjustPitch.Value && modDt.SpeedChange.Value == 1.02) || mod is ModHardRock || mod is ModHidden) == 3); + + changeMods(new ModWindUp()); + AddStep("windup active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("windup still active", () => songSelect!.Mods.Value.First() is ModWindUp); + + changeMods(new ModAdaptiveSpeed()); + AddStep("adaptivespeed active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("adaptivespeed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed); + } + [Test] public void TestPlaceholderBeatmapPresence() { From 3fdbd735ce063653bb92b7570df716e700a9b529 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Sat, 18 May 2024 18:40:51 +0200 Subject: [PATCH 047/103] change to single Function, Nightcore now switches into Daycore, keep Adjustpitch after change --- osu.Game/Screens/Select/SongSelect.cs | 182 +++++++++++++++----------- 1 file changed, 109 insertions(+), 73 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index de0f24aa90..e1447b284a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -98,6 +98,9 @@ namespace osu.Game.Screens.Select new OsuMenuItem(@"Select", MenuItemType.Highlighted, () => FinaliseSelection(getBeatmap())) }; + [Resolved] + private OsuGameBase? game { get; set; } + [Resolved] private Bindable> selectedMods { get; set; } = null!; @@ -144,6 +147,10 @@ namespace osu.Game.Screens.Select private Bindable configBackgroundBlur = null!; + private bool lastPitchState; + + private bool usedPitchMods; + [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { @@ -809,94 +816,123 @@ namespace osu.Game.Screens.Select return false; } - private void increaseSpeed() + public void ChangeSpeed(double delta) { - var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); - var stateDoubleTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModDoubleTime); - bool rateModActive = ModSelect.AllAvailableMods.Count(pair => pair.Mod is ModRateAdjust && pair.Active.Value) > 0; - const double stepsize = 0.05d; - double newRate = 1d + stepsize; + // Mod Change from 0.95 DC to 1.0 none to 1.05 DT/NC ? - // If no mod rateAdjust mod is currently active activate DoubleTime with speed newRate - if (!rateModActive) - { - stateDoubleTime.Active.Value = true; - ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; + if (game == null) return; + + ModNightcore modNc = (ModNightcore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModNightcore) > 0)).Mods.First(mod => mod is ModNightcore); + ModDoubleTime modDt = (ModDoubleTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDoubleTime) > 0)).Mods.First(mod => mod is ModDoubleTime); + ModDaycore modDc = (ModDaycore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDaycore) > 0)).Mods.First(mod => mod is ModDaycore); + ModHalfTime modHt = (ModHalfTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModHalfTime) > 0)).Mods.First(mod => mod is ModHalfTime); + bool rateModActive = selectedMods.Value.Count(mod => mod is ModRateAdjust) > 0; + bool incompatiableModActive = selectedMods.Value.Count(mod => modDt.IncompatibleMods.Count(incompatibleMod => (mod.GetType().IsSubclassOf(incompatibleMod) || mod.GetType() == incompatibleMod) && incompatibleMod != typeof(ModRateAdjust)) > 0) > 0; + double newRate = 1d + delta; + bool isPositive = delta > 0; + + if (incompatiableModActive) return; - } - // Find current active rateAdjust mod and modify speed, enable DoubleTime if necessary - foreach (var state in rateAdjustStates) + if (rateModActive) { - ModRateAdjust mod = (ModRateAdjust)state.Mod; + ModRateAdjust mod = (ModRateAdjust)selectedMods.Value.First(mod => mod is ModRateAdjust); - if (!state.Active.Value) continue; + // Find current active rateAdjust mod and modify speed, enable HalfTime if necessary + newRate = mod.SpeedChange.Value + delta; - newRate = mod.SpeedChange.Value + stepsize; - - if (mod.Acronym == "DT" || mod.Acronym == "NC") - mod.SpeedChange.Value = newRate; - else + if (newRate == 1.0) { - if (newRate == 1.0d) - state.Active.Value = false; + lastPitchState = false; + usedPitchMods = false; - if (newRate > 1d) - { - state.Active.Value = false; - stateDoubleTime.Active.Value = true; - ((ModDoubleTime)stateDoubleTime.Mod).SpeedChange.Value = newRate; - break; - } + if (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) lastPitchState = true; - if (newRate < 1d) - mod.SpeedChange.Value = newRate; + if (mod is ModHalfTime htmod && htmod.AdjustPitch.Value) lastPitchState = true; + + if (mod is ModNightcore || mod is ModDaycore) usedPitchMods = true; + + //Disable RateAdjustMods + selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); + return; } - } - } - private void decreaseSpeed() - { - var rateAdjustStates = ModSelect.AllAvailableMods.Where(pair => pair.Mod is ModRateAdjust); - var stateHalfTime = ModSelect.AllAvailableMods.First(pair => pair.Mod is ModHalfTime); - bool rateModActive = ModSelect.AllAvailableMods.Count(pair => pair.Mod is ModRateAdjust && pair.Active.Value) > 0; - const double stepsize = 0.05d; - double newRate = 1d - stepsize; - - // If no mod rateAdjust mod is currently active activate HalfTime with speed newRate - if (!rateModActive) - { - stateHalfTime.Active.Value = true; - ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; - return; - } - - // Find current active rateAdjust mod and modify speed, enable HalfTime if necessary - foreach (var state in rateAdjustStates) - { - ModRateAdjust mod = (ModRateAdjust)state.Mod; - - if (!state.Active.Value) continue; - - newRate = mod.SpeedChange.Value - stepsize; - - if (mod.Acronym == "HT" || mod.Acronym == "DC") - mod.SpeedChange.Value = newRate; - else + if (((mod is ModDoubleTime || mod is ModNightcore) && newRate < mod.SpeedChange.MinValue) + || ((mod is ModHalfTime || mod is ModDaycore) && newRate > mod.SpeedChange.MaxValue)) { - if (newRate == 1.0d) - state.Active.Value = false; + bool adjustPitch = (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) || (mod is ModHalfTime htmod && htmod.AdjustPitch.Value); - if (newRate < 1d) + //Disable RateAdjustMods + selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); + + ModRateAdjust? oppositeMod = null; + + switch (mod) { - state.Active.Value = false; - stateHalfTime.Active.Value = true; - ((ModHalfTime)stateHalfTime.Mod).SpeedChange.Value = newRate; - break; + case ModDoubleTime: + modHt.AdjustPitch.Value = adjustPitch; + oppositeMod = modHt; + break; + + case ModHalfTime: + modDt.AdjustPitch.Value = adjustPitch; + oppositeMod = modDt; + break; + + case ModNightcore: + oppositeMod = modDc; + break; + + case ModDaycore: + oppositeMod = modNc; + break; } - if (newRate > 1d) - mod.SpeedChange.Value = newRate; + if (oppositeMod == null) return; + + oppositeMod.SpeedChange.Value = newRate; + selectedMods.Value = selectedMods.Value.Append(oppositeMod).ToList(); + return; + } + + if (newRate > mod.SpeedChange.MaxValue && (mod is ModDoubleTime || mod is ModNightcore)) + newRate = mod.SpeedChange.MaxValue; + + if (newRate < mod.SpeedChange.MinValue && (mod is ModHalfTime || mod is ModDaycore)) + newRate = mod.SpeedChange.MinValue; + + mod.SpeedChange.Value = newRate; + } + else + { + // If no ModRateAdjust is actived activate one + if (isPositive) + { + if (!usedPitchMods) + { + modDt.SpeedChange.Value = newRate; + modDt.AdjustPitch.Value = lastPitchState; + selectedMods.Value = selectedMods.Value.Append(modDt).ToList(); + } + else + { + modNc.SpeedChange.Value = newRate; + selectedMods.Value = selectedMods.Value.Append(modNc).ToList(); + } + } + else + { + if (!usedPitchMods) + { + modHt.SpeedChange.Value = newRate; + modHt.AdjustPitch.Value = lastPitchState; + selectedMods.Value = selectedMods.Value.Append(modHt).ToList(); + } + else + { + modDc.SpeedChange.Value = newRate; + selectedMods.Value = selectedMods.Value.Append(modDc).ToList(); + } } } } @@ -1111,11 +1147,11 @@ namespace osu.Game.Screens.Select return true; case GlobalAction.IncreaseSpeed: - increaseSpeed(); + ChangeSpeed(0.05); return true; case GlobalAction.DecreaseSpeed: - decreaseSpeed(); + ChangeSpeed(-0.05); return true; } From db8b72eb37464d50ec5092f7180082507e7fc2b0 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Mon, 20 May 2024 16:23:16 +0200 Subject: [PATCH 048/103] Clamped Difficulty Ranges to [0,10] --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 6fa78fa8e6..059451e228 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -383,21 +383,24 @@ namespace osu.Game.Beatmaps.Formats switch (pair.Key) { case @"HPDrainRate": - difficulty.DrainRate = Parsing.ParseFloat(pair.Value); + difficulty.DrainRate = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); break; case @"CircleSize": difficulty.CircleSize = Parsing.ParseFloat(pair.Value); + //If the mode is not Mania, clamp circle size to [0,10] + if (!beatmap.BeatmapInfo.Ruleset.OnlineID.Equals(3)) + difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 0, 10); break; case @"OverallDifficulty": - difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); + difficulty.OverallDifficulty = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); if (!hasApproachRate) difficulty.ApproachRate = difficulty.OverallDifficulty; break; case @"ApproachRate": - difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); + difficulty.ApproachRate = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); hasApproachRate = true; break; From 1127a69359f6eb9305d74a85dff8579278135997 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Tue, 21 May 2024 10:15:53 +0200 Subject: [PATCH 049/103] Moved DIfficulty Clamping to occur after the file has been parsed This is to handle potential issues with the ruleset being parsed after circle size has been parsed. --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 059451e228..2acabe2518 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -85,6 +85,8 @@ namespace osu.Game.Beatmaps.Formats base.ParseStreamInto(stream, beatmap); + applyDifficultyRestrictions(beatmap.Difficulty); + flushPendingPoints(); // Objects may be out of order *only* if a user has manually edited an .osu file. @@ -102,6 +104,26 @@ namespace osu.Game.Beatmaps.Formats } } + /// + /// Clamp Difficulty settings to be within the normal range. + /// + private void applyDifficultyRestrictions(BeatmapDifficulty difficulty) + { + difficulty.DrainRate = Math.Clamp(difficulty.DrainRate, 0, 10); + //If the mode is not Mania, clamp circle size to [0,10] + if (!beatmap.BeatmapInfo.Ruleset.OnlineID.Equals(3)) + difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 0, 10); + //If it is Mania, it must be within [1,20] - dual stages with 10 keys each. + //The lower bound should be 4, but there are ranked maps that are lower than this. + else + difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 1, 20); + difficulty.OverallDifficulty = Math.Clamp(difficulty.OverallDifficulty, 0, 10); + difficulty.ApproachRate = Math.Clamp(difficulty.ApproachRate, 0, 10); + + difficulty.SliderMultiplier = Math.Clamp(difficulty.SliderMultiplier, 0.4, 3.6); + difficulty.SliderTickRate = Math.Clamp(difficulty.SliderTickRate, 0.5, 8); + } + /// /// Processes the beatmap such that a new combo is started the first hitobject following each break. /// @@ -383,33 +405,30 @@ namespace osu.Game.Beatmaps.Formats switch (pair.Key) { case @"HPDrainRate": - difficulty.DrainRate = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); + difficulty.DrainRate = Parsing.ParseFloat(pair.Value); break; case @"CircleSize": difficulty.CircleSize = Parsing.ParseFloat(pair.Value); - //If the mode is not Mania, clamp circle size to [0,10] - if (!beatmap.BeatmapInfo.Ruleset.OnlineID.Equals(3)) - difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 0, 10); break; case @"OverallDifficulty": - difficulty.OverallDifficulty = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); + difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); if (!hasApproachRate) difficulty.ApproachRate = difficulty.OverallDifficulty; break; case @"ApproachRate": - difficulty.ApproachRate = Math.Clamp(Parsing.ParseFloat(pair.Value), 0, 10); + difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); hasApproachRate = true; break; case @"SliderMultiplier": - difficulty.SliderMultiplier = Math.Clamp(Parsing.ParseDouble(pair.Value), 0.4, 3.6); + difficulty.SliderMultiplier = Parsing.ParseDouble(pair.Value); break; case @"SliderTickRate": - difficulty.SliderTickRate = Math.Clamp(Parsing.ParseDouble(pair.Value), 0.5, 8); + difficulty.SliderTickRate = Parsing.ParseDouble(pair.Value); break; } } From 148afd120127c58655ed35190fb7456ce1f0e973 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Tue, 21 May 2024 14:47:34 +0200 Subject: [PATCH 050/103] Change Speedchange behaviour to keep changing while holding key, Add Toast to nofity user what just happend --- osu.Game/Localisation/ToastStrings.cs | 5 +++++ osu.Game/Overlays/OSD/SpeedChangeToast.cs | 17 +++++++++++++++ osu.Game/Screens/Select/SongSelect.cs | 26 ++++++++++++++++------- 3 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Overlays/OSD/SpeedChangeToast.cs diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index da798a3937..33027966dd 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -49,6 +49,11 @@ namespace osu.Game.Localisation /// public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied"); + /// + /// "Speed Changed" + /// + public static LocalisableString SpeedChanged => new TranslatableString(getKey(@"speed_changed"), @"Speed Changed"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/OSD/SpeedChangeToast.cs b/osu.Game/Overlays/OSD/SpeedChangeToast.cs new file mode 100644 index 0000000000..73ba23622b --- /dev/null +++ b/osu.Game/Overlays/OSD/SpeedChangeToast.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Configuration; +using osu.Game.Input.Bindings; +using osu.Game.Localisation; + +namespace osu.Game.Overlays.OSD +{ + public partial class SpeedChangeToast : Toast + { + public SpeedChangeToast(OsuConfigManager config, double delta) + : base(CommonStrings.Beatmaps, ToastStrings.SpeedChanged, config.LookupKeyBindings(GlobalAction.IncreaseSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseSpeed)) + { + } + } +} diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e1447b284a..7eb2be9100 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -30,6 +30,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Backgrounds; @@ -151,6 +152,12 @@ namespace osu.Game.Screens.Select private bool usedPitchMods; + [Resolved] + private OnScreenDisplay? onScreenDisplay { get; set; } + + [Resolved] + private OsuConfigManager? config { get; set; } + [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { @@ -819,7 +826,7 @@ namespace osu.Game.Screens.Select public void ChangeSpeed(double delta) { // Mod Change from 0.95 DC to 1.0 none to 1.05 DT/NC ? - + onScreenDisplay?.Display(new SpeedChangeToast(config!, delta)); if (game == null) return; ModNightcore modNc = (ModNightcore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModNightcore) > 0)).Mods.First(mod => mod is ModNightcore); @@ -1135,17 +1142,10 @@ namespace osu.Game.Screens.Select public virtual bool OnPressed(KeyBindingPressEvent e) { - if (e.Repeat) - return false; - if (!this.IsCurrentScreen()) return false; switch (e.Action) { - case GlobalAction.Select: - FinaliseSelection(); - return true; - case GlobalAction.IncreaseSpeed: ChangeSpeed(0.05); return true; @@ -1155,6 +1155,16 @@ namespace osu.Game.Screens.Select return true; } + if (e.Repeat) + return false; + + switch (e.Action) + { + case GlobalAction.Select: + FinaliseSelection(); + return true; + } + return false; } From 3403789c6fef04827679b27715b653778b7d0aed Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Tue, 21 May 2024 16:11:20 +0200 Subject: [PATCH 051/103] Toast now only shows when speed is actually changed --- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 7eb2be9100..b78134392b 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -825,8 +825,6 @@ namespace osu.Game.Screens.Select public void ChangeSpeed(double delta) { - // Mod Change from 0.95 DC to 1.0 none to 1.05 DT/NC ? - onScreenDisplay?.Display(new SpeedChangeToast(config!, delta)); if (game == null) return; ModNightcore modNc = (ModNightcore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModNightcore) > 0)).Mods.First(mod => mod is ModNightcore); @@ -841,6 +839,8 @@ namespace osu.Game.Screens.Select if (incompatiableModActive) return; + onScreenDisplay?.Display(new SpeedChangeToast(config!, delta)); + if (rateModActive) { ModRateAdjust mod = (ModRateAdjust)selectedMods.Value.First(mod => mod is ModRateAdjust); From 99d99cede03993796aa4f8fe5f1d344a9cfe9472 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 11:59:34 +0800 Subject: [PATCH 052/103] Basic cleanup Before I gave up on attempting to fix the method. --- osu.Game/Screens/Select/SongSelect.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b78134392b..18d5799bae 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Select }; [Resolved] - private OsuGameBase? game { get; set; } + private OsuGameBase game { get; set; } = null!; [Resolved] private Bindable> selectedMods { get; set; } = null!; @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Select private OnScreenDisplay? onScreenDisplay { get; set; } [Resolved] - private OsuConfigManager? config { get; set; } + private OsuConfigManager config { get; set; } = null!; [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) @@ -825,21 +825,19 @@ namespace osu.Game.Screens.Select public void ChangeSpeed(double delta) { - if (game == null) return; - ModNightcore modNc = (ModNightcore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModNightcore) > 0)).Mods.First(mod => mod is ModNightcore); ModDoubleTime modDt = (ModDoubleTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDoubleTime) > 0)).Mods.First(mod => mod is ModDoubleTime); ModDaycore modDc = (ModDaycore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDaycore) > 0)).Mods.First(mod => mod is ModDaycore); ModHalfTime modHt = (ModHalfTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModHalfTime) > 0)).Mods.First(mod => mod is ModHalfTime); bool rateModActive = selectedMods.Value.Count(mod => mod is ModRateAdjust) > 0; - bool incompatiableModActive = selectedMods.Value.Count(mod => modDt.IncompatibleMods.Count(incompatibleMod => (mod.GetType().IsSubclassOf(incompatibleMod) || mod.GetType() == incompatibleMod) && incompatibleMod != typeof(ModRateAdjust)) > 0) > 0; + bool incompatibleModActive = selectedMods.Value.Count(mod => modDt.IncompatibleMods.Count(incompatibleMod => (mod.GetType().IsSubclassOf(incompatibleMod) || mod.GetType() == incompatibleMod) && incompatibleMod != typeof(ModRateAdjust)) > 0) > 0; double newRate = 1d + delta; bool isPositive = delta > 0; - if (incompatiableModActive) + if (incompatibleModActive) return; - onScreenDisplay?.Display(new SpeedChangeToast(config!, delta)); + onScreenDisplay?.Display(new SpeedChangeToast(config, delta)); if (rateModActive) { @@ -912,7 +910,7 @@ namespace osu.Game.Screens.Select } else { - // If no ModRateAdjust is actived activate one + // If no ModRateAdjust is active, activate one if (isPositive) { if (!usedPitchMods) From 02a388cba6493207a170728abd607d80bfdecb3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 12:03:48 +0800 Subject: [PATCH 053/103] Fix enum not being at end (and adjust naming) --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 16 ++++++++-------- .../GlobalActionKeyBindingStrings.cs | 8 ++++---- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- osu.Game/Overlays/OSD/SpeedChangeToast.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index b0a1684512..09db7461d6 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -182,8 +182,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions), new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods), - new KeyBinding(new[] { InputKey.Control, InputKey.Up }, GlobalAction.IncreaseSpeed), - new KeyBinding(new[] { InputKey.Control, InputKey.Down }, GlobalAction.DecreaseSpeed), + new KeyBinding(new[] { InputKey.Control, InputKey.Up }, GlobalAction.IncreaseModSpeed), + new KeyBinding(new[] { InputKey.Control, InputKey.Down }, GlobalAction.DecreaseModSpeed), }; private static IEnumerable audioControlKeyBindings => new[] @@ -411,12 +411,6 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))] EditorToggleRotateControl, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseSpeed))] - IncreaseSpeed, - - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseSpeed))] - DecreaseSpeed, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))] IncreaseOffset, @@ -428,6 +422,12 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.StepReplayBackward))] StepReplayBackward, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseModSpeed))] + IncreaseModSpeed, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseModSpeed))] + DecreaseModSpeed, } public enum GlobalActionCategory diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index d0cbf52f07..18a1d3e4fe 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -370,14 +370,14 @@ namespace osu.Game.Localisation public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control"); /// - /// "Increase Speed" + /// "Increase mod speed" /// - public static LocalisableString IncreaseSpeed => new TranslatableString(getKey(@"increase_speed"), @"Increase Speed"); + public static LocalisableString IncreaseModSpeed => new TranslatableString(getKey(@"increase_mod_speed"), @"Increase mod speed"); /// - /// "Decrease Speed" + /// "Decrease mod speed" /// - public static LocalisableString DecreaseSpeed => new TranslatableString(getKey(@"decrease_speed"), @"Decrease Speed"); + public static LocalisableString DecreaseModSpeed => new TranslatableString(getKey(@"decrease_mod_speed"), @"Decrease mod speed"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 572379ea2c..3b8090a4b2 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -757,11 +757,11 @@ namespace osu.Game.Overlays.Mods return true; } - case GlobalAction.IncreaseSpeed: + case GlobalAction.IncreaseModSpeed: songSelect!.ChangeSpeed(0.05); return true; - case GlobalAction.DecreaseSpeed: + case GlobalAction.DecreaseModSpeed: songSelect!.ChangeSpeed(-0.05); return true; } diff --git a/osu.Game/Overlays/OSD/SpeedChangeToast.cs b/osu.Game/Overlays/OSD/SpeedChangeToast.cs index 73ba23622b..231ef86526 100644 --- a/osu.Game/Overlays/OSD/SpeedChangeToast.cs +++ b/osu.Game/Overlays/OSD/SpeedChangeToast.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.OSD public partial class SpeedChangeToast : Toast { public SpeedChangeToast(OsuConfigManager config, double delta) - : base(CommonStrings.Beatmaps, ToastStrings.SpeedChanged, config.LookupKeyBindings(GlobalAction.IncreaseSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseSpeed)) + : base(CommonStrings.Beatmaps, ToastStrings.SpeedChanged, config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed)) { } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 18d5799bae..257f6583a4 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -1144,11 +1144,11 @@ namespace osu.Game.Screens.Select switch (e.Action) { - case GlobalAction.IncreaseSpeed: + case GlobalAction.IncreaseModSpeed: ChangeSpeed(0.05); return true; - case GlobalAction.DecreaseSpeed: + case GlobalAction.DecreaseModSpeed: ChangeSpeed(-0.05); return true; } From f979200712aa0336de04e30fb2b3bebd714b5920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 12:06:51 +0800 Subject: [PATCH 054/103] Use null conditional rather than implicit not-null --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 3b8090a4b2..ad589e8fa9 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -758,11 +758,11 @@ namespace osu.Game.Overlays.Mods } case GlobalAction.IncreaseModSpeed: - songSelect!.ChangeSpeed(0.05); + songSelect?.ChangeSpeed(0.05); return true; case GlobalAction.DecreaseModSpeed: - songSelect!.ChangeSpeed(-0.05); + songSelect?.ChangeSpeed(-0.05); return true; } From d0b1ebff5a616ca89391a87699df320edbb695a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 16:29:39 +0800 Subject: [PATCH 055/103] Revert "Temporary rollback of framework / SDL3" This reverts commit d7d569cf4e68acdbcc9cec844337f93bdb207a54. --- osu.Android.props | 2 +- osu.Android/AndroidJoystickSettings.cs | 76 --------------- osu.Android/AndroidMouseSettings.cs | 97 ------------------- osu.Android/OsuGameAndroid.cs | 22 ----- osu.Desktop/OsuGameDesktop.cs | 11 ++- osu.Desktop/Program.cs | 33 ++++--- .../Components/PathControlPointVisualiser.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 3 +- .../Screens/Ladder/LadderDragContainer.cs | 2 +- osu.Game/Database/EmptyRealmSet.cs | 5 + .../UserInterface/ExpandableSlider.cs | 8 +- .../Graphics/UserInterface/OsuSliderBar.cs | 9 +- .../Graphics/UserInterface/OsuTabControl.cs | 24 +++-- .../Graphics/UserInterface/PageTabControl.cs | 14 ++- .../UserInterface/RoundedSliderBar.cs | 5 +- .../UserInterface/ShearedSliderBar.cs | 5 +- .../UserInterfaceV2/LabelledSliderBar.cs | 4 +- .../UserInterfaceV2/SliderWithTextBoxInput.cs | 8 +- osu.Game/OsuGameBase.cs | 12 ++- .../BeatmapListingCardSizeTabControl.cs | 12 ++- ...BeatmapSearchMultipleSelectionFilterRow.cs | 4 + .../Overlays/BeatmapListing/FilterTabItem.cs | 12 ++- .../BeatmapSet/BeatmapRulesetSelector.cs | 2 +- .../OverlayPanelDisplayStyleControl.cs | 14 ++- osu.Game/Overlays/OverlayRulesetTabItem.cs | 14 ++- osu.Game/Overlays/OverlayStreamItem.cs | 12 ++- osu.Game/Overlays/OverlayTabControl.cs | 14 ++- .../Overlays/Settings/Sections/SizeSlider.cs | 3 +- .../Settings/SettingsPercentageSlider.cs | 4 +- osu.Game/Overlays/Settings/SettingsSlider.cs | 6 +- .../Toolbar/ToolbarRulesetSelector.cs | 16 +-- .../Toolbar/ToolbarRulesetTabButton.cs | 12 +++ osu.Game/Rulesets/Mods/DifficultyBindable.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 3 +- .../Scoring/LegacyDrainingHealthProcessor.cs | 7 ++ .../Rulesets/UI/FrameStabilityContainer.cs | 2 +- .../Timeline/TimelineTickDisplay.cs | 3 +- osu.Game/Screens/Edit/Editor.cs | 14 +-- .../IndeterminateSliderWithTextBoxInput.cs | 8 +- .../Match/Components/MatchTypePicker.cs | 11 ++- .../Play/PlayerSettings/PlayerSliderBar.cs | 4 +- osu.Game/osu.Game.csproj | 4 +- osu.iOS.props | 2 +- 44 files changed, 226 insertions(+), 304 deletions(-) delete mode 100644 osu.Android/AndroidJoystickSettings.cs delete mode 100644 osu.Android/AndroidMouseSettings.cs diff --git a/osu.Android.props b/osu.Android.props index 2d7a9d2652..e20ac2e0b7 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index b2e3fc0779..103ef50e0c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From b25987ffe726cd5815aa8f84919180db47a88ddc Mon Sep 17 00:00:00 2001 From: Aurelian Date: Wed, 22 May 2024 11:37:55 +0200 Subject: [PATCH 056/103] Changed allowed mania keys, and reverted 0af32c5 --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 +++--- osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 2acabe2518..e5567b2215 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -111,12 +111,12 @@ namespace osu.Game.Beatmaps.Formats { difficulty.DrainRate = Math.Clamp(difficulty.DrainRate, 0, 10); //If the mode is not Mania, clamp circle size to [0,10] - if (!beatmap.BeatmapInfo.Ruleset.OnlineID.Equals(3)) + if (beatmap.BeatmapInfo.Ruleset.OnlineID != 3) difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 0, 10); - //If it is Mania, it must be within [1,20] - dual stages with 10 keys each. + //If it is Mania, it must be within [1,18] - copying what stable does https://github.com/ppy/osu/pull/28200#discussion_r1609522988 //The lower bound should be 4, but there are ranked maps that are lower than this. else - difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 1, 20); + difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 1, 18); difficulty.OverallDifficulty = Math.Clamp(difficulty.OverallDifficulty, 0, 10); difficulty.ApproachRate = Math.Clamp(difficulty.ApproachRate, 0, 10); diff --git a/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs b/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs index 1d3416f494..2a5a11161b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs +++ b/osu.Game/Rulesets/Objects/Legacy/LegacyRulesetExtensions.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Objects.Legacy // It works out to under 1 game pixel and is generally not meaningful to gameplay, but is to replay playback accuracy. const float broken_gamefield_rounding_allowance = 1.00041f; - return (float)Math.Max(0.02, (1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1)); + return (float)(1.0f - 0.7f * IBeatmapDifficultyInfo.DifficultyRange(circleSize)) / 2 * (applyFudge ? broken_gamefield_rounding_allowance : 1); } public static int CalculateDifficultyPeppyStars(BeatmapDifficulty difficulty, int objectCount, int drainLength) From 97fe59cb24cc2f26ff2d099ef6da09c95d9fd6ba Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 22 May 2024 10:38:47 +0100 Subject: [PATCH 057/103] set `Ranked` to `true` for `OsuModTraceable` --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 9671f53bea..75ad00e169 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; + public override bool Ranked => true; public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) }; From f3cae73e2ed892469e1879f834f4c8472e06cf13 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Wed, 22 May 2024 13:26:00 +0200 Subject: [PATCH 058/103] Added tests for difficulty clamping --- .../Formats/LegacyBeatmapDecoderTest.cs | 30 +++++++++++++++++++ .../out-of-range-difficulties-mania.osu | 5 ++++ .../Resources/out-of-range-difficulties.osu | 10 +++++++ 3 files changed, 45 insertions(+) create mode 100644 osu.Game.Tests/Resources/out-of-range-difficulties-mania.osu create mode 100644 osu.Game.Tests/Resources/out-of-range-difficulties.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 02432a1935..e6daba2016 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -1188,5 +1188,35 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(beatmap.HitObjects[0].GetEndTime(), Is.EqualTo(3153)); } } + + [Test] + public void TestBeatmapDifficultyIsClamped() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("out-of-range-difficulties.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream).Difficulty; + Assert.That(decoded.DrainRate, Is.EqualTo(10)); + Assert.That(decoded.CircleSize, Is.EqualTo(10)); + Assert.That(decoded.OverallDifficulty, Is.EqualTo(10)); + Assert.That(decoded.ApproachRate, Is.EqualTo(10)); + Assert.That(decoded.SliderMultiplier, Is.EqualTo(3.6)); + Assert.That(decoded.SliderTickRate, Is.EqualTo(8)); + } + } + [Test] + public void TestManiaBeatmapDifficultyCircleSizeClamp() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("out-of-range-difficulties-mania.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream).Difficulty; + Assert.That(decoded.CircleSize, Is.EqualTo(14)); + } + } } } diff --git a/osu.Game.Tests/Resources/out-of-range-difficulties-mania.osu b/osu.Game.Tests/Resources/out-of-range-difficulties-mania.osu new file mode 100644 index 0000000000..7dc2e51ad9 --- /dev/null +++ b/osu.Game.Tests/Resources/out-of-range-difficulties-mania.osu @@ -0,0 +1,5 @@ +[General] +Mode: 3 + +[Difficulty] +CircleSize:14 \ No newline at end of file diff --git a/osu.Game.Tests/Resources/out-of-range-difficulties.osu b/osu.Game.Tests/Resources/out-of-range-difficulties.osu new file mode 100644 index 0000000000..5029395614 --- /dev/null +++ b/osu.Game.Tests/Resources/out-of-range-difficulties.osu @@ -0,0 +1,10 @@ +[General] +Mode: 0 + +[Difficulty] +HPDrainRate:25 +CircleSize:25 +OverallDifficulty:25 +ApproachRate:30 +SliderMultiplier:30 +SliderTickRate:30 \ No newline at end of file From 57da4229ff621a12a43ae704bbd21884a9039d74 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Wed, 22 May 2024 13:58:59 +0200 Subject: [PATCH 059/103] Add speed value to Toast --- osu.Game/Localisation/ToastStrings.cs | 4 ++-- osu.Game/Overlays/OSD/SpeedChangeToast.cs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 33027966dd..25899153f8 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -50,9 +50,9 @@ namespace osu.Game.Localisation public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied"); /// - /// "Speed Changed" + /// "Speed changed to" /// - public static LocalisableString SpeedChanged => new TranslatableString(getKey(@"speed_changed"), @"Speed Changed"); + public static LocalisableString SpeedChangedTo => new TranslatableString(getKey(@"speed_changed"), @"Speed changed to"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Overlays/OSD/SpeedChangeToast.cs b/osu.Game/Overlays/OSD/SpeedChangeToast.cs index 231ef86526..df4f825541 100644 --- a/osu.Game/Overlays/OSD/SpeedChangeToast.cs +++ b/osu.Game/Overlays/OSD/SpeedChangeToast.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.Threading; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -9,8 +10,8 @@ namespace osu.Game.Overlays.OSD { public partial class SpeedChangeToast : Toast { - public SpeedChangeToast(OsuConfigManager config, double delta) - : base(CommonStrings.Beatmaps, ToastStrings.SpeedChanged, config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed)) + public SpeedChangeToast(OsuConfigManager config, double newSpeed) + : base(CommonStrings.Beatmaps, ToastStrings.SpeedChangedTo + " " + newSpeed.ToString(Thread.CurrentThread.CurrentCulture), config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed)) { } } From abc67ebbaccfdc3d36ef1853f766829685e1308e Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Wed, 22 May 2024 13:59:26 +0200 Subject: [PATCH 060/103] Fix test not running due to floating point number inaccuacy --- .../SongSelect/TestScenePlaySongSelect.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 938b858110..af8b2a7760 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -94,25 +94,25 @@ namespace osu.Game.Tests.Visual.SongSelect changeMods(); AddStep("decreasing speed without mods", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.95); + AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); AddStep("decreasing speed with halftime", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.9", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.9); + AddAssert("halftime at 0.9", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.9); AddStep("increasing speed with halftime", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.95); + AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); AddStep("increasing speed with halftime to nomod", () => songSelect?.ChangeSpeed(+0.05)); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); AddStep("increasing speed without mods", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.05); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); AddStep("increasing speed with doubletime", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("doubletime at 1.1", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.1); + AddAssert("doubletime at 1.1", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.1); AddStep("decreasing speed with doubletime", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && mod.SpeedChange.Value == 1.05); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); OsuModNightcore nc = new OsuModNightcore { @@ -120,22 +120,22 @@ namespace osu.Game.Tests.Visual.SongSelect }; changeMods(nc); AddStep("increasing speed with nightcore", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("nightcore at 1.1", () => songSelect!.Mods.Value.Single() is ModNightcore mod && mod.SpeedChange.Value == 1.1); + AddAssert("nightcore at 1.1", () => songSelect!.Mods.Value.Single() is ModNightcore mod && Math.Round(mod.SpeedChange.Value, 2) == 1.1); AddStep("decreasing speed with nightcore", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModNightcore mod && mod.SpeedChange.Value == 1.05); + AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModNightcore mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); AddStep("decreasing speed with nightcore to nomod", () => songSelect?.ChangeSpeed(-0.05)); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); AddStep("decreasing speed nomod, nightcore was selected", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.95); + AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); AddStep("decreasing speed with daycore", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("daycore at 0.9", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.9); + AddAssert("daycore at 0.9", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.9); AddStep("increasing speed with daycore", () => songSelect?.ChangeSpeed(0.05)); - AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && mod.SpeedChange.Value == 0.95); + AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); OsuModDoubleTime dt = new OsuModDoubleTime { @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.SongSelect }; changeMods(dt); AddStep("decreasing speed from doubletime 1.02 with adjustpitch enabled", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.97 with adjustpitch enabled", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && mod.SpeedChange.Value == 0.97 && mod.AdjustPitch.Value); + AddAssert("halftime at 0.97 with adjustpitch enabled", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.97 && mod.AdjustPitch.Value); OsuModHalfTime ht = new OsuModHalfTime { @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.SongSelect Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() }; changeMods(modlist); AddStep("decreasing speed from halftime 0.97 with adjustpitch enabled, HDHR enabled", () => songSelect?.ChangeSpeed(0.05)); - AddAssert("doubletime at 1.02 with adjustpitch enabled, HDHR still enabled", () => songSelect!.Mods.Value.Count(mod => (mod is ModDoubleTime modDt && modDt.AdjustPitch.Value && modDt.SpeedChange.Value == 1.02) || mod is ModHardRock || mod is ModHidden) == 3); + AddAssert("doubletime at 1.02 with adjustpitch enabled, HDHR still enabled", () => songSelect!.Mods.Value.Count(mod => (mod is ModDoubleTime modDt && modDt.AdjustPitch.Value && Math.Round(modDt.SpeedChange.Value, 2) == 1.02) || mod is ModHardRock || mod is ModHidden) == 3); changeMods(new ModWindUp()); AddStep("windup active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); From 0df634574fc5b680e168d59966a2d537a2baa160 Mon Sep 17 00:00:00 2001 From: Fabian van Oeffelt Date: Wed, 22 May 2024 13:59:33 +0200 Subject: [PATCH 061/103] Improve readability --- osu.Game/Screens/Select/SongSelect.cs | 223 ++++++++++++++------------ 1 file changed, 119 insertions(+), 104 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 257f6583a4..b3823d7a0f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -823,123 +823,138 @@ namespace osu.Game.Screens.Select return false; } + private Mod getRateMod(ModType modType, Type type) + { + var modList = game.AvailableMods.Value[modType]; + var multiMod = (MultiMod)modList.First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(mod2 => mod2.GetType().IsSubclassOf(type)) > 0); + var mod = multiMod.Mods.First(mod => mod.GetType().IsSubclassOf(type)); + return mod; + } + public void ChangeSpeed(double delta) { - ModNightcore modNc = (ModNightcore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModNightcore) > 0)).Mods.First(mod => mod is ModNightcore); - ModDoubleTime modDt = (ModDoubleTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyIncrease].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDoubleTime) > 0)).Mods.First(mod => mod is ModDoubleTime); - ModDaycore modDc = (ModDaycore)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModDaycore) > 0)).Mods.First(mod => mod is ModDaycore); - ModHalfTime modHt = (ModHalfTime)((MultiMod)game.AvailableMods.Value[ModType.DifficultyReduction].First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(modType => modType is ModHalfTime) > 0)).Mods.First(mod => mod is ModHalfTime); + ModNightcore modNc = (ModNightcore)getRateMod(ModType.DifficultyIncrease, typeof(ModNightcore)); + ModDoubleTime modDt = (ModDoubleTime)getRateMod(ModType.DifficultyIncrease, typeof(ModDoubleTime)); + ModDaycore modDc = (ModDaycore)getRateMod(ModType.DifficultyReduction, typeof(ModDaycore)); + ModHalfTime modHt = (ModHalfTime)getRateMod(ModType.DifficultyReduction, typeof(ModHalfTime)); bool rateModActive = selectedMods.Value.Count(mod => mod is ModRateAdjust) > 0; bool incompatibleModActive = selectedMods.Value.Count(mod => modDt.IncompatibleMods.Count(incompatibleMod => (mod.GetType().IsSubclassOf(incompatibleMod) || mod.GetType() == incompatibleMod) && incompatibleMod != typeof(ModRateAdjust)) > 0) > 0; - double newRate = 1d + delta; + double newRate = Math.Round(1d + delta, 2); bool isPositive = delta > 0; if (incompatibleModActive) return; - onScreenDisplay?.Display(new SpeedChangeToast(config, delta)); - - if (rateModActive) + if (!rateModActive) { - ModRateAdjust mod = (ModRateAdjust)selectedMods.Value.First(mod => mod is ModRateAdjust); + onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); - // Find current active rateAdjust mod and modify speed, enable HalfTime if necessary - newRate = mod.SpeedChange.Value + delta; - - if (newRate == 1.0) - { - lastPitchState = false; - usedPitchMods = false; - - if (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) lastPitchState = true; - - if (mod is ModHalfTime htmod && htmod.AdjustPitch.Value) lastPitchState = true; - - if (mod is ModNightcore || mod is ModDaycore) usedPitchMods = true; - - //Disable RateAdjustMods - selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); - return; - } - - if (((mod is ModDoubleTime || mod is ModNightcore) && newRate < mod.SpeedChange.MinValue) - || ((mod is ModHalfTime || mod is ModDaycore) && newRate > mod.SpeedChange.MaxValue)) - { - bool adjustPitch = (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) || (mod is ModHalfTime htmod && htmod.AdjustPitch.Value); - - //Disable RateAdjustMods - selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); - - ModRateAdjust? oppositeMod = null; - - switch (mod) - { - case ModDoubleTime: - modHt.AdjustPitch.Value = adjustPitch; - oppositeMod = modHt; - break; - - case ModHalfTime: - modDt.AdjustPitch.Value = adjustPitch; - oppositeMod = modDt; - break; - - case ModNightcore: - oppositeMod = modDc; - break; - - case ModDaycore: - oppositeMod = modNc; - break; - } - - if (oppositeMod == null) return; - - oppositeMod.SpeedChange.Value = newRate; - selectedMods.Value = selectedMods.Value.Append(oppositeMod).ToList(); - return; - } - - if (newRate > mod.SpeedChange.MaxValue && (mod is ModDoubleTime || mod is ModNightcore)) - newRate = mod.SpeedChange.MaxValue; - - if (newRate < mod.SpeedChange.MinValue && (mod is ModHalfTime || mod is ModDaycore)) - newRate = mod.SpeedChange.MinValue; - - mod.SpeedChange.Value = newRate; - } - else - { // If no ModRateAdjust is active, activate one - if (isPositive) - { - if (!usedPitchMods) - { - modDt.SpeedChange.Value = newRate; - modDt.AdjustPitch.Value = lastPitchState; - selectedMods.Value = selectedMods.Value.Append(modDt).ToList(); - } - else - { - modNc.SpeedChange.Value = newRate; - selectedMods.Value = selectedMods.Value.Append(modNc).ToList(); - } - } - else - { - if (!usedPitchMods) - { - modHt.SpeedChange.Value = newRate; - modHt.AdjustPitch.Value = lastPitchState; - selectedMods.Value = selectedMods.Value.Append(modHt).ToList(); - } - else - { - modDc.SpeedChange.Value = newRate; - selectedMods.Value = selectedMods.Value.Append(modDc).ToList(); - } - } + ModRateAdjust? newMod = null; + + if (isPositive && !usedPitchMods) + newMod = modDt; + + if (isPositive && usedPitchMods) + newMod = modNc; + + if (!isPositive && !usedPitchMods) + newMod = modHt; + + if (!isPositive && usedPitchMods) + newMod = modDc; + + if (!usedPitchMods && newMod is ModDoubleTime newModDt) + newModDt.AdjustPitch.Value = lastPitchState; + + if (!usedPitchMods && newMod is ModHalfTime newModHt) + newModHt.AdjustPitch.Value = lastPitchState; + + newMod!.SpeedChange.Value = newRate; + selectedMods.Value = selectedMods.Value.Append(newMod).ToList(); + return; } + + ModRateAdjust mod = (ModRateAdjust)selectedMods.Value.First(mod => mod is ModRateAdjust); + newRate = Math.Round(mod.SpeedChange.Value + delta, 2); + + // Disable RateAdjustMods if newRate is 1 + if (newRate == 1.0) + { + lastPitchState = false; + usedPitchMods = false; + + if (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) + lastPitchState = true; + + if (mod is ModHalfTime htmod && htmod.AdjustPitch.Value) + lastPitchState = true; + + if (mod is ModNightcore || mod is ModDaycore) + usedPitchMods = true; + + //Disable RateAdjustMods + selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); + + onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); + + return; + } + + bool overMaxRateLimit = (mod is ModHalfTime || mod is ModDaycore) && newRate > mod.SpeedChange.MaxValue; + bool underMinRateLimit = (mod is ModDoubleTime || mod is ModNightcore) && newRate < mod.SpeedChange.MinValue; + + // Swap mod to opposite mod if newRate exceeds max/min speed values + if (overMaxRateLimit || underMinRateLimit) + { + bool adjustPitch = (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) || (mod is ModHalfTime htmod && htmod.AdjustPitch.Value); + + //Disable RateAdjustMods + selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); + + ModRateAdjust? oppositeMod = null; + + switch (mod) + { + case ModDoubleTime: + modHt.AdjustPitch.Value = adjustPitch; + oppositeMod = modHt; + break; + + case ModHalfTime: + modDt.AdjustPitch.Value = adjustPitch; + oppositeMod = modDt; + break; + + case ModNightcore: + oppositeMod = modDc; + break; + + case ModDaycore: + oppositeMod = modNc; + break; + } + + if (oppositeMod == null) return; + + oppositeMod.SpeedChange.Value = newRate; + selectedMods.Value = selectedMods.Value.Append(oppositeMod).ToList(); + + onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); + + return; + } + + // Cap newRate to max/min values and change rate of current active mod + if (newRate > mod.SpeedChange.MaxValue && (mod is ModDoubleTime || mod is ModNightcore)) + newRate = mod.SpeedChange.MaxValue; + + if (newRate < mod.SpeedChange.MinValue && (mod is ModHalfTime || mod is ModDaycore)) + newRate = mod.SpeedChange.MinValue; + + mod.SpeedChange.Value = newRate; + + onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); } protected override void Dispose(bool isDisposing) From 8d02ac5e219ad064e553c560d076224683ad2651 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 21:20:34 +0800 Subject: [PATCH 062/103] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index e20ac2e0b7..8fefce3a60 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 103ef50e0c..29a0350fde 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 66ceda1d674ce57395419b9157daec91128d403f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 21:27:53 +0800 Subject: [PATCH 063/103] Update focus specifications in line with framework changes --- .../Screens/Ladder/Components/LadderEditorSettings.cs | 2 +- osu.Game/Collections/ManageCollectionsDialog.cs | 2 +- osu.Game/Graphics/UserInterface/FocusedTextBox.cs | 2 +- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 2 +- osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs | 2 +- osu.Game/Overlays/AccountCreation/ScreenEntry.cs | 2 +- osu.Game/Overlays/Comments/ReplyCommentEditor.cs | 2 +- osu.Game/Overlays/Login/LoginForm.cs | 2 +- osu.Game/Overlays/Login/LoginPanel.cs | 4 ++-- osu.Game/Overlays/Login/SecondFactorAuthForm.cs | 2 +- osu.Game/Overlays/LoginOverlay.cs | 2 +- osu.Game/Overlays/Mods/AddPresetPopover.cs | 2 +- osu.Game/Overlays/Mods/EditPresetPopover.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 2 +- .../Settings/Sections/Input/KeyBindingsSubsection.cs | 2 +- osu.Game/Overlays/SettingsPanel.cs | 2 +- .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 +- .../Compose/Components/Timeline/DifficultyPointPiece.cs | 2 +- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 2 +- osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs | 2 +- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 2 +- .../Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 2 +- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 6 +++--- osu.Game/Screens/Select/FilterControl.cs | 2 +- osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs | 2 +- 26 files changed, 29 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index 9f0fa19915..08ed815253 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components editorInfo.Selected.ValueChanged += selection => { // ensure any ongoing edits are committed out to the *current* selection before changing to a new one. - GetContainingInputManager().TriggerFocusContention(null); + GetContainingFocusManager().TriggerFocusContention(null); // Required to avoid cyclic failure in BindableWithCurrent (TriggerChange called during the Current_Set process). // Arguable a framework issue but since we haven't hit it anywhere else a local workaround seems best. diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 16645d6796..ea663f45fe 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -137,7 +137,7 @@ namespace osu.Game.Collections this.ScaleTo(0.9f, exit_duration); // Ensure that textboxes commit - GetContainingInputManager()?.TriggerFocusContention(this); + GetContainingFocusManager()?.TriggerFocusContention(this); } } } diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 338f32f321..4ec93995a4 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface if (!allowImmediateFocus) return; - Scheduler.Add(() => GetContainingInputManager().ChangeFocus(this)); + Scheduler.Add(() => GetContainingFocusManager().ChangeFocus(this)); } public new void KillFocus() => base.KillFocus(); diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 8b9d35e343..863ad5a173 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override void OnFocus(FocusEvent e) { base.OnFocus(e); - GetContainingInputManager().ChangeFocus(Component); + GetContainingFocusManager().ChangeFocus(Component); } protected override OsuTextBox CreateComponent() => CreateTextBox().With(t => diff --git a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs index abd828e98f..4c16cb4951 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs @@ -85,7 +85,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Current.BindValueChanged(updateTextBoxFromSlider, true); } - public bool TakeFocus() => GetContainingInputManager().ChangeFocus(textBox); + public bool TakeFocus() => GetContainingFocusManager().ChangeFocus(textBox); private bool updatingFromTextBox; diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index f57c7d22a2..53e51e0611 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -243,7 +243,7 @@ namespace osu.Game.Overlays.AccountCreation if (nextTextBox != null) { - Schedule(() => GetContainingInputManager().ChangeFocus(nextTextBox)); + Schedule(() => GetContainingFocusManager().ChangeFocus(nextTextBox)); return true; } diff --git a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs index 8e9e82507d..caf19829ee 100644 --- a/osu.Game/Overlays/Comments/ReplyCommentEditor.cs +++ b/osu.Game/Overlays/Comments/ReplyCommentEditor.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Comments base.LoadComplete(); if (!TextBox.ReadOnly) - GetContainingInputManager().ChangeFocus(TextBox); + GetContainingFocusManager().ChangeFocus(TextBox); } protected override void OnCommit(string text) diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index 80dfca93d2..418721f371 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -150,7 +150,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); + Schedule(() => { GetContainingFocusManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); } } } diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index a8adf4ce8c..845d20ccaf 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -186,7 +186,7 @@ namespace osu.Game.Overlays.Login } if (form != null) - ScheduleAfterChildren(() => GetContainingInputManager()?.ChangeFocus(form)); + ScheduleAfterChildren(() => GetContainingFocusManager()?.ChangeFocus(form)); }); private void updateDropdownCurrent(UserStatus? status) @@ -216,7 +216,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - if (form != null) GetContainingInputManager().ChangeFocus(form); + if (form != null) GetContainingFocusManager().ChangeFocus(form); base.OnFocus(e); } } diff --git a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs index dcd3119f33..82e328c036 100644 --- a/osu.Game/Overlays/Login/SecondFactorAuthForm.cs +++ b/osu.Game/Overlays/Login/SecondFactorAuthForm.cs @@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Login protected override void OnFocus(FocusEvent e) { - Schedule(() => { GetContainingInputManager().ChangeFocus(codeTextBox); }); + Schedule(() => { GetContainingFocusManager().ChangeFocus(codeTextBox); }); } } } diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index c0aff6aae9..8dc454c0a0 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays this.FadeIn(transition_time, Easing.OutQuint); FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(panel)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(panel)); } protected override void PopOut() diff --git a/osu.Game/Overlays/Mods/AddPresetPopover.cs b/osu.Game/Overlays/Mods/AddPresetPopover.cs index b782b5d6ba..50aa5a2eb4 100644 --- a/osu.Game/Overlays/Mods/AddPresetPopover.cs +++ b/osu.Game/Overlays/Mods/AddPresetPopover.cs @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(nameTextBox)); nameTextBox.Current.BindValueChanged(s => { diff --git a/osu.Game/Overlays/Mods/EditPresetPopover.cs b/osu.Game/Overlays/Mods/EditPresetPopover.cs index 9554ba8ce2..8fa6b35162 100644 --- a/osu.Game/Overlays/Mods/EditPresetPopover.cs +++ b/osu.Game/Overlays/Mods/EditPresetPopover.cs @@ -136,7 +136,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(nameTextBox)); } public override bool OnPressed(KeyBindingPressEvent e) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 25293e8e20..54124e10c7 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -949,7 +949,7 @@ namespace osu.Game.Overlays.Mods RequestScroll?.Invoke(this); // Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action. - Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); + Scheduler.Add(() => GetContainingFocusManager().ChangeFocus(null)); return true; } diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index e82cebe9f4..3f6eeca10e 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -465,7 +465,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } if (HasFocus) - GetContainingInputManager().ChangeFocus(null); + GetContainingFocusManager().ChangeFocus(null); cancelAndClearButtons.FadeOut(300, Easing.OutQuint); cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index dd0a88bfb1..db3b56b9f0 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { var next = Children.SkipWhile(c => c != sender).Skip(1).FirstOrDefault(); if (next != null) - GetContainingInputManager().ChangeFocus(next); + GetContainingFocusManager().ChangeFocus(next); } } } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 748673035b..d5c642d24f 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -201,7 +201,7 @@ namespace osu.Game.Overlays searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) - GetContainingInputManager().ChangeFocus(null); + GetContainingFocusManager().ChangeFocus(null); } public override bool AcceptsFocus => true; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 40b97d2137..005b96bfef 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -580,7 +580,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); - GetContainingInputManager().ChangeFocus(this); + GetContainingFocusManager().ChangeFocus(this); SelectAll(); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index fc240c570b..d9084a7477 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void LoadComplete() { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(sliderVelocitySlider)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(sliderVelocitySlider)); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 28841fc9e5..5c4a9faaca 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void LoadComplete() { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(volume)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(volume)); } private static string? getCommonBank(IList[] relevantSamples) => relevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(relevantSamples.First()) : null; diff --git a/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs index 79288e2977..5abf40dda7 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Setup OnFocused?.Invoke(); base.OnFocus(e); - GetContainingInputManager().TriggerFocusContention(this); + GetContainingFocusManager().TriggerFocusContention(this); } } } diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 752f590308..660c470204 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Setup base.LoadComplete(); if (string.IsNullOrEmpty(ArtistTextBox.Current.Value)) - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(ArtistTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(ArtistTextBox)); ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox)); TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox)); diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index 26f374ba85..4f7a1bf589 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.Edit.Timing protected override void OnFocus(FocusEvent e) { base.OnFocus(e); - GetContainingInputManager().ChangeFocus(textBox); + GetContainingFocusManager().ChangeFocus(textBox); } private void updateState() diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 66bbf92e58..2f6a220c82 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -248,21 +248,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(passwordTextBox)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(passwordTextBox)); passwordTextBox.OnCommit += (_, _) => performJoin(); } private void performJoin() { lounge?.Join(room, passwordTextBox.Text, null, joinFailed); - GetContainingInputManager().TriggerFocusContention(passwordTextBox); + GetContainingFocusManager().TriggerFocusContention(passwordTextBox); } private void joinFailed(string error) => Schedule(() => { passwordTextBox.Text = string.Empty; - GetContainingInputManager().ChangeFocus(passwordTextBox); + GetContainingFocusManager().ChangeFocus(passwordTextBox); errorText.Text = error; errorText diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 73c122dda6..30eb4a8491 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -245,7 +245,7 @@ namespace osu.Game.Screens.Select searchTextBox.ReadOnly = true; searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) - GetContainingInputManager().ChangeFocus(searchTextBox); + GetContainingFocusManager().ChangeFocus(searchTextBox); } public void Activate() diff --git a/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs b/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs index f73be15a36..2827a9cb50 100644 --- a/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs +++ b/osu.Game/Screens/SelectV2/Footer/BeatmapOptionsPopover.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.SelectV2.Footer { base.LoadComplete(); - ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(this)); + ScheduleAfterChildren(() => GetContainingFocusManager().ChangeFocus(this)); beatmap.BindValueChanged(_ => Hide()); } From f7ca18b52ec4e8dbc704d58a7cfef0c301201524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 22 May 2024 15:52:57 +0200 Subject: [PATCH 064/103] Menial cleanups --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index e5567b2215..8ea1d55a0d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -105,18 +105,18 @@ namespace osu.Game.Beatmaps.Formats } /// - /// Clamp Difficulty settings to be within the normal range. + /// Ensures that all settings are within the allowed ranges. + /// See also: https://github.com/peppy/osu-stable-reference/blob/0e425c0d525ef21353c8293c235cc0621d28338b/osu!/GameplayElements/Beatmaps/Beatmap.cs#L567-L614 /// private void applyDifficultyRestrictions(BeatmapDifficulty difficulty) { difficulty.DrainRate = Math.Clamp(difficulty.DrainRate, 0, 10); - //If the mode is not Mania, clamp circle size to [0,10] - if (beatmap.BeatmapInfo.Ruleset.OnlineID != 3) - difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 0, 10); - //If it is Mania, it must be within [1,18] - copying what stable does https://github.com/ppy/osu/pull/28200#discussion_r1609522988 - //The lower bound should be 4, but there are ranked maps that are lower than this. - else - difficulty.CircleSize = Math.Clamp(difficulty.CircleSize, 1, 18); + + // mania uses "circle size" for key count, thus different allowable range + difficulty.CircleSize = beatmap.BeatmapInfo.Ruleset.OnlineID != 3 + ? Math.Clamp(difficulty.CircleSize, 0, 10) + : Math.Clamp(difficulty.CircleSize, 1, 18); + difficulty.OverallDifficulty = Math.Clamp(difficulty.OverallDifficulty, 0, 10); difficulty.ApproachRate = Math.Clamp(difficulty.ApproachRate, 0, 10); From 093be3d723ef18bcb3e7eaac1642c0006b62f922 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 21:55:53 +0800 Subject: [PATCH 065/103] Cast remaining test usages to `IFocusManager` to remove obsolete notice --- .../TestSceneHitObjectSampleAdjustments.cs | 3 ++- .../Editing/TestSceneLabelledTimeSignature.cs | 9 +++++---- .../UserInterface/TestSceneModSelectOverlay.cs | 3 ++- .../TestSceneSliderWithTextBoxInput.cs | 17 +++++++++-------- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 1415ff4b0f..0e12ed68e4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Collections.Generic; using Humanizer; using NUnit.Framework; +using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -396,7 +397,7 @@ namespace osu.Game.Tests.Visual.Editing textBox.Current.Value = bank; // force a commit via keyboard. // this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit. - InputManager.ChangeFocus(textBox); + ((IFocusManager)InputManager).ChangeFocus(textBox); InputManager.Key(Key.Enter); }); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs index e91596b872..3d7d0797d4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics.UserInterface; @@ -62,12 +63,12 @@ namespace osu.Game.Tests.Visual.Editing createLabelledTimeSignature(TimeSignature.SimpleQuadruple); AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); - AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox)); + AddStep("focus text box", () => ((IFocusManager)InputManager).ChangeFocus(numeratorTextBox)); AddStep("set numerator to 7", () => numeratorTextBox.Current.Value = "7"); AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); - AddStep("drop focus", () => InputManager.ChangeFocus(null)); + AddStep("drop focus", () => ((IFocusManager)InputManager).ChangeFocus(null)); AddAssert("current is 7/4", () => timeSignature.Current.Value.Equals(new TimeSignature(7))); } @@ -77,12 +78,12 @@ namespace osu.Game.Tests.Visual.Editing createLabelledTimeSignature(TimeSignature.SimpleQuadruple); AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); - AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox)); + AddStep("focus text box", () => ((IFocusManager)InputManager).ChangeFocus(numeratorTextBox)); AddStep("set numerator to 0", () => numeratorTextBox.Current.Value = "0"); AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); - AddStep("drop focus", () => InputManager.ChangeFocus(null)); + AddStep("drop focus", () => ((IFocusManager)InputManager).ChangeFocus(null)); AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); AddAssert("numerator is 4", () => numeratorTextBox.Current.Value == "4"); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 8ddbd84890..a1452ddb31 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Framework.Utils; @@ -623,7 +624,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press tab", () => InputManager.Key(Key.Tab)); AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); - AddStep("unfocus search text box externally", () => InputManager.ChangeFocus(null)); + AddStep("unfocus search text box externally", () => ((IFocusManager)InputManager).ChangeFocus(null)); AddStep("press tab", () => InputManager.Key(Key.Tab)); AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs index d23fcebae3..06b9623508 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSliderWithTextBoxInput.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; @@ -42,7 +43,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("set instantaneous to false", () => sliderWithTextBoxInput.Instantaneous = false); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("change text", () => textBox.Text = "3"); AddAssert("slider not moved", () => slider.Current.Value, () => Is.Zero); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.Zero); @@ -61,7 +62,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5")); AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("set text to invalid", () => textBox.Text = "garbage"); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); @@ -71,12 +72,12 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("set text to invalid", () => textBox.Text = "garbage"); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("lose focus", () => InputManager.ChangeFocus(null)); + AddStep("lose focus", () => ((IFocusManager)InputManager).ChangeFocus(null)); AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5")); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); @@ -87,7 +88,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("set instantaneous to true", () => sliderWithTextBoxInput.Instantaneous = true); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("change text", () => textBox.Text = "3"); AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3)); AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3)); @@ -106,7 +107,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("-5")); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("set text to invalid", () => textBox.Text = "garbage"); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); @@ -116,12 +117,12 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("focus textbox", () => InputManager.ChangeFocus(textBox)); + AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox)); AddStep("set text to invalid", () => textBox.Text = "garbage"); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); - AddStep("lose focus", () => InputManager.ChangeFocus(null)); + AddStep("lose focus", () => ((IFocusManager)InputManager).ChangeFocus(null)); AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5")); AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5)); AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5)); From 0d13848421de5198fc438fd7f4a2420c265dc31b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 May 2024 00:21:19 +0900 Subject: [PATCH 066/103] Add whitespace to appease R# --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index e6daba2016..a4cd888823 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -1206,6 +1206,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.SliderTickRate, Is.EqualTo(8)); } } + [Test] public void TestManiaBeatmapDifficultyCircleSizeClamp() { From 73cb363eba003fd329477ab778f28610d0b9e596 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 May 2024 23:25:59 +0800 Subject: [PATCH 067/103] Make some more methods static --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 8ea1d55a0d..c2f4097889 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -85,7 +85,7 @@ namespace osu.Game.Beatmaps.Formats base.ParseStreamInto(stream, beatmap); - applyDifficultyRestrictions(beatmap.Difficulty); + applyDifficultyRestrictions(beatmap.Difficulty, beatmap); flushPendingPoints(); @@ -108,7 +108,7 @@ namespace osu.Game.Beatmaps.Formats /// Ensures that all settings are within the allowed ranges. /// See also: https://github.com/peppy/osu-stable-reference/blob/0e425c0d525ef21353c8293c235cc0621d28338b/osu!/GameplayElements/Beatmaps/Beatmap.cs#L567-L614 /// - private void applyDifficultyRestrictions(BeatmapDifficulty difficulty) + private static void applyDifficultyRestrictions(BeatmapDifficulty difficulty, Beatmap beatmap) { difficulty.DrainRate = Math.Clamp(difficulty.DrainRate, 0, 10); @@ -127,7 +127,7 @@ namespace osu.Game.Beatmaps.Formats /// /// Processes the beatmap such that a new combo is started the first hitobject following each break. /// - private void postProcessBreaks(Beatmap beatmap) + private static void postProcessBreaks(Beatmap beatmap) { int currentBreak = 0; bool forceNewCombo = false; @@ -183,7 +183,7 @@ namespace osu.Game.Beatmaps.Formats /// This method's intention is to restore those legacy defaults. /// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29 /// - private void applyLegacyDefaults(BeatmapInfo beatmapInfo) + private static void applyLegacyDefaults(BeatmapInfo beatmapInfo) { beatmapInfo.WidescreenStoryboard = false; beatmapInfo.SamplesMatchPlaybackRate = false; From c3a2a1361d045dbfd7c409fcd003ddc33dd54164 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Wed, 22 May 2024 10:39:42 +0200 Subject: [PATCH 068/103] SliderBody's Size getter updates size to the body/path's Size --- .../Sliders/Components/SliderBodyPiece.cs | 12 +++++++++++- .../Skinning/Default/ManualSliderBody.cs | 13 ++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 075e9e6aa1..14d72a2d36 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -61,10 +61,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components body.SetVertices(vertices); } - Size = body.Size; OriginPosition = body.PathOffset; } + public override Vector2 Size + { + get + { + if (base.Size != body.Size) + Size = body.Size; + return base.Size; + } + set => base.Size = value; + } + public void RecyclePath() => body.RecyclePath(); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos); diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs index d171f56f40..99d954059c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs @@ -11,10 +11,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default /// public partial class ManualSliderBody : SliderBody { - public new void SetVertices(IReadOnlyList vertices) + public new void SetVertices(IReadOnlyList vertices) => base.SetVertices(vertices); + + public override Vector2 Size { - base.SetVertices(vertices); - Size = Path.Size; + get + { + if (base.Size != Path.Size) + Size = Path.Size; + return base.Size; + } + set => base.Size = value; } } } From fd9f8bd3e098ed85b84c562afd884e1191018d25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 01:20:58 +0800 Subject: [PATCH 069/103] Update framework --- osu.Android.props | 2 +- osu.Game/Collections/CollectionDropdown.cs | 7 ++++++- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8fefce3a60..1f241c6db5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 29a0350fde..eba9abd3b8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From f85a1339d9e8b0a185aa48d38912ba971556bd73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 14:12:27 +0800 Subject: [PATCH 070/103] Unload daily challenge background less aggressively --- .../UpdateableOnlineBeatmapSetCover.cs | 17 +++++++++++------ osu.Game/Screens/Menu/DailyChallengeButton.cs | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs index 2a6b6f90e3..5bce472613 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableOnlineBeatmapSetCover.cs @@ -27,8 +27,17 @@ namespace osu.Game.Beatmaps.Drawables set => base.Masking = value; } - public UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover) + protected override double LoadDelay { get; } + + private readonly double timeBeforeUnload; + + protected override double TransformDuration => 400; + + public UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover, double timeBeforeLoad = 500, double timeBeforeUnload = 1000) { + LoadDelay = timeBeforeLoad; + this.timeBeforeUnload = timeBeforeUnload; + this.coverType = coverType; InternalChild = new Box @@ -38,12 +47,8 @@ namespace osu.Game.Beatmaps.Drawables }; } - protected override double LoadDelay => 500; - - protected override double TransformDuration => 400; - protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) - => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad) + => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, timeBeforeUnload) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 907fd04148..28b3747fbf 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Menu { Children = new Drawable[] { - cover = new UpdateableOnlineBeatmapSetCover + cover = new UpdateableOnlineBeatmapSetCover(timeBeforeLoad: 0, timeBeforeUnload: 600_000) { RelativeSizeAxes = Axes.Y, Anchor = Anchor.Centre, From 84fe3699f641b3489eda5730a2f94ea2317e53ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 14:31:20 +0800 Subject: [PATCH 071/103] Reorder test steps to work better on multiple runs --- .../UserInterface/TestSceneMainMenuButton.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 921e28d607..5914898cb1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -39,12 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDailyChallengeButton() { - AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - ButtonSystemState = ButtonSystemState.TopLevel, - }); + AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); AddStep("set up API", () => dummyAPI.HandleRequest = req => { @@ -72,12 +67,17 @@ namespace osu.Game.Tests.Visual.UserInterface } }); + AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ButtonSystemState = ButtonSystemState.TopLevel, + }); + AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = 1234, })); - - AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); } } } From 88a2f74326183605a7130c3a74cdd09ebb6a9b36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 16:00:23 +0800 Subject: [PATCH 072/103] Adjust animation --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 28b3747fbf..3e514d0c1f 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics; @@ -72,8 +73,7 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Y, Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = -0.5f, + RelativePositionAxes = Axes.Both, }, new Box { @@ -100,18 +100,25 @@ namespace osu.Game.Screens.Menu base.LoadComplete(); info.BindValueChanged(updateDisplay, true); - FinishTransforms(true); - - cover.MoveToX(-0.5f, 10000, Easing.InOutSine) - .Then().MoveToX(0.5f, 10000, Easing.InOutSine) - .Loop(); } protected override void Update() { base.Update(); - cover.Width = 2 * background.DrawWidth; + if (cover.LatestTransformEndTime == Time.Current) + { + const double duration = 3000; + + float scale = 1 + RNG.NextSingle(); + + cover.ScaleTo(scale, duration, Easing.InOutSine) + .RotateTo(RNG.NextSingle(-4, 4) * (scale - 1), duration, Easing.InOutSine) + .MoveTo(new Vector2( + RNG.NextSingle(-0.5f, 0.5f) * (scale - 1), + RNG.NextSingle(-0.5f, 0.5f) * (scale - 1) + ), duration, Easing.InOutSine); + } } private void updateDisplay(ValueChangedEvent info) From a3639e0ce3c8cec679432624f571c71443c84978 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 17:19:11 +0800 Subject: [PATCH 073/103] Remove unused field --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 3e514d0c1f..7dbd90eeba 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -38,7 +38,6 @@ namespace osu.Game.Screens.Menu private UpdateableOnlineBeatmapSetCover cover = null!; private IBindable info = null!; - private BufferedContainer background = null!; [Resolved] private IAPIProvider api { get; set; } = null!; @@ -64,7 +63,7 @@ namespace osu.Game.Screens.Menu }); } - protected override Drawable CreateBackground(Colour4 accentColour) => background = new BufferedContainer + protected override Drawable CreateBackground(Colour4 accentColour) => new BufferedContainer { Children = new Drawable[] { From 357e55ae1f9d4d19fc7e28f508a73d2ca05e8751 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 May 2024 17:39:59 +0800 Subject: [PATCH 074/103] Make gradient layer a bit more dynamic --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 7dbd90eeba..c365994736 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -39,6 +39,8 @@ namespace osu.Game.Screens.Menu private UpdateableOnlineBeatmapSetCover cover = null!; private IBindable info = null!; + private Box gradientLayer = null!; + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -74,10 +76,10 @@ namespace osu.Game.Screens.Menu Origin = Anchor.Centre, RelativePositionAxes = Axes.Both, }, - new Box + gradientLayer = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(accentColour.Opacity(0), accentColour), + Colour = ColourInfo.GradientVertical(accentColour.Opacity(0.2f), accentColour), Blending = BlendingParameters.Additive, }, new Box @@ -117,6 +119,10 @@ namespace osu.Game.Screens.Menu RNG.NextSingle(-0.5f, 0.5f) * (scale - 1), RNG.NextSingle(-0.5f, 0.5f) * (scale - 1) ), duration, Easing.InOutSine); + + gradientLayer.FadeIn(duration / 2) + .Then() + .FadeOut(duration / 2); } } From bfa23ec7a47ed41be7ffc40e46b28fd5fff2b648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 11:46:48 +0200 Subject: [PATCH 075/103] Fix main menu button animation not playing on initial show --- osu.Game/Screens/Menu/MainMenuButton.cs | 69 ++++++++++++++----------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index fe8fb91766..29a661066c 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -179,7 +179,6 @@ namespace osu.Game.Screens.Menu { base.LoadComplete(); - background.Size = initialSize; background.Shear = new Vector2(ButtonSystem.WEDGE_WIDTH / initialSize.Y, 0); // for whatever reason, attempting to size the background "just in time" to cover the visible width @@ -189,6 +188,9 @@ namespace osu.Game.Screens.Menu // (which can exceed the [0;1] range during interpolation). backgroundContent.Width = 2 * initialSize.X; backgroundContent.Shear = -background.Shear; + + animateState(); + FinishTransforms(true); } private bool rightward; @@ -318,41 +320,46 @@ namespace osu.Game.Screens.Menu state = value; - switch (state) - { - case ButtonState.Contracted: - switch (ContractStyle) - { - default: - background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 500, Easing.OutExpo); - this.FadeOut(500); - break; - - case 1: - background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 400, Easing.InSine); - this.FadeOut(800); - break; - } - - break; - - case ButtonState.Expanded: - const int expand_duration = 500; - background.ResizeTo(initialSize, expand_duration, Easing.OutExpo); - this.FadeIn(expand_duration / 6f); - break; - - case ButtonState.Exploded: - const int explode_duration = 200; - background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(2, 1)), explode_duration, Easing.OutExpo); - this.FadeOut(explode_duration / 4f * 3); - break; - } + animateState(); StateChanged?.Invoke(State); } } + private void animateState() + { + switch (state) + { + case ButtonState.Contracted: + switch (ContractStyle) + { + default: + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 500, Easing.OutExpo); + this.FadeOut(500); + break; + + case 1: + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(0, 1)), 400, Easing.InSine); + this.FadeOut(800); + break; + } + + break; + + case ButtonState.Expanded: + const int expand_duration = 500; + background.ResizeTo(initialSize, expand_duration, Easing.OutExpo); + this.FadeIn(expand_duration / 6f); + break; + + case ButtonState.Exploded: + const int explode_duration = 200; + background.ResizeTo(Vector2.Multiply(initialSize, new Vector2(2, 1)), explode_duration, Easing.OutExpo); + this.FadeOut(explode_duration / 4f * 3); + break; + } + } + private ButtonSystemState buttonSystemState; public ButtonSystemState ButtonSystemState From 3411ebc4af5711c275e21b843b264bc7d96f864b Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 23 May 2024 12:50:06 +0200 Subject: [PATCH 076/103] Move `SDL3BatteryInfo` to separate file --- osu.Desktop/OsuGameDesktop.cs | 20 -------------------- osu.Desktop/SDL3BatteryInfo.cs | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 osu.Desktop/SDL3BatteryInfo.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index e8783c997a..b1e1a8f118 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -22,7 +22,6 @@ using osu.Game.IPC; using osu.Game.Online.Multiplayer; using osu.Game.Performance; using osu.Game.Utils; -using SDL; namespace osu.Desktop { @@ -169,24 +168,5 @@ namespace osu.Desktop osuSchemeLinkIPCChannel?.Dispose(); archiveImportIPCChannel?.Dispose(); } - - private unsafe class SDL3BatteryInfo : BatteryInfo - { - public override double? ChargeLevel - { - get - { - int percentage; - SDL3.SDL_GetPowerInfo(null, &percentage); - - if (percentage == -1) - return null; - - return percentage / 100.0; - } - } - - public override bool OnBattery => SDL3.SDL_GetPowerInfo(null, null) == SDL_PowerState.SDL_POWERSTATE_ON_BATTERY; - } } } diff --git a/osu.Desktop/SDL3BatteryInfo.cs b/osu.Desktop/SDL3BatteryInfo.cs new file mode 100644 index 0000000000..89084b5a15 --- /dev/null +++ b/osu.Desktop/SDL3BatteryInfo.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Utils; +using SDL; + +namespace osu.Desktop +{ + internal unsafe class SDL3BatteryInfo : BatteryInfo + { + public override double? ChargeLevel + { + get + { + int percentage; + SDL3.SDL_GetPowerInfo(null, &percentage); + + if (percentage == -1) + return null; + + return percentage / 100.0; + } + } + + public override bool OnBattery => SDL3.SDL_GetPowerInfo(null, null) == SDL_PowerState.SDL_POWERSTATE_ON_BATTERY; + } +} From 45ed86f46cdf413d1acaa189f0faea7a10b0ad44 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 23 May 2024 12:53:33 +0200 Subject: [PATCH 077/103] Add back `SDL2BatteryInfo` --- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Desktop/SDL2BatteryInfo.cs | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 osu.Desktop/SDL2BatteryInfo.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index b1e1a8f118..3e06dad4c5 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -160,7 +160,7 @@ namespace osu.Desktop host.Window.Title = Name; } - protected override BatteryInfo CreateBatteryInfo() => new SDL3BatteryInfo(); + protected override BatteryInfo CreateBatteryInfo() => FrameworkEnvironment.UseSDL3 ? new SDL3BatteryInfo() : new SDL2BatteryInfo(); protected override void Dispose(bool isDisposing) { diff --git a/osu.Desktop/SDL2BatteryInfo.cs b/osu.Desktop/SDL2BatteryInfo.cs new file mode 100644 index 0000000000..9ca2dc3a5c --- /dev/null +++ b/osu.Desktop/SDL2BatteryInfo.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Utils; + +namespace osu.Desktop +{ + internal class SDL2BatteryInfo : BatteryInfo + { + public override double? ChargeLevel + { + get + { + SDL2.SDL.SDL_GetPowerInfo(out _, out int percentage); + + if (percentage == -1) + return null; + + return percentage / 100.0; + } + } + + public override bool OnBattery => SDL2.SDL.SDL_GetPowerInfo(out _, out _) == SDL2.SDL.SDL_PowerState.SDL_POWERSTATE_ON_BATTERY; + } +} From ccf8473aae70b4898c7289c2601a07e418e23257 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 23 May 2024 13:00:18 +0200 Subject: [PATCH 078/103] Use appropriate `SDL_ShowSimpleMessageBox` --- osu.Desktop/Program.cs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 23e56cdce9..0d8de8dce7 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -28,6 +28,14 @@ namespace osu.Desktop private static LegacyTcpIpcProvider? legacyIpc; + private static unsafe void showMessageBox(string title, string message) + { + if (FrameworkEnvironment.UseSDL3) + SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, title, message, null); + else + SDL2.SDL.SDL_ShowSimpleMessageBox(SDL2.SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, title, message, IntPtr.Zero); + } + [STAThread] public static void Main(string[] args) { @@ -52,19 +60,15 @@ namespace osu.Desktop // See https://www.mongodb.com/docs/realm/sdk/dotnet/compatibility/ if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)) { - unsafe - { - // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider - // disabling it ourselves. - // We could also better detect compatibility mode if required: - // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 - SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, - "Your operating system is too old to run osu!"u8, - "This version of osu! requires at least Windows 8.1 to run.\n"u8 - + "Please upgrade your operating system or consider using an older version of osu!.\n\n"u8 - + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"u8, null); - return; - } + // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider + // disabling it ourselves. + // We could also better detect compatibility mode if required: + // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 + showMessageBox("Your operating system is too old to run osu!", + "This version of osu! requires at least Windows 8.1 to run.\n" + + "Please upgrade your operating system or consider using an older version of osu!.\n\n" + + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"); + return; } setupSquirrel(); From 070668c96f4494958b0e1b4464dd4059e9ba0ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 13:55:11 +0200 Subject: [PATCH 079/103] Use `ShiftPressed` instead of explicitly checking both physical keys Not only is this simpler, but it also is more correct (for explanation why, try holding both shift keys while dragging, and just releasing one of them - the previous code would briefly turn aspect ratio off). --- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index c188d23a58..12787a1c55 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -51,9 +51,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnKeyDown(KeyDownEvent e) { - if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) + if (IsDragged) { - applyScale(shouldLockAspectRatio: true); + applyScale(shouldLockAspectRatio: e.ShiftPressed); return true; } @@ -64,8 +64,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.OnKeyUp(e); - if (IsDragged && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) - applyScale(shouldLockAspectRatio: false); + if (IsDragged) + applyScale(shouldLockAspectRatio: e.ShiftPressed); } protected override void OnDragEnd(DragEndEvent e) From 9e86a08405db8a88fec2066975843b13ae831eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 14:07:43 +0200 Subject: [PATCH 080/103] Simplify scale origin computation --- .../Components/SelectionBoxScaleHandle.cs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 12787a1c55..352a4985d6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Utils; @@ -102,21 +103,8 @@ namespace osu.Game.Screens.Edit.Compose.Components ? new Vector2((rawScale.X + rawScale.Y) * 0.5f) : rawScale; - scaleHandler!.Update(newScale, getOriginPosition(), getAdjustAxis()); - } - - private Vector2 getOriginPosition() - { - var quad = scaleHandler!.OriginalSurroundingQuad!.Value; - Vector2 origin = quad.TopLeft; - - if ((originalAnchor & Anchor.x0) > 0) - origin.X += quad.Width; - - if ((originalAnchor & Anchor.y0) > 0) - origin.Y += quad.Height; - - return origin; + var scaleOrigin = originalAnchor.Opposite().PositionOnQuad(scaleHandler!.OriginalSurroundingQuad!.Value); + scaleHandler!.Update(newScale, scaleOrigin, getAdjustAxis()); } private Axes getAdjustAxis() From abca62d5f0e545cd167305d292f09944a6397cd1 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 23 May 2024 14:24:42 +0200 Subject: [PATCH 081/103] Revert "Use appropriate `SDL_ShowSimpleMessageBox`" This reverts commit ccf8473aae70b4898c7289c2601a07e418e23257. --- osu.Desktop/Program.cs | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 0d8de8dce7..23e56cdce9 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -28,14 +28,6 @@ namespace osu.Desktop private static LegacyTcpIpcProvider? legacyIpc; - private static unsafe void showMessageBox(string title, string message) - { - if (FrameworkEnvironment.UseSDL3) - SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, title, message, null); - else - SDL2.SDL.SDL_ShowSimpleMessageBox(SDL2.SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, title, message, IntPtr.Zero); - } - [STAThread] public static void Main(string[] args) { @@ -60,15 +52,19 @@ namespace osu.Desktop // See https://www.mongodb.com/docs/realm/sdk/dotnet/compatibility/ if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)) { - // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider - // disabling it ourselves. - // We could also better detect compatibility mode if required: - // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 - showMessageBox("Your operating system is too old to run osu!", - "This version of osu! requires at least Windows 8.1 to run.\n" - + "Please upgrade your operating system or consider using an older version of osu!.\n\n" - + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"); - return; + unsafe + { + // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider + // disabling it ourselves. + // We could also better detect compatibility mode if required: + // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 + SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, + "Your operating system is too old to run osu!"u8, + "This version of osu! requires at least Windows 8.1 to run.\n"u8 + + "Please upgrade your operating system or consider using an older version of osu!.\n\n"u8 + + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"u8, null); + return; + } } setupSquirrel(); From f17f70dca7eeb60690d83435d0bdba399fdd38bd Mon Sep 17 00:00:00 2001 From: Aurelian Date: Thu, 23 May 2024 14:36:49 +0200 Subject: [PATCH 082/103] Changed Size to be handled by AutoSizeAxes --- .../Sliders/Components/SliderBodyPiece.cs | 13 ++----------- .../Skinning/Default/ManualSliderBody.cs | 13 +++++-------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 14d72a2d36..44c754d8f5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning.Default; @@ -41,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void load(OsuColour colours) { body.BorderColour = colours.Yellow; + AutoSizeAxes = Axes.Both; } private int? lastVersion; @@ -64,17 +66,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components OriginPosition = body.PathOffset; } - public override Vector2 Size - { - get - { - if (base.Size != body.Size) - Size = body.Size; - return base.Size; - } - set => base.Size = value; - } - public void RecyclePath() => body.RecyclePath(); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos); diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs index 99d954059c..2fc18da254 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Default @@ -13,15 +15,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public new void SetVertices(IReadOnlyList vertices) => base.SetVertices(vertices); - public override Vector2 Size + [BackgroundDependencyLoader] + private void load() { - get - { - if (base.Size != Path.Size) - Size = Path.Size; - return base.Size; - } - set => base.Size = value; + AutoSizeAxes = Axes.Both; } } } From ac5c031a3a077cc5072b7f6b331623aa91681d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 14:18:29 +0200 Subject: [PATCH 083/103] Simplify original state management in skin selection scale handler --- .../SkinEditor/SkinSelectionScaleHandler.cs | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index 0c2ee6aae3..08df8df7e2 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -53,13 +53,8 @@ namespace osu.Game.Overlays.SkinEditor private bool allSelectedSupportManualSizing(Axes axis) => selectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false); - private Drawable[]? objectsInScale; - + private Dictionary? objectsInScale; private Vector2? defaultOrigin; - private Dictionary? originalWidths; - private Dictionary? originalHeights; - private Dictionary? originalScales; - private Dictionary? originalPositions; private bool isFlippedX; private bool isFlippedY; @@ -71,12 +66,8 @@ namespace osu.Game.Overlays.SkinEditor changeHandler?.BeginChange(); - objectsInScale = selectedItems.Cast().ToArray(); - originalWidths = objectsInScale.ToDictionary(d => d, d => d.Width); - originalHeights = objectsInScale.ToDictionary(d => d, d => d.Height); - originalScales = objectsInScale.ToDictionary(d => d, d => d.Scale); - originalPositions = objectsInScale.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition)); - OriginalSurroundingQuad = ToLocalSpace(GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray()))); + objectsInScale = selectedItems.Cast().ToDictionary(d => d, d => new OriginalDrawableState(d)); + OriginalSurroundingQuad = ToLocalSpace(GeometryUtils.GetSurroundingQuad(objectsInScale.SelectMany(d => d.Key.ScreenSpaceDrawQuad.GetVertices().ToArray()))); defaultOrigin = OriginalSurroundingQuad.Value.Centre; isFlippedX = false; @@ -88,7 +79,7 @@ namespace osu.Game.Overlays.SkinEditor if (objectsInScale == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); - Debug.Assert(originalWidths != null && originalHeights != null && originalScales != null && originalPositions != null && defaultOrigin != null && OriginalSurroundingQuad != null); + Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null); var actualOrigin = ToScreenSpace(origin ?? defaultOrigin.Value); @@ -132,9 +123,9 @@ namespace osu.Game.Overlays.SkinEditor return; } - foreach (var b in objectsInScale) + foreach (var (b, originalState) in objectsInScale) { - UpdatePosition(b, GeometryUtils.GetScaledPosition(scale, actualOrigin, originalPositions[b])); + UpdatePosition(b, GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.ScreenSpaceOriginPosition)); var currentScale = scale; if (Precision.AlmostEquals(MathF.Abs(b.Rotation) % 180, 90)) @@ -143,15 +134,15 @@ namespace osu.Game.Overlays.SkinEditor switch (adjustAxis) { case Axes.X: - b.Width = MathF.Abs(originalWidths[b] * currentScale.X); + b.Width = MathF.Abs(originalState.Width * currentScale.X); break; case Axes.Y: - b.Height = MathF.Abs(originalHeights[b] * currentScale.Y); + b.Height = MathF.Abs(originalState.Height * currentScale.Y); break; case Axes.Both: - b.Scale = originalScales[b] * currentScale; + b.Scale = originalState.Scale * currentScale; break; } } @@ -165,11 +156,23 @@ namespace osu.Game.Overlays.SkinEditor changeHandler?.EndChange(); objectsInScale = null; - originalPositions = null; - originalWidths = null; - originalHeights = null; - originalScales = null; defaultOrigin = null; } + + private struct OriginalDrawableState + { + public float Width { get; } + public float Height { get; } + public Vector2 Scale { get; } + public Vector2 ScreenSpaceOriginPosition { get; } + + public OriginalDrawableState(Drawable drawable) + { + Width = drawable.Width; + Height = drawable.Height; + Scale = drawable.Scale; + ScreenSpaceOriginPosition = drawable.ToScreenSpace(drawable.OriginPosition); + } + } } } From f7bcccacb03358667fa40d0603bb01d3233bf591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 14:41:59 +0200 Subject: [PATCH 084/103] Simplify original state management in osu! scale handler --- .../Edit/OsuSelectionScaleHandler.cs | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 7d5240fb69..b0299c5668 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -55,12 +55,8 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; } - private OsuHitObject[]? objectsInScale; - + private Dictionary? objectsInScale; private Vector2? defaultOrigin; - private Dictionary? originalPositions; - private Dictionary? originalPathControlPointPositions; - private Dictionary? originalPathControlPointTypes; public override void Begin() { @@ -69,18 +65,11 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); - objectsInScale = selectedMovableObjects.ToArray(); - OriginalSurroundingQuad = objectsInScale.Length == 1 && objectsInScale.First() is Slider slider + objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho)); + OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)) - : GeometryUtils.GetSurroundingQuad(objectsInScale); + : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); defaultOrigin = OriginalSurroundingQuad.Value.Centre; - originalPositions = objectsInScale.ToDictionary(obj => obj, obj => obj.Position); - originalPathControlPointPositions = objectsInScale.OfType().ToDictionary( - obj => obj, - obj => obj.Path.ControlPoints.Select(point => point.Position).ToArray()); - originalPathControlPointTypes = objectsInScale.OfType().ToDictionary( - obj => obj, - obj => obj.Path.ControlPoints.Select(p => p.Type).ToArray()); } public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) @@ -88,22 +77,26 @@ namespace osu.Game.Rulesets.Osu.Edit if (objectsInScale == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); - Debug.Assert(originalPositions != null && originalPathControlPointPositions != null && defaultOrigin != null && originalPathControlPointTypes != null && OriginalSurroundingQuad != null); + Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null); Vector2 actualOrigin = origin ?? defaultOrigin.Value; // for the time being, allow resizing of slider paths only if the slider is // the only hit object selected. with a group selection, it's likely the user // is not looking to change the duration of the slider but expand the whole pattern. - if (objectsInScale.Length == 1 && objectsInScale.First() is Slider slider) - scaleSlider(slider, scale, originalPathControlPointPositions[slider], originalPathControlPointTypes[slider]); + if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider) + { + var originalInfo = objectsInScale[slider]; + Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null); + scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes); + } else { scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale); - foreach (var ho in objectsInScale) + foreach (var (ho, originalState) in objectsInScale) { - ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalPositions[ho]); + ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.Position); } } @@ -119,9 +112,6 @@ namespace osu.Game.Rulesets.Osu.Edit objectsInScale = null; OriginalSurroundingQuad = null; - originalPositions = null; - originalPathControlPointPositions = null; - originalPathControlPointTypes = null; defaultOrigin = null; } @@ -193,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void moveSelectionInBounds() { - Quad quad = GeometryUtils.GetSurroundingQuad(objectsInScale!); + Quad quad = GeometryUtils.GetSurroundingQuad(objectsInScale!.Keys); Vector2 delta = Vector2.Zero; @@ -207,8 +197,22 @@ namespace osu.Game.Rulesets.Osu.Edit if (quad.BottomRight.Y > OsuPlayfield.BASE_SIZE.Y) delta.Y -= quad.BottomRight.Y - OsuPlayfield.BASE_SIZE.Y; - foreach (var h in objectsInScale!) + foreach (var (h, _) in objectsInScale!) h.Position += delta; } + + private struct OriginalHitObjectState + { + public Vector2 Position { get; } + public Vector2[]? PathControlPointPositions { get; } + public PathType?[]? PathControlPointTypes { get; } + + public OriginalHitObjectState(OsuHitObject hitObject) + { + Position = hitObject.Position; + PathControlPointPositions = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Position).ToArray(); + PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray(); + } + } } } From 3e34b2d37ed895f9eb707be9921964795c2e750e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 14:56:08 +0200 Subject: [PATCH 085/103] Bring back clamping in osu! scale handler Being able to flip doesn't really feel all that good and `master` was already clamping, so let's just bring that back for now. Flipping can be reconsidered in a follow-up if it actually can be made to behave well. --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index b0299c5668..75b404684f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; +using osu.Framework.Logging; using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -120,6 +121,8 @@ namespace osu.Game.Rulesets.Osu.Edit private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes) { + scale = Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); + // Maintain the path types in case they were defaulted to bezier at some point during scaling for (int i = 0; i < slider.Path.ControlPoints.Count; i++) { @@ -178,7 +181,8 @@ namespace osu.Game.Rulesets.Osu.Edit if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0)) scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); - return scale; + Logger.Log($"scale = {scale}"); + return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); } private void moveSelectionInBounds() From 128029e2af5bd0d23db7935761807487ded1eb15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 15:08:43 +0200 Subject: [PATCH 086/103] Fix aspect ratio lock applying when shift pressed on a non-corner anchor It doesn't make sense and it wasn't doing the right thing. --- .../Edit/Compose/Components/SelectionBoxScaleHandle.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs index 352a4985d6..eca0c08ba1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxScaleHandle.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Utils; @@ -47,14 +48,14 @@ namespace osu.Game.Screens.Edit.Compose.Components rawScale = convertDragEventToScaleMultiplier(e); - applyScale(shouldLockAspectRatio: e.ShiftPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed); } protected override bool OnKeyDown(KeyDownEvent e) { if (IsDragged) { - applyScale(shouldLockAspectRatio: e.ShiftPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed); return true; } @@ -66,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components base.OnKeyUp(e); if (IsDragged) - applyScale(shouldLockAspectRatio: e.ShiftPressed); + applyScale(shouldLockAspectRatio: isCornerAnchor(originalAnchor) && e.ShiftPressed); } protected override void OnDragEnd(DragEndEvent e) @@ -123,5 +124,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return Axes.Both; } } + + private bool isCornerAnchor(Anchor anchor) => !anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1); } } From d8ba95f87712a7e407a8e28c9c41cc8035b9826a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 May 2024 15:13:42 +0200 Subject: [PATCH 087/103] Remove leftover log whooops. --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 75b404684f..af03c4d925 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; -using osu.Framework.Logging; using osu.Framework.Utils; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -181,7 +180,6 @@ namespace osu.Game.Rulesets.Osu.Edit if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0)) scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); - Logger.Log($"scale = {scale}"); return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); } From b1c7afd75b273c1f7f12738e1a48db7c2c956002 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 May 2024 23:45:04 +0900 Subject: [PATCH 088/103] Move to ctor --- .../Blueprints/Sliders/Components/SliderBodyPiece.cs | 11 ++++++----- .../Skinning/Default/ManualSliderBody.cs | 8 +++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 44c754d8f5..12626a77ed 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -28,21 +28,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public SliderBodyPiece() { - InternalChild = body = new ManualSliderBody - { - AccentColour = Color4.Transparent - }; + AutoSizeAxes = Axes.Both; // SliderSelectionBlueprint relies on calling ReceivePositionalInputAt on this drawable to determine whether selection should occur. // Without AlwaysPresent, a movement in a parent container (ie. the editor composer area resizing) could cause incorrect input handling. AlwaysPresent = true; + + InternalChild = body = new ManualSliderBody + { + AccentColour = Color4.Transparent + }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { body.BorderColour = colours.Yellow; - AutoSizeAxes = Axes.Both; } private int? lastVersion; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs index 2fc18da254..127d13730a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/ManualSliderBody.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osuTK; @@ -13,12 +12,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default /// public partial class ManualSliderBody : SliderBody { - public new void SetVertices(IReadOnlyList vertices) => base.SetVertices(vertices); - - [BackgroundDependencyLoader] - private void load() + public ManualSliderBody() { AutoSizeAxes = Axes.Both; } + + public new void SetVertices(IReadOnlyList vertices) => base.SetVertices(vertices); } } From a80dbba9d0d4fc836b899b1825692170c1c843d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 10:21:40 +0200 Subject: [PATCH 089/103] Update to not use obsoleted method --- osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs index 5930c077a4..8b6f31d599 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Editing // It's important values are committed immediately on focus loss so the editor exit sequence detects them. AddAssert("value immediately changed on focus loss", () => { - InputManager.TriggerFocusContention(metadataSection); + ((IFocusManager)InputManager).TriggerFocusContention(metadataSection); return editorBeatmap.Metadata.Artist; }, () => Is.EqualTo("Example ArtistExample Artist")); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 47b2a53607..55ab03a590 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -723,7 +723,7 @@ namespace osu.Game.Screens.Edit // // This is important to ensure that if the user is still editing a textbox, it will commit // (and potentially block the exit procedure for save). - GetContainingInputManager().TriggerFocusContention(this); + GetContainingFocusManager().TriggerFocusContention(this); if (!ExitConfirmed) { From 807d982a721ec043990d1871f41316763d3b5b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 10:24:50 +0200 Subject: [PATCH 090/103] Move workaround to subscreen --- osu.Game/Screens/Edit/Editor.cs | 6 +----- osu.Game/Screens/Edit/EditorScreen.cs | 5 +++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 12 ++++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 55ab03a590..07c32983f5 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -719,11 +719,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(ScreenExitEvent e) { - // Before exiting, trigger a focus loss. - // - // This is important to ensure that if the user is still editing a textbox, it will commit - // (and potentially block the exit procedure for save). - GetContainingFocusManager().TriggerFocusContention(this); + currentScreen?.OnExiting(e); if (!ExitConfirmed) { diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 3bc870b898..a795b310a2 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Screens; namespace osu.Game.Screens.Edit { @@ -37,6 +38,10 @@ namespace osu.Game.Screens.Edit protected override void PopOut() => this.FadeOut(); + public virtual void OnExiting(ScreenExitEvent e) + { + } + #region Clipboard operations public BindableBool CanCut { get; } = new BindableBool(); diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 266ea1f929..5345db0a4f 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Overlays; @@ -55,6 +56,17 @@ namespace osu.Game.Screens.Edit.Setup })); } + public override void OnExiting(ScreenExitEvent e) + { + base.OnExiting(e); + + // Before exiting, trigger a focus loss. + // + // This is important to ensure that if the user is still editing a textbox, it will commit + // (and potentially block the exit procedure for save). + GetContainingFocusManager().TriggerFocusContention(this); + } + private partial class SetupScreenSectionsContainer : SectionsContainer { protected override UserTrackingScrollContainer CreateScrollContainer() From 7255cc3344e75ec0e0eb1fd99eab94da1f50f784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 11:25:29 +0200 Subject: [PATCH 091/103] Fix tests dying on a nullref --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 5345db0a4f..7a7907d08a 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Edit.Setup // // This is important to ensure that if the user is still editing a textbox, it will commit // (and potentially block the exit procedure for save). - GetContainingFocusManager().TriggerFocusContention(this); + GetContainingFocusManager()?.TriggerFocusContention(this); } private partial class SetupScreenSectionsContainer : SectionsContainer From 9045ec24abc5e844da7cc646fcc7fcbb620e75bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 12:04:09 +0200 Subject: [PATCH 092/103] Rewrite test --- .../SongSelect/TestScenePlaySongSelect.cs | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index af8b2a7760..7f0c209215 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -93,49 +93,49 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); changeMods(); - AddStep("decreasing speed without mods", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("half time activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("decreasing speed with halftime", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.9", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.9); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("half time speed changed to 0.9x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); - AddStep("increasing speed with halftime", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("halftime at 0.95", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("half time speed changed to 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("increasing speed with halftime to nomod", () => songSelect?.ChangeSpeed(+0.05)); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - AddStep("increasing speed without mods", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("double time activated at 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - AddStep("increasing speed with doubletime", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("doubletime at 1.1", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.1); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("double time speed changed to 1.1x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); - AddStep("decreasing speed with doubletime", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModDoubleTime mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("double time speed changed to 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); OsuModNightcore nc = new OsuModNightcore { SpeedChange = { Value = 1.05 } }; changeMods(nc); - AddStep("increasing speed with nightcore", () => songSelect?.ChangeSpeed(+0.05)); - AddAssert("nightcore at 1.1", () => songSelect!.Mods.Value.Single() is ModNightcore mod && Math.Round(mod.SpeedChange.Value, 2) == 1.1); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("nightcore speed changed to 1.1x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); - AddStep("decreasing speed with nightcore", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("doubletime at 1.05", () => songSelect!.Mods.Value.Single() is ModNightcore mod && Math.Round(mod.SpeedChange.Value, 2) == 1.05); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("nightcore speed changed to 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - AddStep("decreasing speed with nightcore to nomod", () => songSelect?.ChangeSpeed(-0.05)); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - AddStep("decreasing speed nomod, nightcore was selected", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("decreasing speed with daycore", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("daycore at 0.9", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.9); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); - AddStep("increasing speed with daycore", () => songSelect?.ChangeSpeed(0.05)); - AddAssert("daycore at 0.95", () => songSelect!.Mods.Value.Single() is ModDaycore mod && Math.Round(mod.SpeedChange.Value, 2) == 0.95); + AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); OsuModDoubleTime dt = new OsuModDoubleTime { @@ -143,8 +143,9 @@ namespace osu.Game.Tests.Visual.SongSelect AdjustPitch = { Value = true }, }; changeMods(dt); - AddStep("decreasing speed from doubletime 1.02 with adjustpitch enabled", () => songSelect?.ChangeSpeed(-0.05)); - AddAssert("halftime at 0.97 with adjustpitch enabled", () => songSelect!.Mods.Value.Single() is ModHalfTime mod && Math.Round(mod.SpeedChange.Value, 2) == 0.97 && mod.AdjustPitch.Value); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + AddAssert("half time activated at 0.97x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.97).Within(0.005)); + AddAssert("adjust pitch preserved", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); OsuModHalfTime ht = new OsuModHalfTime { @@ -153,16 +154,19 @@ namespace osu.Game.Tests.Visual.SongSelect }; Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() }; changeMods(modlist); - AddStep("decreasing speed from halftime 0.97 with adjustpitch enabled, HDHR enabled", () => songSelect?.ChangeSpeed(0.05)); - AddAssert("doubletime at 1.02 with adjustpitch enabled, HDHR still enabled", () => songSelect!.Mods.Value.Count(mod => (mod is ModDoubleTime modDt && modDt.AdjustPitch.Value && Math.Round(modDt.SpeedChange.Value, 2) == 1.02) || mod is ModHardRock || mod is ModHidden) == 3); + AddStep("decrease speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.02).Within(0.005)); + AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); + AddAssert("HD still enabled", () => songSelect!.Mods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); + AddAssert("HR still enabled", () => songSelect!.Mods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); changeMods(new ModWindUp()); AddStep("windup active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); AddAssert("windup still active", () => songSelect!.Mods.Value.First() is ModWindUp); changeMods(new ModAdaptiveSpeed()); - AddStep("adaptivespeed active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); - AddAssert("adaptivespeed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed); + AddStep("adaptive speed active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); + AddAssert("adaptive speed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed); } [Test] From 63406b6feb2215111626903bceef923ffb5ca46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 12:59:24 +0200 Subject: [PATCH 093/103] Rewrite implementation --- .../SongSelect/TestScenePlaySongSelect.cs | 51 ++++-- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 11 +- .../Screens/Select/ModSpeedHotkeyHandler.cs | 105 ++++++++++++ osu.Game/Screens/Select/SongSelect.cs | 152 +----------------- 4 files changed, 149 insertions(+), 170 deletions(-) create mode 100644 osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 7f0c209215..6581ce0323 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -93,25 +93,25 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); changeMods(); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("half time activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("half time speed changed to 0.9x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("half time speed changed to 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("double time activated at 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("double time speed changed to 1.1x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("double time speed changed to 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); OsuModNightcore nc = new OsuModNightcore @@ -119,22 +119,23 @@ namespace osu.Game.Tests.Visual.SongSelect SpeedChange = { Value = 1.05 } }; changeMods(nc); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + + increaseModSpeed(); AddAssert("nightcore speed changed to 1.1x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.1).Within(0.005)); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("nightcore speed changed to 1.05x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.05).Within(0.005)); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + decreaseModSpeed(); AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.9).Within(0.005)); - AddStep("increase speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("daycore activated at 0.95x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.95).Within(0.005)); OsuModDoubleTime dt = new OsuModDoubleTime @@ -143,7 +144,8 @@ namespace osu.Game.Tests.Visual.SongSelect AdjustPitch = { Value = true }, }; changeMods(dt); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(-0.05)); + + decreaseModSpeed(); AddAssert("half time activated at 0.97x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(0.97).Within(0.005)); AddAssert("adjust pitch preserved", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); @@ -154,19 +156,34 @@ namespace osu.Game.Tests.Visual.SongSelect }; Mod[] modlist = { ht, new OsuModHardRock(), new OsuModHidden() }; changeMods(modlist); - AddStep("decrease speed", () => songSelect?.ChangeSpeed(0.05)); + + increaseModSpeed(); AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType().Single().SpeedChange.Value, () => Is.EqualTo(1.02).Within(0.005)); AddAssert("double time activated at 1.02x", () => songSelect!.Mods.Value.OfType().Single().AdjustPitch.Value, () => Is.True); AddAssert("HD still enabled", () => songSelect!.Mods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); AddAssert("HR still enabled", () => songSelect!.Mods.Value.OfType().SingleOrDefault(), () => Is.Not.Null); changeMods(new ModWindUp()); - AddStep("windup active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("windup still active", () => songSelect!.Mods.Value.First() is ModWindUp); changeMods(new ModAdaptiveSpeed()); - AddStep("adaptive speed active, trying to change speed", () => songSelect?.ChangeSpeed(0.05)); + increaseModSpeed(); AddAssert("adaptive speed still active", () => songSelect!.Mods.Value.First() is ModAdaptiveSpeed); + + void increaseModSpeed() => AddStep("increase mod speed", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Up); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + void decreaseModSpeed() => AddStep("decrease mod speed", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Down); + InputManager.ReleaseKey(Key.ControlLeft); + }); } [Test] diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ad589e8fa9..f8c67f4a10 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -64,9 +64,6 @@ namespace osu.Game.Overlays.Mods private Func isValidMod = _ => true; - [Resolved] - private SongSelect? songSelect { get; set; } - /// /// A function determining whether each mod in the column should be displayed. /// A return value of means that the mod is not filtered and therefore its corresponding panel should be displayed. @@ -138,6 +135,7 @@ namespace osu.Game.Overlays.Mods private FillFlowContainer footerButtonFlow = null!; private FillFlowContainer footerContentFlow = null!; private DeselectAllModsButton deselectAllModsButton = null!; + private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!; private Container aboveColumnsContent = null!; private RankingInformationDisplay? rankingInformationDisplay; @@ -190,7 +188,8 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Height = 0 - } + }, + modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(), }); MainAreaContent.AddRange(new Drawable[] @@ -758,11 +757,11 @@ namespace osu.Game.Overlays.Mods } case GlobalAction.IncreaseModSpeed: - songSelect?.ChangeSpeed(0.05); + modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); return true; case GlobalAction.DecreaseModSpeed: - songSelect?.ChangeSpeed(-0.05); + modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); return true; } diff --git a/osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs b/osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs new file mode 100644 index 0000000000..af64002bcf --- /dev/null +++ b/osu.Game/Screens/Select/ModSpeedHotkeyHandler.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Utils; +using osu.Game.Configuration; +using osu.Game.Overlays; +using osu.Game.Overlays.OSD; +using osu.Game.Rulesets.Mods; +using osu.Game.Utils; + +namespace osu.Game.Screens.Select +{ + public partial class ModSpeedHotkeyHandler : Component + { + [Resolved] + private Bindable> selectedMods { get; set; } = null!; + + [Resolved] + private OsuConfigManager config { get; set; } = null!; + + [Resolved] + private OnScreenDisplay? onScreenDisplay { get; set; } + + private ModRateAdjust? lastActiveRateAdjustMod; + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedMods.BindValueChanged(val => + { + lastActiveRateAdjustMod = val.NewValue.OfType().SingleOrDefault() ?? lastActiveRateAdjustMod; + }, true); + } + + public bool ChangeSpeed(double delta, IEnumerable availableMods) + { + double targetSpeed = (selectedMods.Value.OfType().SingleOrDefault()?.SpeedChange.Value ?? 1) + delta; + + if (Precision.AlmostEquals(targetSpeed, 1, 0.005)) + { + selectedMods.Value = selectedMods.Value.Where(m => m is not ModRateAdjust).ToList(); + onScreenDisplay?.Display(new SpeedChangeToast(config, targetSpeed)); + return true; + } + + ModRateAdjust? targetMod; + + if (lastActiveRateAdjustMod is ModDaycore || lastActiveRateAdjustMod is ModNightcore) + { + targetMod = targetSpeed < 1 + ? availableMods.OfType().SingleOrDefault() + : availableMods.OfType().SingleOrDefault(); + } + else + { + targetMod = targetSpeed < 1 + ? availableMods.OfType().SingleOrDefault() + : availableMods.OfType().SingleOrDefault(); + } + + if (targetMod == null) + return false; + + // preserve other settings from latest rate adjust mod instance seen + if (lastActiveRateAdjustMod != null) + { + foreach (var (_, sourceProperty) in lastActiveRateAdjustMod.GetSettingsSourceProperties()) + { + if (sourceProperty.Name == nameof(ModRateAdjust.SpeedChange)) + continue; + + var targetProperty = targetMod.GetType().GetProperty(sourceProperty.Name); + + if (targetProperty == null) + continue; + + var targetBindable = (IBindable)targetProperty.GetValue(targetMod)!; + var sourceBindable = (IBindable)sourceProperty.GetValue(lastActiveRateAdjustMod)!; + + if (targetBindable.GetType() != sourceBindable.GetType()) + continue; + + lastActiveRateAdjustMod.CopyAdjustedSetting(targetBindable, sourceBindable); + } + } + + targetMod.SpeedChange.Value = targetSpeed; + + var intendedMods = selectedMods.Value.Where(m => m is not ModRateAdjust).Append(targetMod).ToList(); + + if (!ModUtils.CheckCompatibleSet(intendedMods)) + return false; + + selectedMods.Value = intendedMods; + onScreenDisplay?.Display(new SpeedChangeToast(config, targetMod.SpeedChange.Value)); + return true; + } + } +} diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b3823d7a0f..14e3931fce 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -30,7 +30,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Mods; -using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Backgrounds; @@ -40,6 +39,7 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Options; using osu.Game.Skinning; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -137,6 +137,7 @@ namespace osu.Game.Screens.Select private double audioFeedbackLastPlaybackTime; private IDisposable? modSelectOverlayRegistration; + private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!; private AdvancedStats advancedStats = null!; @@ -148,16 +149,6 @@ namespace osu.Game.Screens.Select private Bindable configBackgroundBlur = null!; - private bool lastPitchState; - - private bool usedPitchMods; - - [Resolved] - private OnScreenDisplay? onScreenDisplay { get; set; } - - [Resolved] - private OsuConfigManager config { get; set; } = null!; - [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { @@ -333,6 +324,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, }, + modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(), }); if (ShowFooter) @@ -823,140 +815,6 @@ namespace osu.Game.Screens.Select return false; } - private Mod getRateMod(ModType modType, Type type) - { - var modList = game.AvailableMods.Value[modType]; - var multiMod = (MultiMod)modList.First(mod => mod is MultiMod multiMod && multiMod.Mods.Count(mod2 => mod2.GetType().IsSubclassOf(type)) > 0); - var mod = multiMod.Mods.First(mod => mod.GetType().IsSubclassOf(type)); - return mod; - } - - public void ChangeSpeed(double delta) - { - ModNightcore modNc = (ModNightcore)getRateMod(ModType.DifficultyIncrease, typeof(ModNightcore)); - ModDoubleTime modDt = (ModDoubleTime)getRateMod(ModType.DifficultyIncrease, typeof(ModDoubleTime)); - ModDaycore modDc = (ModDaycore)getRateMod(ModType.DifficultyReduction, typeof(ModDaycore)); - ModHalfTime modHt = (ModHalfTime)getRateMod(ModType.DifficultyReduction, typeof(ModHalfTime)); - bool rateModActive = selectedMods.Value.Count(mod => mod is ModRateAdjust) > 0; - bool incompatibleModActive = selectedMods.Value.Count(mod => modDt.IncompatibleMods.Count(incompatibleMod => (mod.GetType().IsSubclassOf(incompatibleMod) || mod.GetType() == incompatibleMod) && incompatibleMod != typeof(ModRateAdjust)) > 0) > 0; - double newRate = Math.Round(1d + delta, 2); - bool isPositive = delta > 0; - - if (incompatibleModActive) - return; - - if (!rateModActive) - { - onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); - - // If no ModRateAdjust is active, activate one - ModRateAdjust? newMod = null; - - if (isPositive && !usedPitchMods) - newMod = modDt; - - if (isPositive && usedPitchMods) - newMod = modNc; - - if (!isPositive && !usedPitchMods) - newMod = modHt; - - if (!isPositive && usedPitchMods) - newMod = modDc; - - if (!usedPitchMods && newMod is ModDoubleTime newModDt) - newModDt.AdjustPitch.Value = lastPitchState; - - if (!usedPitchMods && newMod is ModHalfTime newModHt) - newModHt.AdjustPitch.Value = lastPitchState; - - newMod!.SpeedChange.Value = newRate; - selectedMods.Value = selectedMods.Value.Append(newMod).ToList(); - return; - } - - ModRateAdjust mod = (ModRateAdjust)selectedMods.Value.First(mod => mod is ModRateAdjust); - newRate = Math.Round(mod.SpeedChange.Value + delta, 2); - - // Disable RateAdjustMods if newRate is 1 - if (newRate == 1.0) - { - lastPitchState = false; - usedPitchMods = false; - - if (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) - lastPitchState = true; - - if (mod is ModHalfTime htmod && htmod.AdjustPitch.Value) - lastPitchState = true; - - if (mod is ModNightcore || mod is ModDaycore) - usedPitchMods = true; - - //Disable RateAdjustMods - selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); - - onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); - - return; - } - - bool overMaxRateLimit = (mod is ModHalfTime || mod is ModDaycore) && newRate > mod.SpeedChange.MaxValue; - bool underMinRateLimit = (mod is ModDoubleTime || mod is ModNightcore) && newRate < mod.SpeedChange.MinValue; - - // Swap mod to opposite mod if newRate exceeds max/min speed values - if (overMaxRateLimit || underMinRateLimit) - { - bool adjustPitch = (mod is ModDoubleTime dtmod && dtmod.AdjustPitch.Value) || (mod is ModHalfTime htmod && htmod.AdjustPitch.Value); - - //Disable RateAdjustMods - selectedMods.Value = selectedMods.Value.Where(search => search is not ModRateAdjust).ToList(); - - ModRateAdjust? oppositeMod = null; - - switch (mod) - { - case ModDoubleTime: - modHt.AdjustPitch.Value = adjustPitch; - oppositeMod = modHt; - break; - - case ModHalfTime: - modDt.AdjustPitch.Value = adjustPitch; - oppositeMod = modDt; - break; - - case ModNightcore: - oppositeMod = modDc; - break; - - case ModDaycore: - oppositeMod = modNc; - break; - } - - if (oppositeMod == null) return; - - oppositeMod.SpeedChange.Value = newRate; - selectedMods.Value = selectedMods.Value.Append(oppositeMod).ToList(); - - onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); - - return; - } - - // Cap newRate to max/min values and change rate of current active mod - if (newRate > mod.SpeedChange.MaxValue && (mod is ModDoubleTime || mod is ModNightcore)) - newRate = mod.SpeedChange.MaxValue; - - if (newRate < mod.SpeedChange.MinValue && (mod is ModHalfTime || mod is ModDaycore)) - newRate = mod.SpeedChange.MinValue; - - mod.SpeedChange.Value = newRate; - - onScreenDisplay?.Display(new SpeedChangeToast(config, newRate)); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -1160,11 +1018,11 @@ namespace osu.Game.Screens.Select switch (e.Action) { case GlobalAction.IncreaseModSpeed: - ChangeSpeed(0.05); + modSpeedHotkeyHandler.ChangeSpeed(0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); return true; case GlobalAction.DecreaseModSpeed: - ChangeSpeed(-0.05); + modSpeedHotkeyHandler.ChangeSpeed(-0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); return true; } From 345fb60679c3017f75b1b2d9dd54c210fde50a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 13:08:17 +0200 Subject: [PATCH 094/103] Fix toast strings --- osu.Game/Localisation/ToastStrings.cs | 4 ++-- osu.Game/Overlays/OSD/SpeedChangeToast.cs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 25899153f8..942540cfc5 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -50,9 +50,9 @@ namespace osu.Game.Localisation public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied"); /// - /// "Speed changed to" + /// "Speed changed to {0:N2}x" /// - public static LocalisableString SpeedChangedTo => new TranslatableString(getKey(@"speed_changed"), @"Speed changed to"); + public static LocalisableString SpeedChangedTo(double speed) => new TranslatableString(getKey(@"speed_changed"), @"Speed changed to {0:N2}x", speed); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Overlays/OSD/SpeedChangeToast.cs b/osu.Game/Overlays/OSD/SpeedChangeToast.cs index df4f825541..49d3985b04 100644 --- a/osu.Game/Overlays/OSD/SpeedChangeToast.cs +++ b/osu.Game/Overlays/OSD/SpeedChangeToast.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Threading; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -11,7 +10,7 @@ namespace osu.Game.Overlays.OSD public partial class SpeedChangeToast : Toast { public SpeedChangeToast(OsuConfigManager config, double newSpeed) - : base(CommonStrings.Beatmaps, ToastStrings.SpeedChangedTo + " " + newSpeed.ToString(Thread.CurrentThread.CurrentCulture), config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed)) + : base(ModSelectOverlayStrings.ModCustomisation, ToastStrings.SpeedChangedTo(newSpeed), config.LookupKeyBindings(GlobalAction.IncreaseModSpeed) + " / " + config.LookupKeyBindings(GlobalAction.DecreaseModSpeed)) { } } From 8cac87e4960253eae950f68c88923ad6dcf622dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 13:09:07 +0200 Subject: [PATCH 095/103] Fix speed controls in mod select overlay not handling repeat --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f8c67f4a10..bc87bb4e3d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -704,6 +704,17 @@ namespace osu.Game.Overlays.Mods public override bool OnPressed(KeyBindingPressEvent e) { + switch (e.Action) + { + case GlobalAction.IncreaseModSpeed: + modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); + return true; + + case GlobalAction.DecreaseModSpeed: + modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); + return true; + } + if (e.Repeat) return false; @@ -755,14 +766,6 @@ namespace osu.Game.Overlays.Mods return true; } - - case GlobalAction.IncreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - return true; - - case GlobalAction.DecreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - return true; } return base.OnPressed(e); From b1b207960a46337b0a64036d575a23b846f638c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 13:09:44 +0200 Subject: [PATCH 096/103] Actually use return value --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 ++---- osu.Game/Screens/Select/SongSelect.cs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index bc87bb4e3d..8489b06f47 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -707,12 +707,10 @@ namespace osu.Game.Overlays.Mods switch (e.Action) { case GlobalAction.IncreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - return true; + return modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); case GlobalAction.DecreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - return true; + return modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); } if (e.Repeat) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 14e3931fce..269ca37ff5 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -1018,12 +1018,10 @@ namespace osu.Game.Screens.Select switch (e.Action) { case GlobalAction.IncreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); - return true; + return modSpeedHotkeyHandler.ChangeSpeed(0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); case GlobalAction.DecreaseModSpeed: - modSpeedHotkeyHandler.ChangeSpeed(-0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); - return true; + return modSpeedHotkeyHandler.ChangeSpeed(-0.05, ModUtils.FlattenMods(game.AvailableMods.Value.SelectMany(kv => kv.Value))); } if (e.Repeat) From cab8cf741073959dc314516bac3e3fb63dcc262b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 May 2024 13:14:06 +0200 Subject: [PATCH 097/103] Move mod speed hotkey handler to user mod select overlay The very base class is the wrong place for it because `FreeModSelectOverlay` inherits from it, and that one has different assumptions and rules than "user" selection. In particular, in non-user selection, more than one rate adjust mod may be active, which breaks the mod speed hotkey's basic assumptions. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 --------- .../Overlays/Mods/UserModSelectOverlay.cs | 26 +++++++++++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 8489b06f47..d2d7ace936 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -27,7 +27,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Select; using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -135,7 +134,6 @@ namespace osu.Game.Overlays.Mods private FillFlowContainer footerButtonFlow = null!; private FillFlowContainer footerContentFlow = null!; private DeselectAllModsButton deselectAllModsButton = null!; - private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!; private Container aboveColumnsContent = null!; private RankingInformationDisplay? rankingInformationDisplay; @@ -189,7 +187,6 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.BottomCentre, Height = 0 }, - modSpeedHotkeyHandler = new ModSpeedHotkeyHandler(), }); MainAreaContent.AddRange(new Drawable[] @@ -704,15 +701,6 @@ namespace osu.Game.Overlays.Mods public override bool OnPressed(KeyBindingPressEvent e) { - switch (e.Action) - { - case GlobalAction.IncreaseModSpeed: - return modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - - case GlobalAction.DecreaseModSpeed: - return modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); - } - if (e.Repeat) return false; diff --git a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs index 49469b99f3..16d71e557b 100644 --- a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs @@ -3,18 +3,30 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Input.Events; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select; using osu.Game.Utils; namespace osu.Game.Overlays.Mods { public partial class UserModSelectOverlay : ModSelectOverlay { + private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!; + public UserModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) : base(colourScheme) { } + [BackgroundDependencyLoader] + private void load() + { + Add(modSpeedHotkeyHandler = new ModSpeedHotkeyHandler()); + } + protected override ModColumn CreateModColumn(ModType modType) => new UserModColumn(modType, false); protected override IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) @@ -38,6 +50,20 @@ namespace osu.Game.Overlays.Mods return modsAfterRemoval.ToList(); } + public override bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case GlobalAction.IncreaseModSpeed: + return modSpeedHotkeyHandler.ChangeSpeed(0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); + + case GlobalAction.DecreaseModSpeed: + return modSpeedHotkeyHandler.ChangeSpeed(-0.05, AllAvailableMods.Where(state => state.ValidForSelection.Value).Select(state => state.Mod)); + } + + return base.OnPressed(e); + } + private partial class UserModColumn : ModColumn { public UserModColumn(ModType modType, bool allowIncompatibleSelection) From 1e90e5e38e22e43c7bbcbb6ee5d11d82aac77216 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 25 May 2024 13:24:25 +0300 Subject: [PATCH 098/103] Use sb element path as a name --- osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs | 1 + osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index fae9ec7f2e..f66f84af7a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -83,6 +83,7 @@ namespace osu.Game.Storyboards.Drawables Origin = animation.Origin; Position = animation.InitialPosition; Loop = animation.LoopType == AnimationLoopType.LoopForever; + Name = animation.Path; LifetimeStart = animation.StartTime; LifetimeEnd = animation.EndTimeForDisplay; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index ec875219b6..c5d70ddecc 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -85,6 +85,7 @@ namespace osu.Game.Storyboards.Drawables Sprite = sprite; Origin = sprite.Origin; Position = sprite.InitialPosition; + Name = sprite.Path; LifetimeStart = sprite.StartTime; LifetimeEnd = sprite.EndTimeForDisplay; From 76f13b21da3ed79e9daf3d7421342bdb29762dae Mon Sep 17 00:00:00 2001 From: Joppe27 Date: Sat, 25 May 2024 23:28:51 +0200 Subject: [PATCH 099/103] Correct scale of taiko-glow element --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs index 623243e9e1..487106d879 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy private Sprite sprite = null!; + private const float base_scale = 0.8f; + [BackgroundDependencyLoader(true)] private void load(ISkinSource skin, HealthProcessor? healthProcessor) { @@ -30,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy Origin = Anchor.Centre, Anchor = Anchor.Centre, Alpha = 0, - Scale = new Vector2(0.7f), + Scale = new Vector2(base_scale), Colour = new Colour4(255, 228, 0, 255), }; @@ -58,8 +60,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (!result.IsHit || !isKiaiActive) return; - sprite.ScaleTo(0.85f).Then() - .ScaleTo(0.7f, 80, Easing.OutQuad); + sprite.ScaleTo(base_scale + 0.15f).Then() + .ScaleTo(base_scale, 80, Easing.OutQuad); } } } From a62b9fa633437bd31dd375bf6c9b12a8591ff225 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 May 2024 11:42:36 +0900 Subject: [PATCH 100/103] Revert windows 16px icon to original version This also fixes the 48px version looking uncanny due to smaller paddings. --- osu.Desktop/lazer.ico | Bin 76679 -> 76679 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/osu.Desktop/lazer.ico b/osu.Desktop/lazer.ico index 24c1c29ba269aaeb38a9b9c604a1c364c986a753..d5dbf933c1c6795b54b485b349aa76f914707996 100644 GIT binary patch delta 11340 zcmbta32>d&bw)r^mQa#mDQyV}18E9PCbYv$(+)}KOgklj@uGc~S9XF)A%rEr0o#%# zOR^-(vi8lkuq|6&WV`^jF<@g``@ZW*deVDZpY~lk{l0tO|MVmbP1`$j-hc18=bn4+ zx%+otc={UZbpg(~;K|cZAknoR# zo4$|8&p|A71vHlM-q1hXHj3C~kOBG`#9sA7pmC5Zj1A35XZ@(uelZ|Lh23)T zpiM63+f0#V2}X&^~3V;ddI83A#VwsKd)ZBpB2QQo&j~B1gA3OSE5! zeDK5-+5FopvUS;2R8}P6%fEq;w{=;OY<}{pDih;hDktLG#NNEfIjqC~y2T;kyyIUC z@V^7v4pL!sU|LQlT4dYGV$@$G5zC8Z+lrDugBHuN+y}qCED_5}CDyM(K1sF8*r3b% z^QbHW+949d`w$W>*k z%y!QTSX3tIFV%^qY}l;sW=1$rZ-OC+n z@aI#NZ1*geh-XTa9`|xlD=JhSWvBXnfVy2F8!`Im5d)EEll?`WO}0K&0!^i|ZMjKh zY7|5)E0JigN{RKZa#A(ECeh*rQZhc(@KNcKGG^H-iS~rmt15pWmk;-t_46A6tA1u6 z!ZFPLl+us-W&289!o8Ll)D8`@<;fy#32Kb>sZq4U`PJ#g{Oz9fdATgeEaW@o{~FDy zmgrSgQg*tZHZaf_KUnwRKO@5Ftn6+r9g#TST8Z_lfyUZJL_XW8Myq#C!mlUqPFOK{ zqdVP#B9Z7M7{lxbXt%ef7|&|i;aek?@)1;~0_WS0V8V|LMD~$2>X?y?7n>x;vleF5 zeT`_+_LY^&tSEf1C9{aAM0{YohvM2dW-Qqo?icb|#bu<~A7 z`5Ta$pP#1KBzi@yP{Y^AuK|kCTyNwf&ZftDF}_$f&ZkcCnMaCTj^T|mW?FrnY3O=L zDT-Qw=6lyE<*XoTS+#tc+6B8*;NtnOu=Y*^5f9KYkl33hBq5*ymNrO&Un7WLO|&r1 zryj~{TzQmZ3z(U&EATJh55`3A~`8)laB+k1YmemX6CSS|eNxV;kGJtts zBdx>G87k3;rq#Lx<4`#2nOZrMXrpZm+&CUuyj5Ix9e7)q$<1(gSThV{WzCBy5$Sjz zxYDyu<;SBDnhYI@S%r}T0nKbu-yL2Ur^~Ar_^$j$+3B~q0<*$6Z}d0hnAVjwOHyc) zI2LwW!@U7KJ#a-YWeZ9Q9kOE;wsQZLuTh+Lql7aXvsH?3EUk=+s9=W|#sOT(d>U1D(m@*;cuD8O{a8s!Srv#e22Lv2fRX<;m(Nx&i17BzSa_F~_VVuWpt^KMM+K+Ubkd2bM|E zDXUCQaGvWhHasa84p^{tm&5p0i3_NbFAlZI=%AWNrkL0*!;#+QdWWa|?Za%Q(x3C+)mG+!B$;v)`eui_yW zikn8%%2W{5q|*H9Zl093vT>OjM*)P^3Wr)p^Iq=|>R`B9&@VB|8zm{Q9o^inylQWB zRnD29X9Hx_nWpaZVcF@8@!;Dky8}BU(Wg!FKWI_P*zzME*d);xiu~H-bbPnWOwWVn zB;%C^+57trX}K~k$0O_#{Y<_5={Hv;{k3wLo1K@*(OF4aQ!CBIgGyJ;XM-4g7@S^4 zn-pyBl$ohHnV+ARy>GV2PE>mM{dV~zrAyM+wIhwu?BAvgN`P?<7hRiardt=BqUMt0 z4%z7i*?zAim(R0e(qtTS@#gI={i^jV#TZi9z>t%j~B$Ra(hx@9MK zU=vzdFZLGQ^8EKOc>Ju&rMm;H5(gJvJv?CQKI_&6ZVl*=)Zi|qn_j(gs87k4ecU6_ z%j?0nNlIWB_8X&CTFZ6oKHk+WyF)QQ9eSSkTBf9=U|5cBwo8IPRzuG=NeXTevjs=O zF%W&Mk_u(J*#L`fKw-2M1GriGoh>~c*(piBoiNCTE@;OXXp^D>=W1%;{^{0&(=@aA zC`P9@6bIWS4YUh|3pUx*0G%A4kwe?CF?r$M;8QB6lAAHY=VW?fPI6wdO3uq2!g)S1 zIwMINaI*bREVJt>Z(n3t~RA?a=(F-#?L zdB1M;ZY&mDTo~G_2c-I z26RcXpIsTjzEAY-kcKZgjSW^KS#B-ZN(Prmj{Y=Qo*vSpC@r{0GFEp>alsJw3g;3B z*`e+kxtKd7X+c^x+21Y)-|P`v?UZXJoSB@L)}nFw@Ug;5#qnU(#<>)o)v zM_I;J)Sexck-k|u^v6C)4(@QZpfJ+rY5@&O3F;Jkqi!v1aG%BjYyda2?B_580_>6z z(yJ-7SJH#A*ZA6H&x^fs>^+AVN8TNf?Dgm$f3(g`knYL|?UDR9`sJuGAO~LSSITHC z4b589D|?>r1COolg{Q!$2SU3QKRu{V(pGo6x}-4DF1vkfFaX{R>Xn=qx&V628Z?%{ z%dHC@MtYJ5iep>gE*L_qGei49eTp*hO%3Rgv_Kf(2Zh0!uk$I_uNspU#L8d@#0jOv`;dE4g7>46M`(HS^(=ZpfDo@O4iuL(WQ}+kD_3VZ?}@8w+bU%w@yxc=Yc}4 zq^=`f>G2`u?u7;np!|Sjfmm<4p{(hXtT1Nus|*S#0&k&UzbmeJkO_l4WJpv-(q-uZ zt%JO}^W!pT!(jV8JP@SIa13w?8?cXJ?NcNsNe#|dy zcU^4G#ACepa}3d2(6@R}QC6to5T%Tjgn>z7rWtpBl{-;s7SfDgONM2#pog@yJkuG$ zC`WzJpW@dmpYFn@w2VIH@fjeZX}S3YkC(|pkk}d~BrVhi<+_rb)k8vJW#%_KEa~V? z9`_}U+~jd@(z%;FzW1my#VP43K@&i_)S#q?*`%k%wUQLzR(Owr8vhNX&YI^^F!Nz= zb{MN1c71aZH`BSRM}%qDcMan@ox^e;hkxaq9Ys zE@(C%3NwNnvTvguXol;wz6X{n%;iBCsTPjbk}-8A$POKbHDg~RZKXSZRQ5tHclD^U z{#s?O&4abN#rNyNYsZ9@riIv~ty~{kU=%M2gnu7QC>{HrQF;QS6q^T2?p-^+h|F59 z#b3(%pVHLD7Ez9MvCfK9L$|>g6$qIH!YT#m#W|Tcr8=-zrZugnBP6{bt-j2x204c*irAzAfYw4z?#PlM7SfmdXIDT zY%+G;P@FLW9k6IZK3uOY`f&XW;tRC@8{(A7Lkrlu=>ZO$J8W`3wNK4|)MbWfL_EF& zE1obATMcq;+FVl4#A8#y?w0D0jVE|mS)zR7tabnTSvPC6A6O%0StfIxP0l3rs^uBF z%|N6f#9YRmUp#932VZf13d#ey4v+(#xMv(2#9^mfa-K&Y;mnyDfD;w`njSEKF9~N8 z@{mUv%9b1-lmX}21p07ZQul7U{Xsq%c_H%eAcB$l1cL~}=m6fMfECnT7*-cnP9_o+ z@&?X4g2GDz_oyRDw?E+VMsoKv6W&b81YyoAicQ3wunZ&m+ zLP?aVzo;)y^D+yCvKGo7VZ(2MMQQj-d&QWfzgi<(o+<|Mv1`dg*UE)7^WMj!T=>%^ zlKVyjARgC}o(xqtL-~VYRE{B1K;;+rcdIuQzG3mz$NjbETJdPSTSczGI}BcwA_3d+ zd+DHS9_2eLx%W9lxDTg;6rQy8$x>B-PbJ~Y@CFG4EEZ2Je1b6_fz0O^@+lYN_COQ^ zz&dhyrJPN-Q6<70JJACzx`&mmC+pIIUWIz{&4ceTJMdycoRDxE4d5;LF1Ehu(wKT5*lFVb_!2QEz)Z}ae7*&c zXUgor0|a^I+kkopFw67(05HEs-5UYT0HXV93`^ro&+zc@9i|byrEu+#BY^6NlA54( z9MH~)CowAV9>X{hZiR$DQ=@oM_>xS-JbFc)YdFgGLmfqG$86TOJv={5; zOiCL!2^pb00ExcNG1(PXFPHQCA&&>4O#nL;`f3qd^(?w!noIfh?El;6WU{B~^eE6P zcmN1#?rRpA19*LLUq6tNcKPu2F2JmF^5uS@A8&UG!6hyx`Qi0S`H3Cv3e0G_0&K{~ z0vW5~f0AsIq>yGQ2yc-jplCaNEfP=6@#9fkKPI_6-p2mF2iA;U%^y5zp&CkPfz_!cq z9u!zF{axcyae7FOMd(391bqM7?UMFv6R^crbj6H(b+Au%dRirO1Ktz>Okc?9k(04( zDDdZvost~T1q0fo`0y~hfcUV%4exfiqqp)D`pmma9})m4wwF)H#MqP+pY9TC!;r!O z_rGqJ0>iGx#-#$QRGjO^1Js1%{;myh{j3xL+TXdVQ?fR6DfIkO-hdEEV|VV`2&4=E zRic*_T`=zK0z>V(H^7?m&PqV(zCgx_`2aP^dA3K&j*Uuh^Q^R9nUrJi4=89RHOMX( zQitIBY3Zz-mUBrw7u$iTj>z%N{iuZ4Aks%hq~P6tbrRDC0GdS}jo=`hk*N~)|Mi>- zn1)%?qoR&o1d)jM5c^5?1r~&K3a}tzJ>(NhNOq=)6)}&<32^|01vwi)ezH%moZC%I zd0z51!Z7H_-e6bL5Ot<~4PGnU>JBInYQb4e8KmJD;yWg!lj%f1H1e?+vQdBpWQh$N z`h!DCkKk&yu3w$^PDB77P+`{aW$PJ`z6;A^fAhr&vdL^TLxWE0(R-tf&Y z`IJ+#i}J`@041_Q&_y_G5@Vp3S}spw0M1}5G~`ZQz5c~!6a!Dmp|^mP;rvc0!Gsj_ zSKqsNKQ_tC*D!=TUjrGGBf>z+3>uWf@8~TUbwxQ}i55(N7k-Ehsoz>Tsrr<+SzaF1 zC7)jv=Ouk9&%D(0yO6Ig^1S@3J3Q|17#mzOPzSB}Tb==M;Fx=s-Es9GuwJ-{_W<6< z_u@KDWPo2q>Mp-Hy*N+b>GeH+OzA2*tPg}xzs@|}V9lS)VucsdalOG6hu0)t9eF7w zsmtx3d7(A0NuOuxJ1lhbn8u^jz4swvFLFyMZ`}+knozQb-|iD@ z~fS6*E$GdQ6J)M-(yMSvey!T>BBQ?BP&07lr73Q1S#X z9Dg+{vw=Jn;DS+xg&8$WVJ#<3_F&B_LtX}Ua2{Lk!Yw7}9z=LJz8%CkiE$6!a~}0b z`q5849=m6148`%xJT<9lViaHSQ0tHXo)Lz|vmz(o#diI?$l;f9d)lOV%ySCgX;6#3 z27e&LusyKh+Qd9tHK2CLF3hd#uA>r`{ibqD`IKcDQwQuYzO~||3TOxe6;Kgdc;YRQ z&th@pdsP$TA8*k{nR(<<7x^s1vTV#}v4+y;`nj0wib}A7hVM-tl{2s+ z5<^k0{+fAAQ=VO6_FIMFti%*D4WO{8$jmd->`yA75ua?;m6*S#4AX2agIV4*mSe&k zFb3U00n=rS)0OUN@<^s~(}nB?R`6|O$1KY)yTdesWf6@3_aLH{|N9_L_}FpFq6u@# b&=ZDEW}Izh;E>_$Qd7_DezO;xnO^@5U$|tB delta 11404 zcmZu%d2E%}m5-|?N+z8V9i>VlWi&*SQKwb^V5Dd?f3zwi&1fN3D zNr>8yCQvZ8v5jq>7x1$IW45pbLmC2(X%fH~jCbRO=Y7Y!!9dzGzu&pv{ob?FPde{z z=bm%!x$8OSZL9m?wz?mld~*015t#{?9Rjmhmv(9>eFidwa6lfQJS}zPAJXzh9{byJ zlv3Q589@EuaTG`+&qet=pb;K>bO^O4e82v2*!urQj=YKiQ@-BYcctu1ubkN4EQP;o zkgRnzlDW28A!}W=*4NMqW^@y>W;>X-J_(61cSQ-%OQKlVMD*eF})T$L?zNq zXIiT1)|Xjt>R2Can~L|z|6YaMH|1P<>vVYnU^L4WCGGQ}U-=TA4EvEo?i7C8tJfty z{JJD9r~p(-BGOPuTKN6C&~}^OI_H|#FD)5vYiL z?=`3lI~P{TA6|h>NRt*2Dov_oH?MWfR|@eJ*KN1`6X%;TX}gPm*lCuLUY)QfEbln1 z$`_4VC`Qg3^-6N*!fK?|k}wbJV(v}NlT;Ac&a%M^)?1uvNt|B^^|EiUj``$8rth9N zZiUX*qZBC`;{3uvW)^^oQ;glQRTBSdxq;V^5>Vdu$_?52a+ykzyr>40U07IS>X3%k zC9@1*f6ZI`sp+%YY44j2VE%wI*pIvY1qY=WfB-G`NhU0m<=Fc_6WY*NrR=3R=^WYT;>wU_3<(`cD9^+TlRevQm* zmiS#h(x(Jd7u7345;b5L>~smTF8Y9)-w+B#Vm8eGgmdrE)elxNrTC|Y0}&plv&pRMzz=A>b9Z&XWF>!<#Mls?VK>TN`}pzrF_MZ_9+J$Pm$+$ zcPsZaN(V?=0_g!vS(iNDsLYm`QgxLfb5kNzn)W*xxd+xTf3?s&;?q0K_o|fF4g*jt-~*DjB3pTiU1x zo?fK5G`HiZ*|`7*pVE~+0+hd*?!)wjEZnunH|$C8B1>~@L<^z z{*B~*P$orLb%0vQe($Pmez_PoL4^(=ZE=$W=Gc|EIXBO_D4FkEmeal(g`j)~p!k-z zDikFK-(nmAXw9@#4}Z`hnZS#G?S|YNf2h^hi?E*|xNlh-+S&na!XwsxR6m7qvRB^L ztA>xnZF8?k!iq~+C7l*ePA9a<$@oA>8N?zCQ3_Y=4<3cG>ergO>SodPh4(5Rk0p9g-DcfLFNvK2OJ-<8(>J(=BcF zHMw!V>wAlD1XGh9Q7=WQEw-ENS=XR+GnTaCh;;&Tc2}F%n6;)BM^>|(&uP`2aL(5x zDGM4TV{xna*9D27!)zC{@BWQOK>r3^)mm5ZQN0c-JEBu8@TqIRqSRK|^%$nTE02S1)QY*Q^`z#xT=3gkiy%jSi}x39tKnXA9&oEt=u=yIB`1dS4(*}O?xihK zw5!$Y=U?9}IV-vV-8zuag_!#$&yL&DUhUWsOz0xoy|_)a;Zd13f_)p>B?lwOUePJF zB_oQ))xYl}JlE%!DR5nIXYu1!^}pVUIlpbK2PwgT6Java0?KgKlw<$5OHmqj1orYD zL)4u6Mw2^&9x&{hvXwXmJY?E&c=2PJu_PeACGFD(z!g7Jk??bTGlY-mmOU$ar68_L zoY14PyLnUsm3J(zreEg|O6JlI@hxqa#w)=Qe{-TmYQOEBviiA*&+Q5xW_jxalD)iJ z_C{i=Fb!Y&dOh0tZ?)i@Xnkqg2wJf9I_1#&!NrCiGu^{SJD*MJm8@l*N;EgBUyg0+ zrJA{zpT2rzNcKebpsi2gz*}8V`vYC*WyeP_@-8`>7!<<)I@SlYVeot9Y(lW@U|ctj zfObzlUKKtZY8=P~U%xjH%Ek{BdIVRT1E1wxvUf$F049jO*26P(9 zC}(*OXqiEuPZ>e`knD}>(^HuDo1S^h@J!$cD$bgzq|`=!9f2QZ>2JBOcqFI-McolU zAUASAM@2#X(F0)Gi+YR@FeN3P4#ur#*;#?nag<1jOoD_xkX}#B_Dm-g8u{&YF8rKjw0EaF?LLeVgeN%uq!9T*%~_zG&fDf#-Yv@vx^Bhw5TbR}AlE_d8)%j7?F`n@YcnnOxHS$|(;V_N^QN z)e$49@8;uSKYdm;2!?D5!l>l?zDDzN{z%i5A3dUL{$ghEOr=XPXfjhpT+v4LB|SyaWL9at z3#9ZIP8RHAYGnUPaE~^WnI{KR9@jj}`&W(20f5ctN1>lBvya(LCJ4xj&AD4Zoi`r6@6DkT*%g)r--n5VQKa|3_9^Hlql$(+AV9Y&8 zcMqKIYI-mhc`X~R+UDYTxV8`RLfULzrHq+)R(C}KR1>lwIp4D&o%%VWP|%Ot=pdxy zNyFX;VO5Nrsr7(6&Kx|QHUr=_2SQw*{CAn zhXx~Gsl4{M-U^|c!r1!;VtlXsQ`pa6Jmi)S#Xj)b$dRjjFAkxjAL0cQ+>azpuk$Y* zWTF7x`Y(mumcIrQgcFO?ZiYc-B>X_g;Lz%aQ&ZF();iR$Htj}0s0lQ5cUo}c!G zak&5Kpq9_e06@Qy{s*|X@HD^^gOkBwbMcX?a)^DX9Le?Y>*Q)Vo8jA`vu{o$9$KI^ zZTJB&n(3a~2uu8zgEH^TWPtZk^*kJMV>|SX#0#F9(QWS?1$R+4CllMaTXXPuFf!*1McjS+KE77i!_#;Ddd~<#9HtM{(ee zVbtl-Vo;!Cb?uLkA=MLrAv7^8=Ec0i{5nrQx?sgxq#T!Yc;ILKu!=IkvS`k6CoCIg%%_7v~@|8xR0U<%I$9t*(TBunaD{ zGT9Dq8eyAFwH{@-^H^7~sa~p!y*oHS&COZ@LL`rQzAudK>m!T|frk6VLEIOjQJ}u04`XJm#>RUR=F&|WjOj4^WZQyy#xRUK)3#+q z)qs{UDnWKLm^>GY40WOnsmrUq&<;a1AHRga9*Ed@ho)`_$L53Whz79D4dv{H3(hk0 z-YYY}D8PN?TX{>JEjH{z#|u5}bfD5#5?XKa2atmT~em54Yut-s5xcT70LVXrew$6kR9;cQy2{it-2UwYMQdiRy zydA1vE*~C%&!a`sBCDkC@{~Z7lz-WUjan%_c$1kA{f6K>&4}ezs8wG}Xxr^dTr_Xg zsT0?_F?}l=6i}k#K+zsW^?}I*prSr zU$k*oHkFRRW!0)a3TlPhhbJuK8C>ZM59CA5H(bB!#oR9aa|7wn<;lK`0&&zWRU;a5 zU<8| zmjepi0W(R7Bd2GD?jU+-Xbl#|(N_Tvj(T%UJ=^J*Qh{7gjOP@Kg6|7;>d$gLLZn53 zC;;zMzC2wmJLIcA2BhWvt@l-R#+76|HheHzR}UY1EHkAL))Arts}IC@Z%lvYt=9&A z3J5{}*Fft7Omi1pPn=dq0_Gr84~;HF+9f2YMl*a9hKTiFAa(s0)>T0NgzPDa(to{%IbQlU_?vB}l?g&9N3_^;dY)+Z2br&UeC_O0~{fPlyk^bz`$ z_u%5{IIwO&sTzCT*oJEPTKip7*E;s^tyHyKY4Xv6B_2}1ftYbQ3>}5IO!bk<7w|?h;ni_^3;eLy zG!)AkqsBV3Hlu4t!eX=bBu!#27p{U3E6u^<^cPbe?6oDMa%QX9l^m~{gVrcTs?tJg zYjhUA-HWw1O4<17gZvm^J@Wj8Buuf#R0hO3Czz1sLeDh%=eE!4g2MDC=)LQ zQ0#&qDeGvzJohiiL=SaM1}^#$BYU@AUr@X7M?mKJf4Y0)-v!tK$WJ-QY;L@YIKtXG zylO)J$fkcrGk;o3hehCCCR26?iPU^Gtgjuuc9U-)7Jst90Qe@{6;UZAe!U^7uDnAZI2}@Yx?66SPDmF*QU~7;=nJLp zt1&743}GZZE&^qD@!G-v5)kDZzqhaO_=k z-s=&IOt4A*DaV+z~nH>Lf0L*#Is<{>2a(xn=(1;VkI2(;aQV%Zdd~<-& zK{<+;mcD+JIoAO{5OY|6_{}lz=rZ0Y_=4O`fBZM&(o!}FRB=o%{-&KD`WtSTaLRP^ s#9jI9cVi%f_Y1BX=D$5KEF(R-VWPRaxcz3Z1Hca`b|D!lchrLa2RxK$m;e9( From 0d6adf160bcff5da41e5e2262b7e223163670179 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 May 2024 14:20:28 +0900 Subject: [PATCH 101/103] Share scale factor with hit target --- .../Skinning/Legacy/LegacyKiaiGlow.cs | 8 +++----- .../Skinning/Legacy/TaikoLegacyHitTarget.cs | 10 ++++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs index 487106d879..9877efa127 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs @@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy private Sprite sprite = null!; - private const float base_scale = 0.8f; - [BackgroundDependencyLoader(true)] private void load(ISkinSource skin, HealthProcessor? healthProcessor) { @@ -32,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy Origin = Anchor.Centre, Anchor = Anchor.Centre, Alpha = 0, - Scale = new Vector2(base_scale), + Scale = new Vector2(TaikoLegacyHitTarget.SCALE), Colour = new Colour4(255, 228, 0, 255), }; @@ -60,8 +58,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (!result.IsHit || !isKiaiActive) return; - sprite.ScaleTo(base_scale + 0.15f).Then() - .ScaleTo(base_scale, 80, Easing.OutQuad); + sprite.ScaleTo(TaikoLegacyHitTarget.SCALE + 0.15f).Then() + .ScaleTo(TaikoLegacyHitTarget.SCALE, 80, Easing.OutQuad); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs index 0b43f1c845..2a008d81d9 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs @@ -12,6 +12,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { public partial class TaikoLegacyHitTarget : CompositeDrawable { + /// + /// In stable this is 0.7f (see https://github.com/peppy/osu-stable-reference/blob/7519cafd1823f1879c0d9c991ba0e5c7fd3bfa02/osu!/GameModes/Play/Rulesets/Taiko/RulesetTaiko.cs#L592) + /// but for whatever reason this doesn't match visually. + /// + public const float SCALE = 0.8f; + [BackgroundDependencyLoader] private void load(ISkinSource skin) { @@ -22,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy new Sprite { Texture = skin.GetTexture("approachcircle"), - Scale = new Vector2(0.83f), + Scale = new Vector2(SCALE + 0.03f), Alpha = 0.47f, // eyeballed to match stable Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -30,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy new Sprite { Texture = skin.GetTexture("taikobigcircle"), - Scale = new Vector2(0.8f), + Scale = new Vector2(SCALE), Alpha = 0.22f, // eyeballed to match stable Anchor = Anchor.Centre, Origin = Anchor.Centre, From 11c3d11db9b66097d61f877cb2e9b602bf90b5e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 May 2024 14:38:43 +0900 Subject: [PATCH 102/103] Fix cinema mod not hiding playfield skin layer --- osu.Game/Rulesets/Mods/ModCinema.cs | 2 ++ osu.Game/Screens/Play/HUDOverlay.cs | 15 +++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 7c88a8a588..0c00eb6ae0 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -36,6 +36,8 @@ namespace osu.Game.Rulesets.Mods { overlay.ShowHud.Value = false; overlay.ShowHud.Disabled = true; + + overlay.PlayfieldSkinLayer.Hide(); } public void ApplyToPlayer(Player player) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 9d7a05bc90..16dfff8c19 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -109,7 +109,10 @@ namespace osu.Game.Screens.Play private readonly List hideTargets; - private readonly Drawable playfieldComponents; + /// + /// The container for skin components attached to + /// + internal readonly Drawable PlayfieldSkinLayer; public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) { @@ -129,7 +132,7 @@ namespace osu.Game.Screens.Play drawableRuleset != null ? (rulesetComponents = new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, }) : Empty(), - playfieldComponents = drawableRuleset != null + PlayfieldSkinLayer = drawableRuleset != null ? new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.Playfield, drawableRuleset.Ruleset.RulesetInfo)) { AlwaysPresent = true, } : Empty(), topRightElements = new FillFlowContainer @@ -247,10 +250,10 @@ namespace osu.Game.Screens.Play { Quad playfieldScreenSpaceDrawQuad = drawableRuleset.Playfield.SkinnableComponentScreenSpaceDrawQuad; - playfieldComponents.Position = ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft); - playfieldComponents.Width = (ToLocalSpace(playfieldScreenSpaceDrawQuad.TopRight) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length; - playfieldComponents.Height = (ToLocalSpace(playfieldScreenSpaceDrawQuad.BottomLeft) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length; - playfieldComponents.Rotation = drawableRuleset.Playfield.Rotation; + PlayfieldSkinLayer.Position = ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft); + PlayfieldSkinLayer.Width = (ToLocalSpace(playfieldScreenSpaceDrawQuad.TopRight) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length; + PlayfieldSkinLayer.Height = (ToLocalSpace(playfieldScreenSpaceDrawQuad.BottomLeft) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft)).Length; + PlayfieldSkinLayer.Rotation = drawableRuleset.Playfield.Rotation; } float? lowestTopScreenSpaceLeft = null; From bdfce4b9dafc90314e6b55f448e899d164ca1d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 May 2024 08:26:00 +0200 Subject: [PATCH 103/103] Fix xmldoc reference --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 16dfff8c19..0c0941573c 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Play private readonly List hideTargets; /// - /// The container for skin components attached to + /// The container for skin components attached to /// internal readonly Drawable PlayfieldSkinLayer;