diff --git a/Gemfile.lock b/Gemfile.lock index ab594aee74..e3954c2681 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.1) + CFPropertyList (3.0.2) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) @@ -18,8 +18,8 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.7.5) emoji_regex (1.0.1) - excon (0.67.0) - faraday (0.15.4) + excon (0.71.1) + faraday (0.17.3) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) faraday (>= 0.7.4) @@ -27,7 +27,7 @@ GEM faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) fastimage (2.1.7) - fastlane (2.133.0) + fastlane (2.140.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) @@ -36,13 +36,13 @@ GEM commander-fastlane (>= 4.4.6, < 5.0.0) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 2.0) - excon (>= 0.45.0, < 1.0.0) - faraday (< 0.16.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 0.17) faraday-cookie_jar (~> 0.0.6) - faraday_middleware (< 0.16.0) + faraday_middleware (~> 0.13.1) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.21.2, < 0.24.0) + google-api-client (>= 0.29.2, < 0.37.0) google-cloud-storage (>= 1.15.0, < 2.0.0) highline (>= 1.7.2, < 2.0.0) json (< 3.0.0) @@ -61,56 +61,58 @@ GEM tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) - xcodeproj (>= 1.8.1, < 2.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-clean_testflight_testers (0.3.0) - fastlane-plugin-souyuz (0.8.1) - souyuz (>= 0.8.1) + fastlane-plugin-souyuz (0.9.1) + souyuz (= 0.9.1) fastlane-plugin-xamarin (0.6.3) gh_inspector (1.1.3) - google-api-client (0.23.9) + google-api-client (0.36.4) addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.5, < 0.7.0) + googleauth (~> 0.9) httpclient (>= 2.8.1, < 3.0) - mime-types (~> 3.0) + mini_mime (~> 1.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - signet (~> 0.9) - google-cloud-core (1.3.1) + signet (~> 0.12) + google-cloud-core (1.5.0) google-cloud-env (~> 1.0) - google-cloud-env (1.2.1) + google-cloud-errors (~> 1.0) + google-cloud-env (1.3.0) faraday (~> 0.11) - google-cloud-storage (1.16.0) + google-cloud-errors (1.0.0) + google-cloud-storage (1.25.1) + addressable (~> 2.5) digest-crc (~> 0.4) - google-api-client (~> 0.23) + google-api-client (~> 0.33) google-cloud-core (~> 1.2) - googleauth (>= 0.6.2, < 0.10.0) - googleauth (0.6.7) + googleauth (~> 0.9) + mini_mime (~> 1.0) + googleauth (0.10.0) faraday (~> 0.12) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.7) + signet (~> 0.12) highline (1.7.10) http-cookie (1.0.3) domain_name (~> 0.5) httpclient (2.8.3) - json (2.2.0) + json (2.3.0) jwt (2.1.0) - memoist (0.16.0) - mime-types (3.3) - mime-types-data (~> 3.2015) - mime-types-data (3.2019.1009) - mini_magick (4.9.5) + memoist (0.16.2) + mini_magick (4.10.1) + mini_mime (1.0.2) mini_portile2 (2.4.0) - multi_json (1.13.1) + multi_json (1.14.1) multi_xml (0.6.0) multipart-post (2.0.0) nanaimo (0.2.6) naturally (2.2.0) - nokogiri (1.10.4) + nokogiri (1.10.7) mini_portile2 (~> 2.4.0) os (1.0.1) plist (3.5.0) @@ -128,12 +130,12 @@ GEM faraday (~> 0.9) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.6) + simctl (1.6.7) CFPropertyList naturally slack-notifier (2.3.2) - souyuz (0.8.1) - fastlane (>= 2.29.0) + souyuz (0.9.1) + fastlane (>= 1.103.0) highline (~> 1.7) nokogiri (~> 1.7) terminal-notifier (2.0.0) @@ -141,15 +143,15 @@ GEM unicode-display_width (~> 1.1, >= 1.1.1) tty-cursor (0.7.0) tty-screen (0.7.0) - tty-spinner (0.9.1) + tty-spinner (0.9.2) tty-cursor (~> 0.7) uber (0.1.0) unf (0.1.4) unf_ext unf_ext (0.0.7.6) - unicode-display_width (1.6.0) + unicode-display_width (1.6.1) word_wrap (1.0.0) - xcodeproj (1.12.0) + xcodeproj (1.14.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 28a83fbbae..510b53054b 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -111,7 +111,6 @@ platform :ios do souyuz( platform: "ios", - build_target: "osu_iOS", plist_path: "../osu.iOS/Info.plist" ) end diff --git a/osu.Android.props b/osu.Android.props index 9c0da1e9fd..5497a82a7a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index da47ad8223..b9294088f4 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index a47efcc10a..4c72b9fd3e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -36,7 +36,10 @@ namespace osu.Game.Rulesets.Catch.Mods //disable keyboard controls public bool OnPressed(CatchAction action) => true; - public bool OnReleased(CatchAction action) => true; + + public void OnReleased(CatchAction action) + { + } protected override bool OnMouseMove(MouseMoveEvent e) { diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 0c8c483048..1de0b6bfa3 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -103,7 +103,9 @@ namespace osu.Game.Rulesets.Catch.UI MovableCatcher.X = state.CatcherX.Value; } - public bool OnReleased(CatchAction action) => false; + public void OnReleased(CatchAction action) + { + } public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj); @@ -341,24 +343,22 @@ namespace osu.Game.Rulesets.Catch.UI return false; } - public bool OnReleased(CatchAction action) + public void OnReleased(CatchAction action) { switch (action) { case CatchAction.MoveLeft: currentDirection++; - return true; + break; case CatchAction.MoveRight: currentDirection--; - return true; + break; case CatchAction.Dash: Dashing = false; - return true; + break; } - - return false; } /// diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index b28d8bb0e6..7a3b42914e 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -54,10 +54,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return true; } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { EndPlacement(); - return base.OnMouseUp(e); + base.OnMouseUp(e); } public override void UpdatePosition(Vector2 screenSpacePosition) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 3bd7fb2d49..9f57160f99 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class ManiaSelectionBlueprint : SelectionBlueprint + public class ManiaSelectionBlueprint : OverlaySelectionBlueprint { public Vector2 ScreenSpaceDragPosition { get; private set; } public Vector2 DragPosition { get; private set; } @@ -55,14 +55,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return base.OnMouseDown(e); } - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { - var result = base.OnDrag(e); + base.OnDrag(e); ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition); - - return result; } public override void Show() diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs new file mode 100644 index 0000000000..d744036b4c --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs @@ -0,0 +1,34 @@ +// 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 osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class ManiaBlueprintContainer : ComposeBlueprintContainer + { + public ManiaBlueprintContainer(IEnumerable drawableHitObjects) + : base(drawableHitObjects) + { + } + + public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) + { + switch (hitObject) + { + case DrawableNote note: + return new NoteSelectionBlueprint(note); + + case DrawableHoldNote holdNote: + return new HoldNoteSelectionBlueprint(holdNote); + } + + return base.CreateBlueprintFor(hitObject); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 1632b6a583..62b609610f 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -5,11 +5,8 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Objects.Drawables; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -52,26 +49,12 @@ namespace osu.Game.Rulesets.Mania.Edit return drawableRuleset; } + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ManiaBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { new NoteCompositionTool(), new HoldNoteCompositionTool() }; - - public override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); - - public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) - { - switch (hitObject) - { - case DrawableNote note: - return new NoteSelectionBlueprint(note); - - case DrawableHoldNote holdNote: - return new HoldNoteSelectionBlueprint(holdNote); - } - - return base.CreateBlueprintFor(hitObject); - } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 618af3e772..9069a636a8 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.UI; @@ -70,10 +71,12 @@ namespace osu.Game.Rulesets.Mania.Edit // When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen. // This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height. if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - delta -= moveEvent.Blueprint.DrawableObject.Parent.DrawHeight; + delta -= moveEvent.Blueprint.Parent.DrawHeight; // todo: probably wrong - foreach (var b in SelectedBlueprints) + foreach (var selectionBlueprint in SelectedBlueprints) { + var b = (OverlaySelectionBlueprint)selectionBlueprint; + var hitObject = b.DrawableObject; var objectParent = (HitObjectContainer)hitObject.Parent; diff --git a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs index ff8882124f..433db79ae0 100644 --- a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mania.Edit.Masks { - public abstract class ManiaSelectionBlueprint : SelectionBlueprint + public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint { protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 02c2158383..b7b523a94d 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -237,19 +237,19 @@ namespace osu.Game.Rulesets.Mania { LeftKeys = new[] { - InputKey.Number1, - InputKey.Number2, - InputKey.Number3, - InputKey.Number4, + InputKey.Q, + InputKey.W, + InputKey.E, + InputKey.R, }, RightKeys = new[] { - InputKey.Z, InputKey.X, InputKey.C, - InputKey.V + InputKey.V, + InputKey.B }, - SpecialKey = InputKey.Tilde, + SpecialKey = InputKey.S, SpecialAction = ManiaAction.Special1, NormalActionStart = ManiaAction.Key1 }.GenerateKeyBindingsFor(keys, out var nextNormal); @@ -265,12 +265,12 @@ namespace osu.Game.Rulesets.Mania }, RightKeys = new[] { - InputKey.O, - InputKey.P, - InputKey.BracketLeft, - InputKey.BracketRight + InputKey.K, + InputKey.L, + InputKey.Semicolon, + InputKey.Quote }, - SpecialKey = InputKey.BackSlash, + SpecialKey = InputKey.I, SpecialAction = ManiaAction.Special2, NormalActionStart = nextNormal }.GenerateKeyBindingsFor(keys, out _); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 155adb958b..14a7c5fda3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -171,17 +171,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyPiece.Hitting = true; } - public bool OnReleased(ManiaAction action) + public void OnReleased(ManiaAction action) { if (AllJudged) - return false; + return; if (action != Action.Value) - return false; + return; // Make sure a hold was started if (HoldStartTime == null) - return false; + return; Tail.UpdateResult(); endHold(); @@ -189,8 +189,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // If the key has been released too early, the user should not receive full score for the release if (!Tail.IsHit) HasBroken = true; - - return true; } private void endHold() diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index a5d03bf765..390c64c5e2 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note - public override bool OnReleased(ManiaAction action) => false; // Handled by the hold note + public override void OnReleased(ManiaAction action) + { + } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index a660144dd1..568b07c958 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -59,6 +59,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note - public override bool OnReleased(ManiaAction action) => false; // Handled by the hold note + public override void OnReleased(ManiaAction action) + { + } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 8f353ae138..85613d3afb 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -77,6 +77,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return UpdateResult(true); } - public virtual bool OnReleased(ManiaAction action) => false; + public virtual void OnReleased(ManiaAction action) + { + } } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 3d2a070b0f..63c573d344 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -191,7 +191,9 @@ namespace osu.Game.Rulesets.Mania.UI return true; } - public bool OnReleased(ManiaAction action) => false; + public void OnReleased(ManiaAction action) + { + } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs index 57241da564..75cc351310 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs @@ -98,11 +98,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components return false; } - public bool OnReleased(ManiaAction action) + public void OnReleased(ManiaAction action) { if (action == this.action.Value) backgroundOverlay.FadeTo(0, 250, Easing.OutQuint); - return false; } } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs index 85880222d7..60fc2713b3 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs @@ -115,11 +115,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components return false; } - public bool OnReleased(ManiaAction action) + public void OnReleased(ManiaAction action) { if (action == this.action.Value) keyIcon.ScaleTo(1f, 125, Easing.OutQuint); - return false; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index a864257274..b0e13808a5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -7,10 +7,10 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit.Blueprints { - public abstract class OsuSelectionBlueprint : SelectionBlueprint + public abstract class OsuSelectionBlueprint : OverlaySelectionBlueprint where T : OsuHitObject { - protected T HitObject => (T)DrawableObject.HitObject; + protected new T HitObject => (T)DrawableObject.HitObject; protected OsuSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 6a0730db91..af4da5e853 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -135,13 +135,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; } - protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null; - protected override bool OnClick(ClickEvent e) => RequestSelection != null; protected override bool OnDragStart(DragStartEvent e) => e.Button == MouseButton.Left; - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { if (ControlPoint == slider.Path.ControlPoints[0]) { @@ -158,12 +156,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } else ControlPoint.Position.Value += e.Delta; - - return true; } - protected override bool OnDragEnd(DragEndEvent e) => true; - /// /// Updates the state of the circular control point marker. /// 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 6f583d7983..e293eba9d7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -108,7 +108,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; } - public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; + public void OnReleased(PlatformAction action) + { + } private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e) { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs index f09279ed73..a0392fe536 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders : base(slider) { this.position = position; + InternalChild = CirclePiece = new HitCirclePiece(); Select(); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 2497e428fc..90512849d4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -106,11 +106,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders return true; } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { if (state == PlacementState.Body && e.Button == MouseButton.Right) endCurve(); - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected override bool OnDoubleClick(DoubleClickEvent e) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 3165c441fb..c18b3b0ff3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -90,19 +90,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnDragStart(DragStartEvent e) => placementControlPointIndex != null; - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { Debug.Assert(placementControlPointIndex != null); HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position.Value = e.MousePosition - HitObject.Position; - - return true; } - protected override bool OnDragEnd(DragEndEvent e) + protected override void OnDragEnd(DragEndEvent e) { placementControlPointIndex = null; - return true; } private BindableList controlPoints => HitObject.Path.ControlPoints; @@ -173,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), }; - public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint; + public override Vector2 SelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs new file mode 100644 index 0000000000..330f34b85c --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -0,0 +1,41 @@ +// 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 osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuBlueprintContainer : ComposeBlueprintContainer + { + public OsuBlueprintContainer(IEnumerable drawableHitObjects) + : base(drawableHitObjects) + { + } + + protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); + + public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) + { + switch (hitObject) + { + case DrawableHitCircle circle: + return new HitCircleSelectionBlueprint(circle); + + case DrawableSlider slider: + return new SliderSelectionBlueprint(slider); + + case DrawableSpinner spinner: + return new SpinnerSelectionBlueprint(spinner); + } + + return base.CreateBlueprintFor(hitObject); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 49624ea733..b01488e7c2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -9,12 +9,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; -using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; -using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; @@ -37,24 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit new SpinnerCompositionTool() }; - public override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); - - public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) - { - switch (hitObject) - { - case DrawableHitCircle circle: - return new HitCircleSelectionBlueprint(circle); - - case DrawableSlider slider: - return new SliderSelectionBlueprint(slider); - - case DrawableSpinner spinner: - return new SpinnerSelectionBlueprint(spinner); - } - - return base.CreateBlueprintFor(hitObject); - } + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects); protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable selectedHitObjects) { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index f74f2d7bc5..3162f4b379 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -205,7 +205,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return false; } - public bool OnReleased(OsuAction action) => false; + public void OnReleased(OsuAction action) + { + } } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 6433ced624..a463542e64 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor return false; } - public bool OnReleased(OsuAction action) + public void OnReleased(OsuAction action) { switch (action) { @@ -120,8 +120,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor updateExpandedState(); break; } - - return false; } public override bool HandlePositionalInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 3b18e41f30..ca3030db3e 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -107,7 +107,9 @@ namespace osu.Game.Rulesets.Osu.UI return false; } - public bool OnReleased(OsuAction action) => false; + public void OnReleased(OsuAction action) + { + } public void Appear() => Schedule(() => { diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 4b25ff0ecc..85dfc8d5e0 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -77,11 +77,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return result; } - public override bool OnReleased(TaikoAction action) + public override void OnReleased(TaikoAction action) { if (action == HitAction) HitAction = null; - return base.OnReleased(action); + + base.OnReleased(action); } protected override void Update() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index b9d31ff906..5f892dd2fa 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -77,7 +77,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public Drawable CreateProxiedContent() => proxiedContent.CreateProxy(); public abstract bool OnPressed(TaikoAction action); - public virtual bool OnReleased(TaikoAction action) => false; + + public virtual void OnReleased(TaikoAction action) + { + } public override double LifetimeStart { diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 5234ae1f69..d26ccfe867 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -187,7 +187,9 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public bool OnReleased(TaikoAction action) => false; + public void OnReleased(TaikoAction action) + { + } } } } diff --git a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs index 2d336bd19c..e825df5a3f 100644 --- a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Edit; @@ -19,7 +20,13 @@ namespace osu.Game.Tests.Editor private TestHitObjectComposer composer; [Cached(typeof(EditorBeatmap))] - private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + [Cached(typeof(IBeatSnapProvider))] + private readonly EditorBeatmap editorBeatmap; + + public TestSceneHitObjectComposerDistanceSnapping() + { + editorBeatmap = new EditorBeatmap(new OsuBeatmap(), BeatDivisor); + } [SetUp] public void Setup() => Schedule(() => diff --git a/osu.Game.Tests/Online/TestAPIModSerialization.cs b/osu.Game.Tests/Online/TestAPIModSerialization.cs new file mode 100644 index 0000000000..d9318aa822 --- /dev/null +++ b/osu.Game.Tests/Online/TestAPIModSerialization.cs @@ -0,0 +1,82 @@ +// 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 Newtonsoft.Json; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Online.API; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Online +{ + [TestFixture] + public class TestAPIModSerialization + { + [Test] + public void TestAcronymIsPreserved() + { + var apiMod = new APIMod(new TestMod()); + + var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); + + Assert.That(deserialized.Acronym, Is.EqualTo(apiMod.Acronym)); + } + + [Test] + public void TestRawSettingIsPreserved() + { + var apiMod = new APIMod(new TestMod { TestSetting = { Value = 2 } }); + + var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); + + Assert.That(deserialized.Settings, Contains.Key("test_setting").With.ContainValue(2.0)); + } + + [Test] + public void TestConvertedModHasCorrectSetting() + { + var apiMod = new APIMod(new TestMod { TestSetting = { Value = 2 } }); + + var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); + var converted = (TestMod)deserialized.ToMod(new TestRuleset()); + + Assert.That(converted.TestSetting.Value, Is.EqualTo(2)); + } + + private class TestRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) => new[] { new TestMod() }; + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new System.NotImplementedException(); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException(); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new System.NotImplementedException(); + + public override string Description { get; } = string.Empty; + public override string ShortName { get; } = string.Empty; + } + + private class TestMod : Mod + { + public override string Name => "Test Mod"; + public override string Acronym => "TM"; + public override double ScoreMultiplier => 1; + + [SettingSource("Test")] + public BindableNumber TestSetting { get; } = new BindableDouble + { + MinValue = 0, + MaxValue = 10, + Default = 5, + Precision = 0.01, + }; + } + } +} diff --git a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs index 3562689482..a8830824c0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; @@ -14,6 +15,7 @@ namespace osu.Game.Tests.Visual.Editor public class TestSceneComposeScreen : EditorClockTestScene { [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap { diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs index c001c83877..e41c2427fb 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs @@ -66,6 +66,7 @@ namespace osu.Game.Tests.Visual.Editor Dependencies.CacheAs(clock); Dependencies.CacheAs(clock); Dependencies.CacheAs(editorBeatmap); + Dependencies.CacheAs(editorBeatmap); Child = new OsuHitObjectComposer(new OsuRuleset()); } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs similarity index 92% rename from osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs rename to osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs index 29575cb42e..e7b2508ac7 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -22,12 +23,11 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestSceneEditorComposeTimeline : EditorClockTestScene + public class TestSceneTimelineBlueprintContainer : EditorClockTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(TimelineArea), - typeof(TimelineHitObjectDisplay), typeof(Timeline), typeof(TimelineButton), typeof(CentreMarker) @@ -38,7 +38,10 @@ namespace osu.Game.Tests.Visual.Editor { Beatmap.Value = new WaveformTestBeatmap(audio); - var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap); + var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap, BeatDivisor); + + Dependencies.Cache(editorBeatmap); + Dependencies.CacheAs(editorBeatmap); Children = new Drawable[] { @@ -55,7 +58,7 @@ namespace osu.Game.Tests.Visual.Editor }, new TimelineArea { - Child = new TimelineHitObjectDisplay(editorBeatmap), + Child = new TimelineBlueprintContainer(), Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs similarity index 75% rename from osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 72fc6d8bd2..8904b54b0d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -19,18 +19,22 @@ using osu.Game.Screens.Play.HUD.HitErrorMeters; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneBarHitErrorMeter : OsuTestScene + public class TestSceneHitErrorMeter : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(HitErrorMeter), + typeof(BarHitErrorMeter), + typeof(ColourHitErrorMeter) }; - private HitErrorMeter meter; - private HitErrorMeter meter2; + private BarHitErrorMeter barMeter; + private BarHitErrorMeter barMeter2; + private ColourHitErrorMeter colourMeter; + private ColourHitErrorMeter colourMeter2; private HitWindows hitWindows; - public TestSceneBarHitErrorMeter() + public TestSceneHitErrorMeter() { recreateDisplay(new OsuHitWindows(), 5); @@ -91,17 +95,31 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(meter = new BarHitErrorMeter(hitWindows, true) + Add(barMeter = new BarHitErrorMeter(hitWindows, true) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }); - Add(meter2 = new BarHitErrorMeter(hitWindows, false) + Add(barMeter2 = new BarHitErrorMeter(hitWindows, false) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }); + + Add(colourMeter = new ColourHitErrorMeter(hitWindows) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 50 } + }); + + Add(colourMeter2 = new ColourHitErrorMeter(hitWindows) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 50 } + }); } private void newJudgement(double offset = 0) @@ -112,8 +130,10 @@ namespace osu.Game.Tests.Visual.Gameplay Type = HitResult.Perfect, }; - meter.OnNewJudgement(judgement); - meter2.OnNewJudgement(judgement); + barMeter.OnNewJudgement(judgement); + barMeter2.OnNewJudgement(judgement); + colourMeter.OnNewJudgement(judgement); + colourMeter2.OnNewJudgement(judgement); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 9a217ae416..b9b13d7bd8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -1,12 +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 System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Framework.Timing; +using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Play; @@ -15,63 +20,125 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneSongProgress : OsuTestScene { - private readonly SongProgress progress; - private readonly TestSongProgressGraph graph; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SongProgressBar), + }; + + private SongProgress progress; + private TestSongProgressGraph graph; + private readonly Container progressContainer; private readonly StopwatchClock clock; + private readonly FramedClock framedClock; [Cached] private readonly GameplayClock gameplayClock; - private readonly FramedClock framedClock; - public TestSceneSongProgress() { - clock = new StopwatchClock(true); - + clock = new StopwatchClock(); gameplayClock = new GameplayClock(framedClock = new FramedClock(clock)); - Add(progress = new SongProgress + Add(progressContainer = new Container { RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Height = 100, + Y = -100, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(1), + } }); - - Add(graph = new TestSongProgressGraph - { - RelativeSizeAxes = Axes.X, - Height = 200, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - }); - - AddWaitStep("wait some", 5); - AddAssert("ensure not created", () => graph.CreationCount == 0); - - AddStep("display values", displayNewValues); - AddWaitStep("wait some", 5); - AddUntilStep("wait for creation count", () => graph.CreationCount == 1); - - AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep("wait some", 5); - AddUntilStep("wait for creation count", () => graph.CreationCount == 1); - - AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep("wait some", 5); - AddUntilStep("wait for creation count", () => graph.CreationCount == 1); - AddRepeatStep("New Values", displayNewValues, 5); - - AddWaitStep("wait some", 5); - AddAssert("ensure debounced", () => graph.CreationCount == 2); } - private void displayNewValues() + [SetUpSteps] + public void SetupSteps() { - List objects = new List(); + AddStep("add new song progress", () => + { + if (progress != null) + { + progress.Expire(); + progress = null; + } + + progressContainer.Add(progress = new SongProgress + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }); + }); + + AddStep("add new big graph", () => + { + if (graph != null) + { + graph.Expire(); + graph = null; + } + + Add(graph = new TestSongProgressGraph + { + RelativeSizeAxes = Axes.X, + Height = 200, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }); + }); + + AddStep("reset clock", clock.Reset); + } + + [Test] + public void TestGraphRecreation() + { + AddAssert("ensure not created", () => graph.CreationCount == 0); + AddStep("display values", displayRandomValues); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); + AddRepeatStep("new values", displayRandomValues, 5); + AddWaitStep("wait some", 5); + AddAssert("ensure recreation debounced", () => graph.CreationCount == 2); + } + + [Test] + public void TestDisplay() + { + AddStep("display max values", displayMaxValues); + AddUntilStep("wait for graph", () => graph.CreationCount == 1); + AddStep("start", clock.Start); + AddStep("allow seeking", () => progress.AllowSeeking.Value = true); + AddStep("hide graph", () => progress.ShowGraph.Value = false); + AddStep("disallow seeking", () => progress.AllowSeeking.Value = false); + AddStep("allow seeking", () => progress.AllowSeeking.Value = true); + AddStep("show graph", () => progress.ShowGraph.Value = true); + AddStep("stop", clock.Stop); + } + + private void displayRandomValues() + { + var objects = new List(); for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) objects.Add(new HitObject { StartTime = i }); + replaceObjects(objects); + } + + private void displayMaxValues() + { + var objects = new List(); + for (double i = 0; i < 5000; i++) + objects.Add(new HitObject { StartTime = i }); + + replaceObjects(objects); + } + + private void replaceObjects(List objects) + { progress.Objects = objects; graph.Objects = objects; diff --git a/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs b/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs new file mode 100644 index 0000000000..d7f23f5cc0 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs @@ -0,0 +1,36 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + public class TestSceneSongTicker : OsuTestScene + { + [Cached] + private MusicController musicController = new MusicController(); + + public TestSceneSongTicker() + { + AddRange(new Drawable[] + { + musicController, + new SongTicker + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new NowPlayingOverlay + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + State = { Value = Visibility.Visible } + } + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 658f678b10..7a8570c09b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneChangelogOverlay : OsuTestScene { - private ChangelogOverlay changelog; + private TestChangelogOverlay changelog; public override IReadOnlyList RequiredTypes => new[] { @@ -29,23 +29,40 @@ namespace osu.Game.Tests.Visual.Online protected override bool UseOnlineAPI => true; - protected override void LoadComplete() + [SetUp] + public void SetUp() => Schedule(() => { - base.LoadComplete(); + Child = changelog = new TestChangelogOverlay(); + }); - Add(changelog = new ChangelogOverlay()); - AddStep(@"Show", changelog.Show); - AddStep(@"Hide", changelog.Hide); + [Test] + public void ShowWithNoFetch() + { + AddStep(@"Show", () => changelog.Show()); + AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0); + AddAssert(@"listing displayed", () => changelog.Current.Value == null); + AddAssert(@"no stream selected", () => changelog.Header.Streams.Current.Value == null); + } - AddWaitStep("wait for hide", 3); + [Test] + public void ShowWithListing() + { + AddStep(@"Show with listing", () => changelog.ShowListing()); + AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0); + AddAssert(@"listing displayed", () => changelog.Current.Value == null); + AddAssert(@"no stream selected", () => changelog.Header.Streams.Current.Value == null); + } + [Test] + public void ShowWithBuild() + { AddStep(@"Show with Lazer 2018.712.0", () => { changelog.ShowBuild(new APIChangelogBuild { Version = "2018.712.0", DisplayVersion = "2018.712.0", - UpdateStream = new APIUpdateStream { Name = OsuGameBase.CLIENT_STREAM_NAME }, + UpdateStream = new APIUpdateStream { Id = 7, Name = OsuGameBase.CLIENT_STREAM_NAME }, ChangelogEntries = new List { new APIChangelogEntry @@ -56,19 +73,16 @@ namespace osu.Game.Tests.Visual.Online } } }); - changelog.Show(); }); - AddWaitStep("wait for show", 3); - AddStep(@"Hide", changelog.Hide); - AddWaitStep("wait for hide", 3); - - AddStep(@"Show with listing", () => - { - changelog.ShowListing(); - changelog.Show(); - }); + AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0); + AddAssert(@"correct build displayed", () => changelog.Current.Value.Version == "2018.712.0"); + AddAssert(@"correct stream selected", () => changelog.Header.Streams.Current.Value.Id == 7); + } + [Test] + public void TestHTMLUnescaping() + { AddStep(@"Ensure HTML string unescaping", () => { changelog.ShowBuild(new APIChangelogBuild @@ -97,5 +111,12 @@ namespace osu.Game.Tests.Visual.Online }); }); } + + private class TestChangelogOverlay : ChangelogOverlay + { + public new List Streams => base.Streams; + + public new ChangelogHeader Header => base.Header; + } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 86bd0ddd11..8134c10750 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -30,29 +30,23 @@ namespace osu.Game.Tests.Visual.Online public TestSceneCommentsContainer() { - BasicScrollContainer scrollFlow; + BasicScrollContainer scroll; + CommentsContainer comments; - Add(scrollFlow = new BasicScrollContainer + Add(scroll = new BasicScrollContainer { RelativeSizeAxes = Axes.Both, + Child = comments = new CommentsContainer() }); - AddStep("Big Black comments", () => + AddStep("Big Black comments", () => comments.ShowComments(CommentableType.Beatmapset, 41823)); + AddStep("Airman comments", () => comments.ShowComments(CommentableType.Beatmapset, 24313)); + AddStep("Lazer build comments", () => comments.ShowComments(CommentableType.Build, 4772)); + AddStep("News comments", () => comments.ShowComments(CommentableType.NewsPost, 715)); + AddStep("Idle state", () => { - scrollFlow.Clear(); - scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 41823)); - }); - - AddStep("Airman comments", () => - { - scrollFlow.Clear(); - scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 24313)); - }); - - AddStep("lazer build comments", () => - { - scrollFlow.Clear(); - scrollFlow.Add(new CommentsContainer(CommentableType.Build, 4772)); + scroll.Clear(); + scroll.Add(comments = new CommentsContainer()); }); } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs index fe8437be17..e60adcee34 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs @@ -41,6 +41,7 @@ namespace osu.Game.Tests.Visual.Online private class TestFullscreenOverlay : FullscreenOverlay { public TestFullscreenOverlay() + : base(OverlayColourScheme.Pink) { Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 63b46c991f..f825e4f1e9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -31,6 +31,9 @@ namespace osu.Game.Tests.Visual.Online typeof(ProfileHeaderButton) }; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + [Resolved] private IAPIProvider api { get; set; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 132b104afb..71ae47dc66 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -437,6 +437,53 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selection was random", () => eagerSelectedIDs.Count > 1); } + [Test] + public void TestFilteringByUserStarDifficulty() + { + BeatmapSetInfo set = null; + + loadBeatmaps(new List()); + + AddStep("add mixed difficulty set", () => + { + set = createTestBeatmapSet(1); + set.Beatmaps.Clear(); + + for (int i = 1; i <= 15; i++) + { + set.Beatmaps.Add(new BeatmapInfo + { + Version = $"Stars: {i}", + StarDifficulty = i, + }); + } + + carousel.UpdateBeatmapSet(set); + }); + + AddStep("select added set", () => carousel.SelectBeatmap(set.Beatmaps[0], false)); + + AddStep("filter [5..]", () => carousel.Filter(new FilterCriteria { UserStarDifficulty = { Min = 5 } })); + AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask); + checkVisibleItemCount(true, 11); + + AddStep("filter to [0..7]", () => carousel.Filter(new FilterCriteria { UserStarDifficulty = { Max = 7 } })); + AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask); + checkVisibleItemCount(true, 7); + + AddStep("filter to [5..7]", () => carousel.Filter(new FilterCriteria { UserStarDifficulty = { Min = 5, Max = 7 } })); + AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask); + checkVisibleItemCount(true, 3); + + AddStep("filter [2..2]", () => carousel.Filter(new FilterCriteria { UserStarDifficulty = { Min = 2, Max = 2 } })); + AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask); + checkVisibleItemCount(true, 1); + + AddStep("filter to [0..]", () => carousel.Filter(new FilterCriteria { UserStarDifficulty = { Min = 0 } })); + AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask); + checkVisibleItemCount(true, 15); + } + private void loadBeatmaps(List beatmapSets = null) { createCarousel(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControl.cs index 19eebc89b6..3967b62c95 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControl.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -12,11 +14,11 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestSceneBreadcrumbControl : OsuTestScene { - private readonly BreadcrumbControl breadcrumbs; + private readonly TestBreadcrumbControl breadcrumbs; public TestSceneBreadcrumbControl() { - Add(breadcrumbs = new BreadcrumbControl + Add(breadcrumbs = new TestBreadcrumbControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -25,8 +27,13 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddStep(@"first", () => breadcrumbs.Current.Value = BreadcrumbTab.Click); + assertVisible(1); + AddStep(@"second", () => breadcrumbs.Current.Value = BreadcrumbTab.The); + assertVisible(2); + AddStep(@"third", () => breadcrumbs.Current.Value = BreadcrumbTab.Circles); + assertVisible(3); } [BackgroundDependencyLoader] @@ -35,11 +42,27 @@ namespace osu.Game.Tests.Visual.UserInterface breadcrumbs.StripColour = colours.Blue; } + private void assertVisible(int count) => AddAssert($"first {count} item(s) visible", () => + { + for (int i = 0; i < count; i++) + { + if (breadcrumbs.GetDrawable((BreadcrumbTab)i).State != Visibility.Visible) + return false; + } + + return true; + }); + private enum BreadcrumbTab { Click, The, Circles, } + + private class TestBreadcrumbControl : BreadcrumbControl + { + public BreadcrumbTabItem GetDrawable(BreadcrumbTab tab) => (BreadcrumbTabItem)TabContainer.First(t => t.Value == tab); + } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 8dcb7dcbf8..e2ce2341e5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -25,6 +25,8 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly Mod testCustomisableMod = new TestModCustomisable1(); + private readonly Mod testCustomisableAutoOpenMod = new TestModCustomisable2(); + [Test] public void TestButtonShowsOnCustomisableMod() { @@ -53,6 +55,17 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); } + [Test] + public void TestCustomisationOpensOnModSelect() + { + createModSelect(); + + AddStep("open", () => modSelect.Show()); + AddAssert("Customisation closed", () => modSelect.ModSettingsContainer.Alpha == 0); + AddStep("select mod", () => modSelect.SelectMod(testCustomisableAutoOpenMod)); + AddAssert("Customisation opened", () => modSelect.ModSettingsContainer.Alpha == 1); + } + private void createModSelect() { AddStep("create mod select", () => @@ -128,6 +141,8 @@ namespace osu.Game.Tests.Visual.UserInterface public override string Name => "Customisable Mod 2"; public override string Acronym => "CM2"; + + public override bool RequiresConfiguration => true; } private abstract class TestModCustomisable : Mod, IApplicableMod diff --git a/osu.Game.Tournament/Resources/countries.json b/osu.Game.Tournament/Resources/countries.json index ec2ca2bf37..7306a8bec5 100644 --- a/osu.Game.Tournament/Resources/countries.json +++ b/osu.Game.Tournament/Resources/countries.json @@ -541,7 +541,7 @@ }, { "FlagName": "MK", - "FullName": "Macedonia", + "FullName": "North Macedonia", "Acronym": "MKD" }, { @@ -811,7 +811,7 @@ }, { "FlagName": "CV", - "FullName": "Cape Verde", + "FullName": "Cabo Verde", "Acronym": "CPV" }, { @@ -821,7 +821,7 @@ }, { "FlagName": "SZ", - "FullName": "Swaziland", + "FullName": "Eswatini", "Acronym": "SWZ" }, { diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs index dde280ccd8..c4b670f059 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -289,16 +289,15 @@ namespace osu.Game.Tournament.Screens.Ladder.Components return true; } - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { - if (base.OnDrag(e)) return true; + base.OnDrag(e); Selected = true; this.MoveToOffset(e.Delta); var pos = Position; Match.Position.Value = new Point((int)pos.X, (int)pos.Y); - return true; } public void Remove() diff --git a/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs b/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs index 84a329085a..cb73985b11 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs @@ -10,8 +10,8 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { public class ProgressionPath : Path { - public DrawableTournamentMatch Source { get; private set; } - public DrawableTournamentMatch Destination { get; private set; } + public DrawableTournamentMatch Source { get; } + public DrawableTournamentMatch Destination { get; } public ProgressionPath(DrawableTournamentMatch source, DrawableTournamentMatch destination) { diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs index 0c450a66b4..bdaa1ae7fd 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs @@ -22,10 +22,9 @@ namespace osu.Game.Tournament.Screens.Ladder protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { this.MoveTo(target += e.Delta, 1000, Easing.OutQuint); - return true; } private const float min_scale = 0.6f; diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 17d7dc0245..1c94856a4e 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -303,7 +303,7 @@ namespace osu.Game.Tournament private class TournamentInputManager : UserInputManager { - protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button) + protected override MouseButtonEventManager CreateButtonEventManagerFor(MouseButton button) { switch (button) { @@ -311,7 +311,7 @@ namespace osu.Game.Tournament return new RightMouseManager(button); } - return base.CreateButtonManagerFor(button); + return base.CreateButtonEventManagerFor(button); } private class RightMouseManager : MouseButtonEventManager diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 66c07209f3..2406b0bef2 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -19,6 +19,6 @@ namespace osu.Game.Audio public IEnumerable LookupNames => new[] { sampleName }; - public int Volume { get; set; } = 100; + public int Volume { get; } = 100; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 447d52d980..009da0656b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -239,11 +239,11 @@ namespace osu.Game.Beatmaps.Formats break; case @"Source": - beatmap.BeatmapInfo.Metadata.Source = pair.Value; + metadata.Source = pair.Value; break; case @"Tags": - beatmap.BeatmapInfo.Metadata.Tags = pair.Value; + metadata.Tags = pair.Value; break; case @"BeatmapID": @@ -300,13 +300,11 @@ namespace osu.Game.Beatmaps.Formats switch (type) { case LegacyEventType.Background: - string bgFilename = split[2].Trim('"'); - beatmap.BeatmapInfo.Metadata.BackgroundFile = bgFilename.ToStandardisedPath(); + beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]); break; case LegacyEventType.Video: - string videoFilename = split[2].Trim('"'); - beatmap.BeatmapInfo.Metadata.VideoFile = videoFilename.ToStandardisedPath(); + beatmap.BeatmapInfo.Metadata.VideoFile = CleanFilename(split[2]); break; case LegacyEventType.Break: diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index f55e24245b..0ec80eee41 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; @@ -115,7 +116,7 @@ namespace osu.Game.Beatmaps.Formats protected KeyValuePair SplitKeyVal(string line, char separator = ':') { - var split = line.Trim().Split(new[] { separator }, 2); + var split = line.Split(separator, 2); return new KeyValuePair ( @@ -124,6 +125,8 @@ namespace osu.Game.Beatmaps.Formats ); } + protected string CleanFilename(string path) => path.Trim('"').ToStandardisedPath(); + protected enum Section { None, diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index c46634e72f..35576e0f33 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -7,7 +7,6 @@ using System.Globalization; using System.IO; using osuTK; using osuTK.Graphics; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.IO; using osu.Game.Storyboards; @@ -65,13 +64,16 @@ namespace osu.Game.Beatmaps.Formats private void handleEvents(string line) { var depth = 0; + var lineSpan = line.AsSpan(); - while (line.StartsWith(" ", StringComparison.Ordinal) || line.StartsWith("_", StringComparison.Ordinal)) + while (lineSpan.StartsWith(" ", StringComparison.Ordinal) || lineSpan.StartsWith("_", StringComparison.Ordinal)) { + lineSpan = lineSpan.Slice(1); ++depth; - line = line.Substring(1); } + line = lineSpan.ToString(); + decodeVariables(ref line); string[] split = line.Split(','); @@ -89,7 +91,7 @@ namespace osu.Game.Beatmaps.Formats { var layer = parseLayer(split[1]); var origin = parseOrigin(split[2]); - var path = cleanFilename(split[3]); + var path = CleanFilename(split[3]); var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); @@ -101,7 +103,7 @@ namespace osu.Game.Beatmaps.Formats { var layer = parseLayer(split[1]); var origin = parseOrigin(split[2]); - var path = cleanFilename(split[3]); + var path = CleanFilename(split[3]); var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo); var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); var frameCount = int.Parse(split[6]); @@ -116,7 +118,7 @@ namespace osu.Game.Beatmaps.Formats { var time = double.Parse(split[1], CultureInfo.InvariantCulture); var layer = parseLayer(split[2]); - var path = cleanFilename(split[3]); + var path = CleanFilename(split[3]); var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; storyboard.GetLayer(layer).Add(new StoryboardSampleInfo(path, time, (int)volume)); break; @@ -331,7 +333,5 @@ namespace osu.Game.Beatmaps.Formats break; } } - - private string cleanFilename(string path) => path.Trim('"').ToStandardisedPath(); } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 947e864a87..4155381790 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -24,7 +24,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowConvertedBeatmaps, true); Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); - Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); + Set(OsuSetting.DisplayStarsMaximum, 10.1, 0, 10.1, 0.1); Set(OsuSetting.SongSelectGroupingMode, GroupMode.All); Set(OsuSetting.SongSelectSortingMode, SortMode.Title); @@ -85,6 +85,7 @@ namespace osu.Game.Configuration Set(OsuSetting.HitLighting, true); Set(OsuSetting.ShowInterface, true); + Set(OsuSetting.ShowProgressGraph, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.KeyOverlay, false); Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); @@ -150,6 +151,7 @@ namespace osu.Game.Configuration ScoreMeter, FloatingComments, ShowInterface, + ShowProgressGraph, ShowHealthDisplayWhenCantFail, MouseDisableButtons, MouseDisableWheel, diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index b85ef9309d..156c4b1377 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -18,5 +18,14 @@ namespace osu.Game.Configuration [Description("Hit Error (both)")] HitErrorBoth, + + [Description("Colour (left)")] + ColourLeft, + + [Description("Colour (right)")] + ColourRight, + + [Description("Colour (both)")] + ColourBoth, } } diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 056fa8bcc0..f859dccc80 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -35,16 +35,11 @@ namespace osu.Game.Configuration { public static IEnumerable CreateSettingsControls(this object obj) { - foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)) + foreach (var (attr, property) in obj.GetSettingsSourceProperties()) { - var attr = property.GetCustomAttribute(true); + object value = property.GetValue(obj); - if (attr == null) - continue; - - var prop = property.GetValue(obj); - - switch (prop) + switch (value) { case BindableNumber bNumber: yield return new SettingsSlider @@ -102,9 +97,22 @@ namespace osu.Game.Configuration break; default: - throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} was attached to an unsupported type ({prop})"); + throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} was attached to an unsupported type ({value})"); } } } + + public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj) + { + foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)) + { + var attr = property.GetCustomAttribute(true); + + if (attr == null) + continue; + + yield return (attr, property); + } + } } } diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index facf70b47a..93ac69bdbf 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -76,12 +76,12 @@ namespace osu.Game.Graphics.Containers return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { if (closeOnMouseUp && !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) Hide(); - return base.OnMouseUp(e); + base.OnMouseUp(e); } public virtual bool OnPressed(GlobalAction action) @@ -99,7 +99,9 @@ namespace osu.Game.Graphics.Containers return false; } - public bool OnReleased(GlobalAction action) => false; + public void OnReleased(GlobalAction action) + { + } protected override void UpdateState(ValueChangedEvent state) { diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index cfd459da5e..6d531887ed 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -50,15 +50,15 @@ namespace osu.Game.Graphics.Containers return base.OnMouseDown(e); } - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { if (rightMouseDragging) { scrollFromMouseEvent(e); - return true; + return; } - return base.OnDrag(e); + base.OnDrag(e); } protected override bool OnDragStart(DragStartEvent e) @@ -72,15 +72,15 @@ namespace osu.Game.Graphics.Containers return base.OnDragStart(e); } - protected override bool OnDragEnd(DragEndEvent e) + protected override void OnDragEnd(DragEndEvent e) { if (rightMouseDragging) { rightMouseDragging = false; - return true; + return; } - return base.OnDragEnd(e); + base.OnDragEnd(e); } protected override bool OnScroll(ScrollEvent e) @@ -162,13 +162,13 @@ namespace osu.Game.Graphics.Containers return true; } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { - if (e.Button != MouseButton.Left) return false; + if (e.Button != MouseButton.Left) return; box.FadeColour(Color4.White, 100); - return base.OnMouseUp(e); + base.OnMouseUp(e); } } } diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 170ea63059..580177d17a 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -92,7 +92,7 @@ namespace osu.Game.Graphics.Cursor return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { if (!e.IsPressed(MouseButton.Left) && !e.IsPressed(MouseButton.Right)) { @@ -107,7 +107,7 @@ namespace osu.Game.Graphics.Cursor dragRotationState = DragRotationState.NotDragging; } - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected override void PopIn() diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 6767104576..c8298543a1 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -3,7 +3,6 @@ using System; using osu.Game.Beatmaps; -using osuTK; using osuTK.Graphics; namespace osu.Game.Graphics @@ -36,6 +35,20 @@ namespace osu.Game.Graphics Convert.ToByte(hex.Substring(2, 2), 16), Convert.ToByte(hex.Substring(4, 2), 16), 255); + + case 4: + return new Color4( + (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17), + (byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17), + (byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17), + (byte)(Convert.ToByte(hex.Substring(3, 1), 16) * 17)); + + case 8: + return new Color4( + Convert.ToByte(hex.Substring(0, 2), 16), + Convert.ToByte(hex.Substring(2, 2), 16), + Convert.ToByte(hex.Substring(4, 2), 16), + Convert.ToByte(hex.Substring(6, 2), 16)); } } @@ -64,46 +77,6 @@ namespace osu.Game.Graphics } } - public Color4 ForOverlayElement(OverlayColourScheme colourScheme, float saturation, float lightness, float opacity = 1) => Color4.FromHsl(new Vector4(getBaseHue(colourScheme), saturation, lightness, opacity)); - - // See https://github.com/ppy/osu-web/blob/4218c288292d7c810b619075471eaea8bbb8f9d8/app/helpers.php#L1463 - private static float getBaseHue(OverlayColourScheme colourScheme) - { - float hue; - - switch (colourScheme) - { - default: - throw new ArgumentException($@"{colourScheme} colour scheme does not provide a hue value in {nameof(getBaseHue)}."); - - case OverlayColourScheme.Red: - hue = 0; - break; - - case OverlayColourScheme.Pink: - hue = 333; - break; - - case OverlayColourScheme.Orange: - hue = 46; - break; - - case OverlayColourScheme.Green: - hue = 115; - break; - - case OverlayColourScheme.Purple: - hue = 255; - break; - - case OverlayColourScheme.Blue: - hue = 200; - break; - } - - return hue / 360f; - } - // See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less public readonly Color4 PurpleLighter = FromHex(@"eeeeff"); public readonly Color4 PurpleLight = FromHex(@"aa88ff"); @@ -206,14 +179,4 @@ namespace osu.Game.Graphics public readonly Color4 ContextMenuGray = FromHex(@"223034"); } - - public enum OverlayColourScheme - { - Red, - Pink, - Orange, - Green, - Purple, - Blue - } } diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index b9151b7393..3ad36577b5 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -67,7 +67,9 @@ namespace osu.Game.Graphics return false; } - public bool OnReleased(GlobalAction action) => false; + public void OnReleased(GlobalAction action) + { + } private volatile int screenShotTasks; @@ -88,7 +90,7 @@ namespace osu.Game.Graphics { ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() => { - if (framesWaited++ < frames_to_wait) + if (framesWaited++ >= frames_to_wait) // ReSharper disable once AccessToDisposedClosure framesWaitedEvent.Set(); }, 10, true); diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 23565e8742..88ba7ede6e 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -67,7 +67,9 @@ namespace osu.Game.Graphics.UserInterface return false; } - public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; + public void OnReleased(GlobalAction action) + { + } } } } diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index e2438cc4cd..84429bf5bd 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -34,13 +34,13 @@ namespace osu.Game.Graphics.UserInterface var tIndex = TabContainer.IndexOf(t); var tabIndex = TabContainer.IndexOf(TabMap[index.NewValue]); - t.State = tIndex < tabIndex ? Visibility.Hidden : Visibility.Visible; - t.Chevron.FadeTo(tIndex <= tabIndex ? 0f : 1f, 500, Easing.OutQuint); + t.State = tIndex > tabIndex ? Visibility.Hidden : Visibility.Visible; + t.Chevron.FadeTo(tIndex >= tabIndex ? 0f : 1f, 500, Easing.OutQuint); } }; } - protected class BreadcrumbTabItem : OsuTabItem, IStateful + public class BreadcrumbTabItem : OsuTabItem, IStateful { protected virtual float ChevronSize => 10; diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index aed07e56ee..9b53ee7b2d 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -232,11 +232,11 @@ namespace osu.Game.Graphics.UserInterface return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { if (Selected.Value) colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In); - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 0b183c0ec9..e59353a480 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -80,7 +80,9 @@ namespace osu.Game.Graphics.UserInterface return false; } - public bool OnReleased(GlobalAction action) => false; + public void OnReleased(GlobalAction action) + { + } public override bool RequestsFocus => HoldFocus; } diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index fcd8940348..40899e7e95 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -24,7 +24,7 @@ namespace osu.Game.Graphics.UserInterface /// /// Length of debounce for hover sound playback, in milliseconds. Default is 50ms. /// - public double HoverDebounceTime { get; set; } = 50; + public double HoverDebounceTime { get; } = 50; protected readonly HoverSampleSet SampleSet; diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index 660bd7979f..cfcf034d1c 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -107,10 +107,10 @@ namespace osu.Game.Graphics.UserInterface return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { Content.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(e); + base.OnMouseUp(e); } } } diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index c6a9aa1c97..9cf8f02024 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -129,10 +129,10 @@ namespace osu.Game.Graphics.UserInterface return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { Content.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected virtual SpriteText CreateText() => new OsuSpriteText diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 958390d5d2..2112aac6a3 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -128,10 +128,10 @@ namespace osu.Game.Graphics.UserInterface return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { Nub.Current.Value = false; - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected override void OnUserChange(T value) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 6a7998d5fb..1bfbee4a60 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -84,14 +83,6 @@ namespace osu.Game.Graphics.UserInterface set => strip.Colour = value; } - protected override TabFillFlowContainer CreateTabFlow() => new OsuTabFillFlowContainer - { - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.Both, - Depth = -1, - Masking = true - }; - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -283,10 +274,5 @@ namespace osu.Game.Graphics.UserInterface } } } - - private class OsuTabFillFlowContainer : TabFillFlowContainer - { - protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y); - } } } diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 24d8009f40..01d8edaecf 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -16,11 +16,7 @@ namespace osu.Game.Graphics.UserInterface /// /// How many leading zeroes the counter has. /// - public uint LeadingZeroes - { - get; - protected set; - } + public uint LeadingZeroes { get; } /// /// Displays score. diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index 39ccf9fe1c..63a6348b57 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -50,7 +50,7 @@ namespace osu.Game.Input public bool OnPressed(PlatformAction action) => updateLastInteractionTime(); - public bool OnReleased(PlatformAction action) => updateLastInteractionTime(); + public void OnReleased(PlatformAction action) => updateLastInteractionTime(); protected override bool Handle(UIEvent e) { diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs new file mode 100644 index 0000000000..46a8db31b7 --- /dev/null +++ b/osu.Game/Online/API/APIMod.cs @@ -0,0 +1,57 @@ +// 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.Linq; +using Humanizer; +using Newtonsoft.Json; +using osu.Framework.Bindables; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Online.API +{ + public class APIMod : IMod + { + [JsonProperty("acronym")] + public string Acronym { get; set; } + + [JsonProperty("settings")] + public Dictionary Settings { get; set; } = new Dictionary(); + + [JsonConstructor] + private APIMod() + { + } + + public APIMod(Mod mod) + { + Acronym = mod.Acronym; + + foreach (var (_, property) in mod.GetSettingsSourceProperties()) + Settings.Add(property.Name.Underscore(), property.GetValue(mod)); + } + + public Mod ToMod(Ruleset ruleset) + { + Mod resultMod = ruleset.GetAllMods().FirstOrDefault(m => m.Acronym == Acronym); + + if (resultMod == null) + throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}."); + + foreach (var (_, property) in resultMod.GetSettingsSourceProperties()) + { + if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue)) + continue; + + ((IBindable)property.GetValue(resultMod)).Parse(settingValue); + } + + return resultMod; + } + + public bool Equals(IMod other) => Acronym == other?.Acronym; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIMod.cs b/osu.Game/Online/API/Requests/Responses/APIMod.cs deleted file mode 100644 index b9da4f49ee..0000000000 --- a/osu.Game/Online/API/Requests/Responses/APIMod.cs +++ /dev/null @@ -1,14 +0,0 @@ -// 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.Rulesets.Mods; - -namespace osu.Game.Online.API.Requests.Responses -{ - public class APIMod : IMod - { - public string Acronym { get; set; } - - public bool Equals(IMod other) => Acronym == other?.Acronym; - } -} diff --git a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs index 15d7dabe65..d109f28e72 100644 --- a/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs +++ b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs @@ -55,10 +55,10 @@ namespace osu.Game.Online.Leaderboards return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { icon.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(e); + base.OnMouseUp(e); } } } diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs index d13e8b31e6..5f8edc607b 100644 --- a/osu.Game/Online/Multiplayer/PlaylistItem.cs +++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using osu.Game.Beatmaps; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -50,7 +51,7 @@ namespace osu.Game.Online.Multiplayer [JsonProperty("allowed_mods")] private APIMod[] allowedMods { - get => AllowedMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray(); + get => AllowedMods.Select(m => new APIMod(m)).ToArray(); set => allowedModsBacking = value; } @@ -59,7 +60,7 @@ namespace osu.Game.Online.Multiplayer [JsonProperty("required_mods")] private APIMod[] requiredMods { - get => RequiredMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray(); + get => RequiredMods.Select(m => new APIMod(m)).ToArray(); set => requiredModsBacking = value; } @@ -72,10 +73,12 @@ namespace osu.Game.Online.Multiplayer Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets); Ruleset = rulesets.GetRuleset(RulesetID); + Ruleset rulesetInstance = Ruleset.CreateInstance(); + if (allowedModsBacking != null) { AllowedMods.Clear(); - AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => allowedModsBacking.Any(m => m.Acronym == mod.Acronym))); + AllowedMods.AddRange(allowedModsBacking.Select(m => m.ToMod(rulesetInstance))); allowedModsBacking = null; } @@ -83,7 +86,7 @@ namespace osu.Game.Online.Multiplayer if (requiredModsBacking != null) { RequiredMods.Clear(); - RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => requiredModsBacking.Any(m => m.Acronym == mod.Acronym))); + RequiredMods.AddRange(requiredModsBacking.Select(m => m.ToMod(rulesetInstance))); requiredModsBacking = null; } diff --git a/osu.Game/Online/Placeholders/LoginPlaceholder.cs b/osu.Game/Online/Placeholders/LoginPlaceholder.cs index ffc6623229..591eb976e2 100644 --- a/osu.Game/Online/Placeholders/LoginPlaceholder.cs +++ b/osu.Game/Online/Placeholders/LoginPlaceholder.cs @@ -31,10 +31,10 @@ namespace osu.Game.Online.Placeholders return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { this.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 9df854d178..ff46c6d849 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -881,7 +881,9 @@ namespace osu.Game #endregion - public bool OnReleased(GlobalAction action) => false; + public void OnReleased(GlobalAction action) + { + } private Container overlayContent; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0819642d2d..07c9d37a86 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -330,7 +330,7 @@ namespace osu.Game private class OsuUserInputManager : UserInputManager { - protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button) + protected override MouseButtonEventManager CreateButtonEventManagerFor(MouseButton button) { switch (button) { @@ -338,7 +338,7 @@ namespace osu.Game return new RightMouseManager(button); } - return base.CreateButtonManagerFor(button); + return base.CreateButtonEventManagerFor(button); } private class RightMouseManager : MouseButtonEventManager diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 50fb2782d4..e4e928df18 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -34,6 +34,7 @@ namespace osu.Game.Overlays public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public BeatmapSetOverlay() + : base(OverlayColourScheme.Blue) { OsuScrollContainer scroll; Info info; diff --git a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs index 2e50c19729..c3f35b4313 100644 --- a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs +++ b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays @@ -15,15 +14,10 @@ namespace osu.Game.Overlays protected override TabControl CreateTabControl() => BreadcrumbControl = new OverlayHeaderBreadcrumbControl(); - protected BreadcrumbControlOverlayHeader(OverlayColourScheme colourScheme) - : base(colourScheme) - { - } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider) { - BreadcrumbControl.AccentColour = colours.ForOverlayElement(ColourScheme, 1, 0.75f); + BreadcrumbControl.AccentColour = colourProvider.Highlight1; } public class OverlayHeaderBreadcrumbControl : BreadcrumbControl diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index d5e0890b4d..7755c0f64b 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -9,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; @@ -26,7 +26,6 @@ namespace osu.Game.Overlays.Changelog private const string listing_string = "listing"; public ChangelogHeader() - : base(OverlayColourScheme.Purple) { BreadcrumbControl.AddItem(listing_string); BreadcrumbControl.Current.ValueChanged += e => @@ -39,7 +38,7 @@ namespace osu.Game.Overlays.Changelog Streams.Current.ValueChanged += e => { - if (e.NewValue?.LatestBuild != null && e.NewValue != Current.Value?.UpdateStream) + if (e.NewValue?.LatestBuild != null && !e.NewValue.Equals(Current.Value?.UpdateStream)) Current.Value = e.NewValue.LatestBuild; }; } @@ -56,7 +55,7 @@ namespace osu.Game.Overlays.Changelog BreadcrumbControl.AddItem(e.NewValue.ToString()); BreadcrumbControl.Current.Value = e.NewValue.ToString(); - Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == e.NewValue.UpdateStream.Name); + updateCurrentStream(); title.Version = e.NewValue.UpdateStream.DisplayName; } @@ -82,6 +81,20 @@ namespace osu.Game.Overlays.Changelog protected override ScreenTitle CreateTitle() => title = new ChangelogHeaderTitle(); + public void Populate(List streams) + { + Streams.Populate(streams); + updateCurrentStream(); + } + + private void updateCurrentStream() + { + if (Current.Value == null) + return; + + Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == Current.Value.UpdateStream.Name); + } + public class HeaderBackground : Sprite { public HeaderBackground() diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs index 2b48811bd6..ca57ba24e2 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -29,8 +29,6 @@ namespace osu.Game.Overlays.Changelog public void Populate(List streams) { - Current.Value = null; - foreach (APIUpdateStream updateStream in streams) AddItem(updateStream); } diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 15b0079277..90ba206077 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays { public readonly Bindable Current = new Bindable(); - private ChangelogHeader header; + protected ChangelogHeader Header; private Container content; @@ -34,16 +34,16 @@ namespace osu.Game.Overlays private List builds; - private List streams; + protected List Streams; + + public ChangelogOverlay() + : base(OverlayColourScheme.Purple) + { + } [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colour) { - Waves.FirstWaveColour = colour.GreyVioletLight; - Waves.SecondWaveColour = colour.GreyViolet; - Waves.ThirdWaveColour = colour.GreyVioletDark; - Waves.FourthWaveColour = colour.GreyVioletDarker; - Children = new Drawable[] { new Box @@ -62,7 +62,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new ChangelogHeader + Header = new ChangelogHeader { ListingSelected = ShowListing, }, @@ -78,7 +78,7 @@ namespace osu.Game.Overlays sampleBack = audio.Samples.Get(@"UI/generic-select-soft"); - header.Current.BindTo(Current); + Header.Current.BindTo(Current); Current.BindValueChanged(e => { @@ -117,7 +117,7 @@ namespace osu.Game.Overlays performAfterFetch(() => { var build = builds.Find(b => b.Version == version && b.UpdateStream.Name == updateStream) - ?? streams.Find(s => s.Name == updateStream)?.LatestBuild; + ?? Streams.Find(s => s.Name == updateStream)?.LatestBuild; if (build != null) ShowBuild(build); @@ -179,9 +179,9 @@ namespace osu.Game.Overlays res.Streams.ForEach(s => s.LatestBuild.UpdateStream = res.Streams.Find(s2 => s2.Id == s.LatestBuild.UpdateStream.Id)); builds = res.Builds; - streams = res.Streams; + Streams = res.Streams; - header.Streams.Populate(res.Streams); + Header.Populate(res.Streams); tcs.SetResult(true); }); diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 2e4d8ce729..104495ae01 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -9,6 +9,7 @@ using osuTK; using System; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; namespace osu.Game.Overlays.Chat.Tabs { @@ -113,5 +114,18 @@ namespace osu.Game.Overlays.Chat.Tabs OnRequestLeave?.Invoke(tab.Value); } + + protected override TabFillFlowContainer CreateTabFlow() => new ChannelTabFillFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.Both, + Depth = -1, + Masking = true + }; + + private class ChannelTabFillFlowContainer : TabFillFlowContainer + { + protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y); + } } } diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 266e68f17e..09dc06b95f 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -141,16 +141,13 @@ namespace osu.Game.Overlays.Chat.Tabs updateState(); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { switch (e.Button) { case MouseButton.Middle: CloseButton.Click(); - return true; - - default: - return false; + break; } } diff --git a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs index bde930d4fb..178afda5ac 100644 --- a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs +++ b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs @@ -34,10 +34,10 @@ namespace osu.Game.Overlays.Chat.Tabs return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { icon.ScaleTo(0.75f, 1000, Easing.OutElastic); - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 45d311df28..2c0fa49b5d 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -299,7 +299,7 @@ namespace osu.Game.Overlays return true; } - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { if (isDragging) { @@ -311,14 +311,12 @@ namespace osu.Game.Overlays ChatHeight.Value = targetChatHeight; } - - return true; } - protected override bool OnDragEnd(DragEndEvent e) + protected override void OnDragEnd(DragEndEvent e) { isDragging = false; - return base.OnDragEnd(e); + base.OnDragEnd(e); } private void selectTab(int index) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 560123eb55..d252083411 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -18,8 +18,8 @@ namespace osu.Game.Overlays.Comments { public class CommentsContainer : CompositeDrawable { - private readonly CommentableType type; - private readonly long id; + private CommentableType type; + private long? id; public readonly Bindable Sort = new Bindable(); public readonly BindableBool ShowDeleted = new BindableBool(); @@ -38,12 +38,10 @@ namespace osu.Game.Overlays.Comments private readonly FillFlowContainer content; private readonly DeletedChildrenPlaceholder deletedChildrenPlaceholder; private readonly CommentsShowMoreButton moreButton; + private readonly TotalCommentsCounter commentCounter; - public CommentsContainer(CommentableType type, long id) + public CommentsContainer() { - this.type = type; - this.id = id; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; AddRangeInternal(new Drawable[] @@ -59,6 +57,7 @@ namespace osu.Game.Overlays.Comments Direction = FillDirection.Vertical, Children = new Drawable[] { + commentCounter = new TotalCommentsCounter(), new CommentsHeader { Sort = { BindTarget = Sort }, @@ -101,7 +100,8 @@ namespace osu.Game.Overlays.Comments Anchor = Anchor.Centre, Origin = Anchor.Centre, Margin = new MarginPadding(5), - Action = getComments + Action = getComments, + IsLoading = true, } } } @@ -121,11 +121,27 @@ namespace osu.Game.Overlays.Comments protected override void LoadComplete() { - Sort.BindValueChanged(onSortChanged, true); + Sort.BindValueChanged(_ => refetchComments(), true); base.LoadComplete(); } - private void onSortChanged(ValueChangedEvent sort) + /// The type of resource to get comments for. + /// The id of the resource to get comments for. + public void ShowComments(CommentableType type, long id) + { + this.type = type; + this.id = id; + + if (!IsLoaded) + return; + + // only reset when changing ID/type. other refetch ops are generally just changing sort order. + commentCounter.Current.Value = 0; + + refetchComments(); + } + + private void refetchComments() { clearComments(); getComments(); @@ -133,9 +149,12 @@ namespace osu.Game.Overlays.Comments private void getComments() { + if (!id.HasValue) + return; + request?.Cancel(); loadCancellation?.Cancel(); - request = new GetCommentsRequest(type, id, Sort.Value, currentPage++); + request = new GetCommentsRequest(type, id.Value, Sort.Value, currentPage++); request.Success += onSuccess; api.Queue(request); } @@ -152,7 +171,7 @@ namespace osu.Game.Overlays.Comments { loadCancellation = new CancellationTokenSource(); - FillFlowContainer page = new FillFlowContainer + var page = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -185,6 +204,8 @@ namespace osu.Game.Overlays.Comments moreButton.IsLoading = false; } + commentCounter.Current.Value = response.Total; + moreButton.FadeTo(response.HasMore ? 1 : 0); }, loadCancellation.Token); } diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 9daf55c796..e4cef319fe 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -84,14 +84,8 @@ namespace osu.Game.Overlays } public DirectOverlay() + : base(OverlayColourScheme.Blue) { - // osu!direct colours are not part of the standard palette - - Waves.FirstWaveColour = OsuColour.FromHex(@"19b0e2"); - Waves.SecondWaveColour = OsuColour.FromHex(@"2280a2"); - Waves.ThirdWaveColour = OsuColour.FromHex(@"005774"); - Waves.FourthWaveColour = OsuColour.FromHex(@"003a4e"); - ScrollFlow.Children = new Drawable[] { resultCountsContainer = new FillFlowContainer diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 0911ee84de..959f6749d2 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -6,7 +6,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osuTK.Graphics; @@ -18,12 +17,12 @@ namespace osu.Game.Overlays [Resolved] protected IAPIProvider API { get; private set; } - protected FullscreenOverlay() + [Cached] + private readonly OverlayColourProvider colourProvider; + + protected FullscreenOverlay(OverlayColourScheme colourScheme) { - Waves.FirstWaveColour = OsuColour.Gray(0.4f); - Waves.SecondWaveColour = OsuColour.Gray(0.3f); - Waves.ThirdWaveColour = OsuColour.Gray(0.2f); - Waves.FourthWaveColour = OsuColour.Gray(0.1f); + colourProvider = new OverlayColourProvider(colourScheme); RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; @@ -41,6 +40,15 @@ namespace osu.Game.Overlays }; } + [BackgroundDependencyLoader] + private void load() + { + Waves.FirstWaveColour = colourProvider.Light4; + Waves.SecondWaveColour = colourProvider.Light3; + Waves.ThirdWaveColour = colourProvider.Dark4; + Waves.FourthWaveColour = colourProvider.Dark3; + } + public override void Show() { if (State.Value == Visibility.Visible) diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 8317951c8a..d2fcc2652a 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -177,17 +177,19 @@ namespace osu.Game.Overlays.KeyBinding return true; } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { // don't do anything until the last button is released. if (!HasFocus || e.HasAnyButtonPressed) - return base.OnMouseUp(e); + { + base.OnMouseUp(e); + return; + } if (bindTarget.IsHovered) finalise(); else updateBindTarget(); - return true; } protected override bool OnScroll(ScrollEvent e) @@ -216,12 +218,15 @@ namespace osu.Game.Overlays.KeyBinding return true; } - protected override bool OnKeyUp(KeyUpEvent e) + protected override void OnKeyUp(KeyUpEvent e) { - if (!HasFocus) return base.OnKeyUp(e); + if (!HasFocus) + { + base.OnKeyUp(e); + return; + } finalise(); - return true; } protected override bool OnJoystickPress(JoystickPressEvent e) @@ -235,13 +240,15 @@ namespace osu.Game.Overlays.KeyBinding return true; } - protected override bool OnJoystickRelease(JoystickReleaseEvent e) + protected override void OnJoystickRelease(JoystickReleaseEvent e) { if (!HasFocus) - return base.OnJoystickRelease(e); + { + base.OnJoystickRelease(e); + return; + } finalise(); - return true; } private void clear() @@ -313,14 +320,6 @@ namespace osu.Game.Overlays.KeyBinding Size = new Vector2(80, 20); } - protected override bool OnMouseUp(MouseUpEvent e) - { - base.OnMouseUp(e); - - // without this, the mouse up triggers a finalise (and deselection) of the current binding target. - return true; - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 77ef08f52d..e574828cd2 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -158,7 +158,7 @@ namespace osu.Game.Overlays.Mods return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { scaleContainer.ScaleTo(1, 500, Easing.OutElastic); @@ -172,8 +172,6 @@ namespace osu.Game.Overlays.Mods break; } } - - return true; } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 7f07ce620c..38f5d54714 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -473,7 +473,10 @@ namespace osu.Game.Overlays.Mods if (selectedMod != null) { if (State.Value == Visibility.Visible) sampleOn?.Play(); + DeselectTypes(selectedMod.IncompatibleMods, true); + + if (selectedMod.RequiresConfiguration) ModSettingsContainer.Alpha = 1; } else { diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 29b6ae00f3..d40f391982 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -43,10 +43,10 @@ namespace osu.Game.Overlays.Music return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { IsDraggable = false; - return base.OnMouseUp(e); + base.OnMouseUp(e); } private bool selected; diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 3cd04ac809..7bdcab6dff 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -136,29 +136,29 @@ namespace osu.Game.Overlays.Music return draggedItem != null || base.OnDragStart(e); } - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { nativeDragPosition = e.ScreenSpaceMousePosition; - if (draggedItem == null) - return base.OnDrag(e); - return true; + if (draggedItem == null) + base.OnDrag(e); } - protected override bool OnDragEnd(DragEndEvent e) + protected override void OnDragEnd(DragEndEvent e) { nativeDragPosition = e.ScreenSpaceMousePosition; if (draggedItem == null) - return base.OnDragEnd(e); + { + base.OnDragEnd(e); + return; + } if (dragDestination != null) musicController.ChangeBeatmapSetPosition(draggedItem.BeatmapSetInfo, dragDestination.Value); draggedItem = null; dragDestination = null; - - return true; } protected override void Update() diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 3c0f6468bc..19f06e99f1 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -326,7 +326,9 @@ namespace osu.Game.Overlays return false; } - public bool OnReleased(GlobalAction action) => false; + public void OnReleased(GlobalAction action) + { + } public class MusicControllerToast : Toast { diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index 03dc64b3bd..1152d9044b 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using System; @@ -23,7 +22,6 @@ namespace osu.Game.Overlays.News public Action ShowFrontPage; public NewsHeader() - : base(OverlayColourScheme.Purple) { BreadcrumbControl.AddItem(front_page_string); diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index e7471cb21d..6dde300556 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -21,6 +21,11 @@ namespace osu.Game.Overlays public readonly Bindable Current = new Bindable(null); + public NewsOverlay() + : base(OverlayColourScheme.Purple) + { + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index a8ba7fa427..042e95c6d7 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -385,7 +385,7 @@ namespace osu.Game.Overlays return true; } - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { Vector2 change = e.MousePosition - e.MouseDownPosition; @@ -393,13 +393,12 @@ namespace osu.Game.Overlays change *= change.Length <= 0 ? 0 : MathF.Pow(change.Length, 0.7f) / change.Length; this.MoveTo(change); - return true; } - protected override bool OnDragEnd(DragEndEvent e) + protected override void OnDragEnd(DragEndEvent e) { this.MoveTo(Vector2.Zero, 800, Easing.OutElastic); - return base.OnDragEnd(e); + base.OnDragEnd(e); } } diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs new file mode 100644 index 0000000000..9816f313ad --- /dev/null +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -0,0 +1,80 @@ +// 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 osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public class OverlayColourProvider + { + private readonly OverlayColourScheme colourScheme; + + public OverlayColourProvider(OverlayColourScheme colourScheme) + { + this.colourScheme = colourScheme; + } + + public Color4 Highlight1 => getColour(1, 0.7f); + public Color4 Content1 => getColour(0.4f, 1); + public Color4 Content2 => getColour(0.4f, 0.9f); + public Color4 Light1 => getColour(0.4f, 0.8f); + public Color4 Light2 => getColour(0.4f, 0.75f); + public Color4 Light3 => getColour(0.4f, 0.7f); + public Color4 Light4 => getColour(0.4f, 0.5f); + public Color4 Dark1 => getColour(0.2f, 0.35f); + public Color4 Dark2 => getColour(0.2f, 0.3f); + public Color4 Dark3 => getColour(0.2f, 0.25f); + public Color4 Dark4 => getColour(0.2f, 0.2f); + public Color4 Dark5 => getColour(0.2f, 0.15f); + public Color4 Dark6 => getColour(0.2f, 0.1f); + public Color4 Foreground1 => getColour(0.1f, 0.6f); + public Color4 Background1 => getColour(0.1f, 0.4f); + public Color4 Background2 => getColour(0.1f, 0.3f); + public Color4 Background3 => getColour(0.1f, 0.25f); + public Color4 Background4 => getColour(0.1f, 0.2f); + public Color4 Background5 => getColour(0.1f, 0.15f); + public Color4 Background6 => getColour(0.1f, 0.1f); + + private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(colourScheme), saturation, lightness, 1)); + + // See https://github.com/ppy/osu-web/blob/4218c288292d7c810b619075471eaea8bbb8f9d8/app/helpers.php#L1463 + private static float getBaseHue(OverlayColourScheme colourScheme) + { + switch (colourScheme) + { + default: + throw new ArgumentException($@"{colourScheme} colour scheme does not provide a hue value in {nameof(getBaseHue)}."); + + case OverlayColourScheme.Red: + return 0; + + case OverlayColourScheme.Pink: + return 333 / 360f; + + case OverlayColourScheme.Orange: + return 46 / 360f; + + case OverlayColourScheme.Green: + return 115 / 360f; + + case OverlayColourScheme.Purple: + return 255 / 360f; + + case OverlayColourScheme.Blue: + return 200 / 360f; + } + } + } + + public enum OverlayColourScheme + { + Red, + Pink, + Orange, + Green, + Purple, + Blue + } +} diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs index c9547bb5b8..bc58a17401 100644 --- a/osu.Game/Overlays/OverlayHeader.cs +++ b/osu.Game/Overlays/OverlayHeader.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; @@ -25,12 +24,8 @@ namespace osu.Game.Overlays set => background.Height = value; } - protected OverlayColourScheme ColourScheme { get; } - - protected OverlayHeader(OverlayColourScheme colourScheme) + protected OverlayHeader() { - ColourScheme = colourScheme; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -89,11 +84,11 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider) { - titleBackground.Colour = colours.ForOverlayElement(ColourScheme, 0.2f, 0.15f); - title.AccentColour = colours.ForOverlayElement(ColourScheme, 1, 0.7f); - controlBackground.Colour = colours.ForOverlayElement(ColourScheme, 0.2f, 0.2f); + titleBackground.Colour = colourProvider.Dark5; + title.AccentColour = colourProvider.Highlight1; + controlBackground.Colour = colourProvider.Dark4; } protected abstract Drawable CreateBackground(); diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index b550d7d823..f8eb03770a 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -24,7 +24,6 @@ namespace osu.Game.Overlays.Profile private DetailHeaderContainer detailHeaderContainer; public ProfileHeader() - : base(OverlayColourScheme.Green) { BackgroundHeight = 150; diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index c8874ef891..1ab18b8c15 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -25,7 +25,6 @@ namespace osu.Game.Overlays private readonly Bindable ruleset = new Bindable(); private readonly BasicScrollContainer scrollFlow; - private readonly Box background; private readonly Container tableContainer; private readonly DimmedLoadingLayer loading; @@ -36,12 +35,14 @@ namespace osu.Game.Overlays private IAPIProvider api { get; set; } public RankingsOverlay() + : base(OverlayColourScheme.Green) { Children = new Drawable[] { - background = new Box + new Box { RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.1f), }, scrollFlow = new BasicScrollContainer { @@ -85,17 +86,6 @@ namespace osu.Game.Overlays }; } - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - Waves.FirstWaveColour = colour.Green; - Waves.SecondWaveColour = colour.GreenLight; - Waves.ThirdWaveColour = colour.GreenDark; - Waves.FourthWaveColour = colour.GreenDarker; - - background.Colour = OsuColour.Gray(0.1f); - } - protected override void LoadComplete() { Country.BindValueChanged(_ => diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 5975e94ffc..0783c64c20 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -16,6 +16,11 @@ namespace osu.Game.Overlays.SearchableList public abstract class SearchableListOverlay : FullscreenOverlay { public const float WIDTH_PADDING = 80; + + protected SearchableListOverlay(OverlayColourScheme colourScheme) + : base(colourScheme) + { + } } public abstract class SearchableListOverlay : SearchableListOverlay @@ -35,7 +40,8 @@ namespace osu.Game.Overlays.SearchableList protected abstract SearchableListHeader CreateHeader(); protected abstract SearchableListFilterControl CreateFilterControl(); - protected SearchableListOverlay() + protected SearchableListOverlay(OverlayColourScheme colourScheme) + : base(colourScheme) { Children = new Drawable[] { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 3f8bc2b0c7..08bc67e43e 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -40,6 +40,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Bindable = config.GetBindable(OsuSetting.ShowInterface) }, new SettingsCheckbox + { + LabelText = "Show difficulty graph on progress bar", + Bindable = config.GetBindable(OsuSetting.ShowProgressGraph) + }, + new SettingsCheckbox { LabelText = "Show health display even when you can't fail", Bindable = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail), diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index a5f56ae76e..0c42247993 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -1,7 +1,9 @@ // 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.Bindables; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -10,11 +12,20 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { public class SongSelectSettings : SettingsSubsection { + private Bindable minStars; + private Bindable maxStars; + protected override string Header => "Song Select"; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { + minStars = config.GetBindable(OsuSetting.DisplayStarsMinimum); + maxStars = config.GetBindable(OsuSetting.DisplayStarsMaximum); + + minStars.ValueChanged += min => maxStars.Value = Math.Max(min.NewValue, maxStars.Value); + maxStars.ValueChanged += max => minStars.Value = Math.Min(max.NewValue, minStars.Value); + Children = new Drawable[] { new SettingsCheckbox @@ -27,19 +38,19 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Show converted beatmaps", Bindable = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), }, - new SettingsSlider + new SettingsSlider { LabelText = "Display beatmaps from", Bindable = config.GetBindable(OsuSetting.DisplayStarsMinimum), KeyboardStep = 0.1f, - Keywords = new[] { "star", "difficulty" } + Keywords = new[] { "minimum", "maximum", "star", "difficulty" } }, - new SettingsSlider + new SettingsSlider { LabelText = "up to", Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum), KeyboardStep = 0.1f, - Keywords = new[] { "star", "difficulty" } + Keywords = new[] { "minimum", "maximum", "star", "difficulty" } }, new SettingsEnumDropdown { @@ -49,7 +60,12 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay }; } - private class StarSlider : OsuSliderBar + private class MaximumStarsSlider : StarsSlider + { + public override string TooltipText => Current.IsDefault ? "no limit" : base.TooltipText; + } + + private class StarsSlider : OsuSliderBar { public override string TooltipText => Current.Value.ToString(@"0.## stars"); } diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 0c99962def..9a523bc1bc 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -52,12 +52,8 @@ namespace osu.Game.Overlays } public SocialOverlay() + : base(OverlayColourScheme.Pink) { - Waves.FirstWaveColour = OsuColour.FromHex(@"cb5fa0"); - Waves.SecondWaveColour = OsuColour.FromHex(@"b04384"); - Waves.ThirdWaveColour = OsuColour.FromHex(@"9b2b6e"); - Waves.FourthWaveColour = OsuColour.FromHex(@"6d214d"); - Add(loading = new LoadingAnimation()); Filter.Search.Current.ValueChanged += text => diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index 8f3aa896ee..0c55b8383b 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -16,15 +16,10 @@ namespace osu.Game.Overlays protected override TabControl CreateTabControl() => TabControl = new OverlayHeaderTabControl(); - protected TabControlOverlayHeader(OverlayColourScheme colourScheme) - : base(colourScheme) - { - } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider) { - TabControl.AccentColour = colours.ForOverlayElement(ColourScheme, 1, 0.75f); + TabControl.AccentColour = colourProvider.Highlight1; } public class OverlayHeaderTabControl : OverlayTabControl diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index a34fc619a8..07c0dbed43 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -29,6 +29,11 @@ namespace osu.Game.Overlays public const float CONTENT_X_MARGIN = 70; + public UserProfileOverlay() + : base(OverlayColourScheme.Green) + { + } + public void ShowUser(long userId) => ShowUser(new User { Id = userId }); public void ShowUser(User user, bool fetchOnline = true) diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index 9cd3aac2cb..76fad945cc 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -16,6 +16,9 @@ namespace osu.Game.Overlays.Volume public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false; public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false; - public bool OnReleased(GlobalAction action) => false; + + public void OnReleased(GlobalAction action) + { + } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index bfaa7e8872..9ee3bacf9b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -46,12 +46,12 @@ namespace osu.Game.Rulesets.Edit private IAdjustableClock adjustableClock { get; set; } [Resolved] - private BindableBeatDivisor beatDivisor { get; set; } + private IBeatSnapProvider beatSnapProvider { get; set; } private IBeatmapProcessor beatmapProcessor; private DrawableEditRulesetWrapper drawableRulesetWrapper; - private BlueprintContainer blueprintContainer; + private ComposeBlueprintContainer blueprintContainer; private Container distanceSnapGridContainer; private DistanceSnapGrid distanceSnapGrid; private readonly List layerContainers = new List(); @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Edit new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both } }); - var layerAboveRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(blueprintContainer = new BlueprintContainer()); + var layerAboveRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(blueprintContainer = CreateBlueprintContainer()); layerContainers.Add(layerBelowRuleset); layerContainers.Add(layerAboveRuleset); @@ -233,6 +233,8 @@ namespace osu.Game.Rulesets.Edit protected abstract IReadOnlyList CompositionTools { get; } + protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null); public void BeginPlacement(HitObject hitObject) @@ -257,40 +259,26 @@ namespace osu.Game.Rulesets.Edit public override float GetBeatSnapDistanceAt(double referenceTime) { DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime); - return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatDivisor.Value); + return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatSnapProvider.BeatDivisor); } public override float DurationToDistance(double referenceTime, double duration) { - double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime); return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime)); } public override double DistanceToDuration(double referenceTime, float distance) { - double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime); return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength; } public override double GetSnappedDurationFromDistance(double referenceTime, float distance) - => beatSnap(referenceTime, DistanceToDuration(referenceTime, distance)); + => beatSnapProvider.SnapTime(referenceTime, DistanceToDuration(referenceTime, distance)); public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) - => DurationToDistance(referenceTime, beatSnap(referenceTime, DistanceToDuration(referenceTime, distance))); - - /// - /// Snaps a duration to the closest beat of a timing point applicable at the reference time. - /// - /// The time of the timing point which resides in. - /// The duration to snap. - /// A value that represents snapped to the closest beat of the timing point. - private double beatSnap(double referenceTime, double duration) - { - double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; - - // A 1ms offset prevents rounding errors due to minute variations in duration - return (int)((duration + 1) / beatLength) * beatLength; - } + => DurationToDistance(referenceTime, beatSnapProvider.SnapTime(referenceTime, DistanceToDuration(referenceTime, distance))); protected override void Dispose(bool isDisposing) { @@ -323,17 +311,6 @@ namespace osu.Game.Rulesets.Edit /// public abstract bool CursorInPlacementArea { get; } - /// - /// Creates a for a specific . - /// - /// The to create the overlay for. - public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; - - /// - /// Creates a which outlines s and handles movement of selections. - /// - public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); - /// /// Creates the applicable for a selection. /// diff --git a/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs b/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs new file mode 100644 index 0000000000..e1daafaebe --- /dev/null +++ b/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Edit +{ + public interface IBeatSnapProvider + { + /// + /// Snaps a duration to the closest beat of a timing point applicable at the reference time. + /// + /// The time of the timing point which resides in. + /// The duration to snap. + /// A value that represents snapped to the closest beat of the timing point. + double SnapTime(double referenceTime, double duration); + + /// + /// Get the most appropriate beat length at a given time. + /// + /// A reference time used for lookup. + /// The most appropriate beat length. + double GetBeatLengthAtTime(double referenceTime); + + /// + /// Returns the current beat divisor. + /// + int BeatDivisor { get; } + } +} diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs new file mode 100644 index 0000000000..b4ae3f3fba --- /dev/null +++ b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs @@ -0,0 +1,34 @@ +// 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.Graphics.Primitives; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Objects.Drawables; +using osuTK; + +namespace osu.Game.Rulesets.Edit +{ + public abstract class OverlaySelectionBlueprint : SelectionBlueprint + { + /// + /// The which this applies to. + /// + public readonly DrawableHitObject DrawableObject; + + protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || State == SelectionState.Selected; + + protected OverlaySelectionBlueprint(DrawableHitObject drawableObject) + : base(drawableObject.HitObject) + { + DrawableObject = drawableObject; + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); + + public override Vector2 SelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; + + public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; + + public override Vector2 GetInstantDelta(Vector2 screenSpacePosition) => DrawableObject.Parent.ToLocalSpace(screenSpacePosition) - DrawableObject.Position; + } +} diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index bf99f83e0b..a972d28480 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.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 System; @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Edit /// public abstract class SelectionBlueprint : CompositeDrawable, IStateful { + public readonly HitObject HitObject; + /// /// Invoked when this has been selected. /// @@ -30,26 +32,24 @@ namespace osu.Game.Rulesets.Edit /// public event Action Deselected; - /// - /// The which this applies to. - /// - public readonly DrawableHitObject DrawableObject; - - protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || State == SelectionState.Selected; public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } - protected SelectionBlueprint(DrawableHitObject drawableObject) + protected SelectionBlueprint(HitObject hitObject) { - DrawableObject = drawableObject; + HitObject = hitObject; RelativeSizeAxes = Axes.Both; - AlwaysPresent = true; - Alpha = 0; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); } private SelectionState state; @@ -66,58 +66,68 @@ namespace osu.Game.Rulesets.Edit state = value; - switch (state) - { - case SelectionState.Selected: - Show(); - Selected?.Invoke(this); - break; - - case SelectionState.NotSelected: - Hide(); - Deselected?.Invoke(this); - break; - } + if (IsLoaded) + updateState(); StateChanged?.Invoke(state); } } + private void updateState() + { + switch (state) + { + case SelectionState.Selected: + OnSelected(); + Selected?.Invoke(this); + break; + + case SelectionState.NotSelected: + OnDeselected(); + Deselected?.Invoke(this); + break; + } + } + + protected virtual void OnDeselected() => Hide(); + + protected virtual void OnSelected() => Show(); + // When not selected, input is only required for the blueprint itself to receive IsHovering protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; /// - /// Selects this , causing it to become visible. + /// Selects this , causing it to become visible. /// public void Select() => State = SelectionState.Selected; /// - /// Deselects this , causing it to become invisible. + /// Deselects this , causing it to become invisible. /// public void Deselect() => State = SelectionState.NotSelected; public bool IsSelected => State == SelectionState.Selected; /// - /// Updates the , invoking and re-processing the beatmap. + /// Updates the , invoking and re-processing the beatmap. /// - protected void UpdateHitObject() => composer?.UpdateHitObject(DrawableObject.HitObject); - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); + protected void UpdateHitObject() => composer?.UpdateHitObject(HitObject); /// - /// The s to be displayed in the context menu for this . + /// The s to be displayed in the context menu for this . /// public virtual MenuItem[] ContextMenuItems => Array.Empty(); /// - /// The screen-space point that causes this to be selected. + /// The screen-space point that causes this to be selected. /// - public virtual Vector2 SelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; + public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; /// - /// The screen-space quad that outlines this for selections. + /// The screen-space quad that outlines this for selections. /// - public virtual Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; + public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; + + public virtual Vector2 GetInstantDelta(Vector2 screenSpacePosition) => Parent.ToLocalSpace(screenSpacePosition) - Position; } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index e168171186..46c0c1da07 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -60,6 +60,12 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool Ranked => false; + /// + /// Whether this mod requires configuration to apply changes to the game. + /// + [JsonIgnore] + public virtual bool RequiresConfiguration => false; + /// /// The mods this mod cannot be enabled with. /// diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index b74bf01d1b..d74e2ce2bc 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1.0; + public override bool RequiresConfiguration => true; + public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModHardRock) }; [SettingSource("Drain Rate", "Override a beatmap's set HP.")] diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 8ccc2af93b..8eafaa88ec 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Scoring public readonly Bindable Rank = new Bindable(ScoreRank.X); /// - /// THe highest combo achieved by this score. + /// The highest combo achieved by this score. /// public readonly BindableInt HighestCombo = new BindableInt(); diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 5cc213be41..41b2739fc5 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -139,7 +139,11 @@ namespace osu.Game.Rulesets.UI public bool OnPressed(T action) => Target.Children.OfType>().Any(c => c.OnPressed(action, Clock.Rate >= 0)); - public bool OnReleased(T action) => Target.Children.OfType>().Any(c => c.OnReleased(action, Clock.Rate >= 0)); + public void OnReleased(T action) + { + foreach (var c in Target.Children.OfType>()) + c.OnReleased(action, Clock.Rate >= 0); + } } #endregion diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index fda1d7c723..8bcdfff2fd 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -201,7 +201,9 @@ namespace osu.Game.Rulesets.UI.Scrolling throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); } - public bool OnReleased(GlobalAction action) => false; + public void OnReleased(GlobalAction action) + { + } private class LocalScrollingInfo : IScrollingInfo { diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 62d6c4648b..66df68418a 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -5,6 +5,8 @@ using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,13 +27,13 @@ namespace osu.Game.Screens.Edit.Components private IAdjustableClock adjustableClock; + private readonly BindableNumber tempo = new BindableDouble(1); + [BackgroundDependencyLoader] private void load(IAdjustableClock adjustableClock) { this.adjustableClock = adjustableClock; - PlaybackTabControl tabs; - Children = new Drawable[] { playButton = new IconButton @@ -58,11 +60,18 @@ namespace osu.Game.Screens.Edit.Components RelativeSizeAxes = Axes.Both, Height = 0.5f, Padding = new MarginPadding { Left = 45 }, - Child = tabs = new PlaybackTabControl(), + Child = new PlaybackTabControl { Current = tempo }, } }; - tabs.Current.ValueChanged += tempo => Beatmap.Value.Track.Tempo.Value = tempo.NewValue; + Track?.AddAdjustment(AdjustableProperty.Tempo, tempo); + } + + protected override void Dispose(bool isDisposing) + { + Track?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); + + base.Dispose(isDisposing); } protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 79ada40a89..5d638d7919 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -32,12 +32,10 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } protected override bool OnDragStart(DragStartEvent e) => true; - protected override bool OnDragEnd(DragEndEvent e) => true; - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { seekToPosition(e.ScreenSpaceMousePosition); - return true; } protected override bool OnMouseDown(MouseDownEvent e) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index 7706e33179..4a7c3f26bc 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -11,20 +11,24 @@ using osu.Game.Beatmaps; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { + public class TimelinePart : TimelinePart + { + } + /// /// Represents a part of the summary timeline.. /// - public abstract class TimelinePart : Container + public class TimelinePart : Container where T : Drawable { protected readonly IBindable Beatmap = new Bindable(); - private readonly Container timeline; + private readonly Container content; - protected override Container Content => timeline; + protected override Container Content => content; - protected TimelinePart() + public TimelinePart(Container content = null) { - AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both }); + AddInternal(this.content = content ?? new Container { RelativeSizeAxes = Axes.Both }); Beatmap.ValueChanged += b => { @@ -44,17 +48,17 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts // the track may not be loaded completely (only has a length once it is). if (!Beatmap.Value.Track.IsLoaded) { - timeline.RelativeChildSize = Vector2.One; + content.RelativeChildSize = Vector2.One; Schedule(updateRelativeChildSize); return; } - timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); + content.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); } protected virtual void LoadBeatmap(WorkingBeatmap beatmap) { - timeline.Clear(); + content.Clear(); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 42773ef687..8201ec2710 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -262,10 +262,10 @@ namespace osu.Game.Screens.Edit.Compose.Components return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { marker.Active = false; - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected override bool OnClick(ClickEvent e) @@ -274,10 +274,9 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { handleMouseInput(e.ScreenSpaceMousePosition); - return true; } private void handleMouseInput(Vector2 screenSpaceMousePosition) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index cafaddc39e..6b21f56567 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -14,7 +15,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -22,27 +22,32 @@ using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { - public class BlueprintContainer : CompositeDrawable, IKeyBindingHandler + /// + /// A container which provides a "blueprint" display of hitobjects. + /// Includes selection and manipulation support via a . + /// + public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler { public event Action> SelectionChanged; - private DragBox dragBox; - private SelectionBlueprintContainer selectionBlueprints; - private Container placementBlueprintContainer; - private PlacementBlueprint currentPlacement; + protected DragBox DragBox { get; private set; } + + private Container selectionBlueprints; + private SelectionHandler selectionHandler; - private InputManager inputManager; [Resolved] private IAdjustableClock adjustableClock { get; set; } - [Resolved] - private HitObjectComposer composer { get; set; } - [Resolved] private EditorBeatmap beatmap { get; set; } - public BlueprintContainer() + private readonly BindableList selectedHitObjects = new BindableList(); + + [Resolved(canBeNull: true)] + private IDistanceSnapProvider snapProvider { get; set; } + + protected BlueprintContainer() { RelativeSizeAxes = Axes.Both; } @@ -50,54 +55,63 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - selectionHandler = composer.CreateSelectionHandler(); + selectionHandler = CreateSelectionHandler(); selectionHandler.DeselectAll = deselectAll; - InternalChildren = new[] + AddRangeInternal(new[] { - dragBox = new DragBox(select), + DragBox = CreateDragBox(select), selectionHandler, - selectionBlueprints = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }, - placementBlueprintContainer = new Container { RelativeSizeAxes = Axes.Both }, - dragBox.CreateProxy() + selectionBlueprints = CreateSelectionBlueprintContainer(), + DragBox.CreateProxy().With(p => p.Depth = float.MinValue) + }); + + foreach (var obj in beatmap.HitObjects) + AddBlueprintFor(obj); + + selectedHitObjects.BindTo(beatmap.SelectedHitObjects); + selectedHitObjects.ItemsAdded += objects => + { + foreach (var o in objects) + selectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Select(); }; - foreach (var obj in composer.HitObjects) - addBlueprintFor(obj); + selectedHitObjects.ItemsRemoved += objects => + { + foreach (var o in objects) + selectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Deselect(); + }; } protected override void LoadComplete() { base.LoadComplete(); - beatmap.HitObjectAdded += addBlueprintFor; + beatmap.HitObjectAdded += AddBlueprintFor; beatmap.HitObjectRemoved += removeBlueprintFor; - - inputManager = GetContainingInputManager(); } - private HitObjectCompositionTool currentTool; + protected virtual Container CreateSelectionBlueprintContainer() => + new Container { RelativeSizeAxes = Axes.Both }; /// - /// The current placement tool. + /// Creates a which outlines s and handles movement of selections. /// - public HitObjectCompositionTool CurrentTool - { - get => currentTool; - set - { - if (currentTool == value) - return; + protected virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); - currentTool = value; + /// + /// Creates a for a specific . + /// + /// The to create the overlay for. + protected virtual SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => null; - refreshTool(); - } - } + protected virtual DragBox CreateDragBox(Action performSelect) => new DragBox(performSelect); protected override bool OnMouseDown(MouseDownEvent e) { beginClickSelection(e); + prepareSelectionMovement(); + return e.Button == MouseButton.Left; } @@ -125,26 +139,16 @@ namespace osu.Game.Screens.Edit.Compose.Components if (clickedBlueprint == null) return false; - adjustableClock?.Seek(clickedBlueprint.DrawableObject.HitObject.StartTime); + adjustableClock?.Seek(clickedBlueprint.HitObject.StartTime); return true; } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { // Special case for when a drag happened instead of a click Schedule(() => endClickSelection()); - return e.Button == MouseButton.Left; - } - protected override bool OnMouseMove(MouseMoveEvent e) - { - if (currentPlacement != null) - { - updatePlacementPosition(e.ScreenSpaceMousePosition); - return true; - } - - return base.OnMouseMove(e); + finishSelectionMovement(); } protected override bool OnDragStart(DragStartEvent e) @@ -152,38 +156,39 @@ namespace osu.Game.Screens.Edit.Compose.Components if (e.Button == MouseButton.Right) return false; - if (!beginSelectionMovement()) + if (movementBlueprint != null) + return true; + + if (DragBox.HandleDrag(e)) { - dragBox.UpdateDrag(e); - dragBox.FadeIn(250, Easing.OutQuint); + DragBox.Show(); + return true; } - return true; + return false; } - protected override bool OnDrag(DragEvent e) + protected override void OnDrag(DragEvent e) { if (e.Button == MouseButton.Right) - return false; + return; - if (!moveCurrentSelection(e)) - dragBox.UpdateDrag(e); + if (DragBox.State == Visibility.Visible) + DragBox.HandleDrag(e); - return true; + moveCurrentSelection(e); } - protected override bool OnDragEnd(DragEndEvent e) + protected override void OnDragEnd(DragEndEvent e) { if (e.Button == MouseButton.Right) - return false; + return; - if (!finishSelectionMovement()) + if (DragBox.State == Visibility.Visible) { - dragBox.FadeOut(250, Easing.OutQuint); + DragBox.Hide(); selectionHandler.UpdateVisibility(); } - - return true; } protected override bool OnKeyDown(KeyDownEvent e) @@ -201,8 +206,6 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - protected override bool OnKeyUp(KeyUpEvent e) => false; - public bool OnPressed(PlatformAction action) { switch (action.ActionType) @@ -215,35 +218,15 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - public bool OnReleased(PlatformAction action) => false; - - protected override void Update() + public void OnReleased(PlatformAction action) { - base.Update(); - - if (currentPlacement != null) - { - if (composer.CursorInPlacementArea) - currentPlacement.State = PlacementState.Shown; - else if (currentPlacement?.PlacementBegun == false) - currentPlacement.State = PlacementState.Hidden; - } } #region Blueprint Addition/Removal - private void addBlueprintFor(HitObject hitObject) - { - var drawable = composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject); - if (drawable == null) - return; - - addBlueprintFor(drawable); - } - private void removeBlueprintFor(HitObject hitObject) { - var blueprint = selectionBlueprints.Single(m => m.DrawableObject.HitObject == hitObject); + var blueprint = selectionBlueprints.SingleOrDefault(m => m.HitObject == hitObject); if (blueprint == null) return; @@ -255,11 +238,9 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionBlueprints.Remove(blueprint); } - private void addBlueprintFor(DrawableHitObject hitObject) + protected virtual void AddBlueprintFor(HitObject hitObject) { - refreshTool(); - - var blueprint = composer.CreateBlueprintFor(hitObject); + var blueprint = CreateBlueprintFor(hitObject); if (blueprint == null) return; @@ -271,37 +252,6 @@ namespace osu.Game.Screens.Edit.Compose.Components #endregion - #region Placement - - /// - /// Refreshes the current placement tool. - /// - private void refreshTool() - { - placementBlueprintContainer.Clear(); - currentPlacement = null; - - var blueprint = CurrentTool?.CreatePlacementBlueprint(); - - if (blueprint != null) - { - placementBlueprintContainer.Child = currentPlacement = blueprint; - - // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame - updatePlacementPosition(inputManager.CurrentState.Mouse.Position); - } - } - - private void updatePlacementPosition(Vector2 screenSpacePosition) - { - Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition), 0).position; - Vector2 snappedScreenSpacePosition = ToScreenSpace(snappedGridPosition); - - currentPlacement.UpdatePosition(snappedScreenSpacePosition); - } - - #endregion - #region Selection /// @@ -324,7 +274,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (!allowDeselection && selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) return; - foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) + foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveChildren) { if (blueprint.IsHovered) { @@ -381,6 +331,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { selectionHandler.HandleSelected(blueprint); selectionBlueprints.ChangeChildDepth(blueprint, 1); + beatmap.SelectedHitObjects.Add(blueprint.HitObject); SelectionChanged?.Invoke(selectionHandler.SelectedHitObjects); } @@ -389,6 +340,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { selectionHandler.HandleDeselected(blueprint); selectionBlueprints.ChangeChildDepth(blueprint, 0); + beatmap.SelectedHitObjects.Remove(blueprint.HitObject); SelectionChanged?.Invoke(selectionHandler.SelectedHitObjects); } @@ -397,27 +349,25 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Selection Movement - private Vector2? screenSpaceMovementStartPosition; + private Vector2? movementBlueprintOriginalPosition; private SelectionBlueprint movementBlueprint; /// /// Attempts to begin the movement of any selected blueprints. /// - /// Whether movement began. - private bool beginSelectionMovement() + private void prepareSelectionMovement() { - Debug.Assert(movementBlueprint == null); + if (!selectionHandler.SelectedBlueprints.Any()) + return; // Any selected blueprint that is hovered can begin the movement of the group, however only the earliest hitobject is used for movement // A special case is added for when a click selection occurred before the drag if (!clickSelectionBegan && !selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) - return false; + return; // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject - movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.DrawableObject.HitObject.StartTime).First(); - screenSpaceMovementStartPosition = movementBlueprint.DrawableObject.ToScreenSpace(movementBlueprint.DrawableObject.OriginPosition); - - return true; + movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First(); + movementBlueprintOriginalPosition = movementBlueprint.SelectionPoint; // todo: unsure if correct } /// @@ -430,17 +380,17 @@ namespace osu.Game.Screens.Edit.Compose.Components if (movementBlueprint == null) return false; - Debug.Assert(screenSpaceMovementStartPosition != null); + Debug.Assert(movementBlueprintOriginalPosition != null); - Vector2 startPosition = screenSpaceMovementStartPosition.Value; - HitObject draggedObject = movementBlueprint.DrawableObject.HitObject; + HitObject draggedObject = movementBlueprint.HitObject; // The final movement position, relative to screenSpaceMovementStartPosition - Vector2 movePosition = startPosition + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; - (Vector2 snappedPosition, double snappedTime) = composer.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); + Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; + + (Vector2 snappedPosition, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); // Move the hitobjects - if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition)))) + if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, ToScreenSpace(snappedPosition)))) return true; // Apply the start time at the newly snapped-to position @@ -460,7 +410,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (movementBlueprint == null) return false; - screenSpaceMovementStartPosition = null; + movementBlueprintOriginalPosition = null; movementBlueprint = null; return true; @@ -474,34 +424,9 @@ namespace osu.Game.Screens.Edit.Compose.Components if (beatmap != null) { - beatmap.HitObjectAdded -= addBlueprintFor; + beatmap.HitObjectAdded -= AddBlueprintFor; beatmap.HitObjectRemoved -= removeBlueprintFor; } } - - private class SelectionBlueprintContainer : Container - { - public IEnumerable AliveBlueprints => AliveInternalChildren.Cast(); - - protected override int Compare(Drawable x, Drawable y) - { - if (!(x is SelectionBlueprint xBlueprint) || !(y is SelectionBlueprint yBlueprint)) - return base.Compare(x, y); - - return Compare(xBlueprint, yBlueprint); - } - - public int Compare(SelectionBlueprint x, SelectionBlueprint y) - { - // dpeth is used to denote selected status (we always want selected blueprints to handle input first). - int d = x.Depth.CompareTo(y.Depth); - if (d != 0) - return d; - - // Put earlier hitobjects towards the end of the list, so they handle input first - int i = y.DrawableObject.HitObject.StartTime.CompareTo(x.DrawableObject.HitObject.StartTime); - return i == 0 ? CompareReverseChildID(x, y) : i; - } - } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs new file mode 100644 index 0000000000..3c41dead5d --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -0,0 +1,149 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// A blueprint container generally displayed as an overlay to a ruleset's playfield. + /// + public class ComposeBlueprintContainer : BlueprintContainer + { + [Resolved] + private HitObjectComposer composer { get; set; } + + private PlacementBlueprint currentPlacement; + + private readonly Container placementBlueprintContainer; + + private InputManager inputManager; + + private readonly IEnumerable drawableHitObjects; + + public ComposeBlueprintContainer(IEnumerable drawableHitObjects) + { + this.drawableHitObjects = drawableHitObjects; + + placementBlueprintContainer = new Container + { + RelativeSizeAxes = Axes.Both + }; + } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(placementBlueprintContainer); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + + #region Placement + + /// + /// Refreshes the current placement tool. + /// + private void refreshTool() + { + placementBlueprintContainer.Clear(); + currentPlacement = null; + + var blueprint = CurrentTool?.CreatePlacementBlueprint(); + + if (blueprint != null) + { + placementBlueprintContainer.Child = currentPlacement = blueprint; + + // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame + updatePlacementPosition(inputManager.CurrentState.Mouse.Position); + } + } + + private void updatePlacementPosition(Vector2 screenSpacePosition) + { + Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition), 0).position; + Vector2 snappedScreenSpacePosition = ToScreenSpace(snappedGridPosition); + + currentPlacement.UpdatePosition(snappedScreenSpacePosition); + } + + #endregion + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (currentPlacement != null) + { + updatePlacementPosition(e.ScreenSpaceMousePosition); + return true; + } + + return base.OnMouseMove(e); + } + + protected override void Update() + { + base.Update(); + + if (currentPlacement != null) + { + if (composer.CursorInPlacementArea) + currentPlacement.State = PlacementState.Shown; + else if (currentPlacement?.PlacementBegun == false) + currentPlacement.State = PlacementState.Hidden; + } + } + + protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) + { + var drawable = drawableHitObjects.FirstOrDefault(d => d.HitObject == hitObject); + if (drawable == null) + return null; + + return CreateBlueprintFor(drawable); + } + + public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; + + protected override void AddBlueprintFor(HitObject hitObject) + { + refreshTool(); + base.AddBlueprintFor(hitObject); + } + + private HitObjectCompositionTool currentTool; + + /// + /// The current placement tool. + /// + public HitObjectCompositionTool CurrentTool + { + get => currentTool; + set + { + if (currentTool == value) + return; + + currentTool = value; + + refreshTool(); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index 2a510e74fd..c5f1bd1575 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,11 +16,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A box that displays the drag selection and provides selection events for users to handle. /// - public class DragBox : CompositeDrawable + public class DragBox : CompositeDrawable, IStateful { - private readonly Action performSelection; + protected readonly Action PerformSelection; - private Drawable box; + protected Drawable Box; /// /// Creates a new . @@ -27,7 +28,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// A delegate that performs drag selection. public DragBox(Action performSelection) { - this.performSelection = performSelection; + PerformSelection = performSelection; RelativeSizeAxes = Axes.Both; AlwaysPresent = true; @@ -37,20 +38,27 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - InternalChild = box = new Container - { - Masking = true, - BorderColour = Color4.White, - BorderThickness = SelectionHandler.BORDER_RADIUS, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f - } - }; + InternalChild = Box = CreateBox(); } - public void UpdateDrag(MouseButtonEvent e) + protected virtual Drawable CreateBox() => new Container + { + Masking = true, + BorderColour = Color4.White, + BorderThickness = SelectionHandler.BORDER_RADIUS, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f + } + }; + + /// + /// Handle a forwarded mouse event. + /// + /// The mouse event. + /// Whether the event should be handled and blocking. + public virtual bool HandleDrag(MouseButtonEvent e) { var dragPosition = e.ScreenSpaceMousePosition; var dragStartPosition = e.ScreenSpaceMouseDownPosition; @@ -63,10 +71,32 @@ namespace osu.Game.Screens.Edit.Compose.Components var topLeft = ToLocalSpace(dragRectangle.TopLeft); var bottomRight = ToLocalSpace(dragRectangle.BottomRight); - box.Position = topLeft; - box.Size = bottomRight - topLeft; + Box.Position = topLeft; + Box.Size = bottomRight - topLeft; - performSelection?.Invoke(dragRectangle); + PerformSelection?.Invoke(dragRectangle); + return true; } + + private Visibility state; + + public Visibility State + { + get => state; + set + { + if (value == state) return; + + state = value; + this.FadeTo(state == Visibility.Hidden ? 0 : 1, 250, Easing.OutQuint); + StateChanged?.Invoke(state); + } + } + + public override void Hide() => State = Visibility.Hidden; + + public override void Show() => State = Visibility.Visible; + + public event Action StateChanged; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs index fe0a47aec8..0792d0f80e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs +++ b/osu.Game/Screens/Edit/Compose/Components/MoveSelectionEvent.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// An event which occurs when a is moved. + /// An event which occurs when a is moved. /// public class MoveSelectionEvent { @@ -16,11 +16,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public readonly SelectionBlueprint Blueprint; - /// - /// The starting screen-space position of the hitobject. - /// - public readonly Vector2 ScreenSpaceStartPosition; - /// /// The expected screen-space position of the hitobject at the current cursor position. /// @@ -29,18 +24,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The distance between and the hitobject's current position, in the coordinate-space of the hitobject's parent. /// - /// - /// This does not use and does not represent the cumulative movement distance. - /// public readonly Vector2 InstantDelta; - public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpaceStartPosition, Vector2 screenSpacePosition) + public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpacePosition) { Blueprint = blueprint; - ScreenSpaceStartPosition = screenSpaceStartPosition; ScreenSpacePosition = screenSpacePosition; - InstantDelta = Blueprint.DrawableObject.Parent.ToLocalSpace(ScreenSpacePosition) - Blueprint.DrawableObject.Position; + InstantDelta = Blueprint.GetInstantDelta(ScreenSpacePosition); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index e2d7855eb5..fc46bf3fed 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -33,12 +33,12 @@ namespace osu.Game.Screens.Edit.Compose.Components public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; - public IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.DrawableObject.HitObject); + public IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject); private Drawable outline; - [Resolved] - private IPlacementHandler placementHandler { get; set; } + [Resolved(CanBeNull = true)] + private EditorBeatmap editorBeatmap { get; set; } public SelectionHandler() { @@ -87,7 +87,9 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; + public void OnReleased(PlatformAction action) + { + } #endregion @@ -102,7 +104,13 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handle a blueprint becoming selected. /// /// The blueprint. - internal void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint); + internal void HandleSelected(SelectionBlueprint blueprint) + { + selectedBlueprints.Add(blueprint); + editorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); + + UpdateVisibility(); + } /// /// Handle a blueprint becoming deselected. @@ -111,6 +119,7 @@ namespace osu.Game.Screens.Edit.Compose.Components internal void HandleDeselected(SelectionBlueprint blueprint) { selectedBlueprints.Remove(blueprint); + editorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); // We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection if (selectedBlueprints.Count == 0) @@ -139,14 +148,12 @@ namespace osu.Game.Screens.Edit.Compose.Components DeselectAll?.Invoke(); blueprint.Select(); } - - UpdateVisibility(); } private void deleteSelected() { foreach (var h in selectedBlueprints.ToList()) - placementHandler.Delete(h.DrawableObject.HitObject); + editorBeatmap.Remove(h.HitObject); } #endregion diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index b4f3b1f610..96395696c3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.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; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -11,10 +12,14 @@ using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class Timeline : ZoomableScrollContainer + [Cached(typeof(IDistanceSnapProvider))] + [Cached] + public class Timeline : ZoomableScrollContainer, IDistanceSnapProvider { public readonly Bindable WaveformVisible = new Bindable(); public readonly IBindable Beatmap = new Bindable(); @@ -143,10 +148,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return false; } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { endUserDrag(); - return base.OnMouseUp(e); + base.OnMouseUp(e); } private void beginUserDrag() @@ -162,5 +167,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (trackWasPlaying) adjustableClock.Start(); } + + [Resolved] + private EditorBeatmap beatmap { get; set; } + + [Resolved] + private IBeatSnapProvider beatSnapProvider { get; set; } + + public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) + { + var targetTime = (position.X / Content.DrawWidth) * track.Length; + return (position, beatSnapProvider.SnapTime(targetTime, targetTime)); + } + + public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException(); + + public float DurationToDistance(double referenceTime, double duration) => throw new NotImplementedException(); + + public double DistanceToDuration(double referenceTime, float distance) => throw new NotImplementedException(); + + public double GetSnappedDurationFromDistance(double referenceTime, float distance) => throw new NotImplementedException(); + + public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => throw new NotImplementedException(); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs new file mode 100644 index 0000000000..3b9cb1df24 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -0,0 +1,141 @@ +// 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.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + internal class TimelineBlueprintContainer : BlueprintContainer + { + [Resolved(CanBeNull = true)] + private Timeline timeline { get; set; } + + private DragEvent lastDragEvent; + + public TimelineBlueprintContainer() + { + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Height = 0.4f; + + AddInternal(new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + DragBox.Alpha = 0; + } + + protected override Container CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + + protected override void OnDrag(DragEvent e) + { + if (timeline != null) + { + var timelineQuad = timeline.ScreenSpaceDrawQuad; + var mouseX = e.ScreenSpaceMousePosition.X; + + // scroll if in a drag and dragging outside visible extents + if (mouseX > timelineQuad.TopRight.X) + timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime)); + else if (mouseX < timelineQuad.TopLeft.X) + timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime)); + } + + base.OnDrag(e); + lastDragEvent = e; + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + lastDragEvent = null; + } + + protected override void Update() + { + // trigger every frame so drags continue to update selection while playback is scrolling the timeline. + if (IsDragged) + OnDrag(lastDragEvent); + + base.Update(); + } + + protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); + + protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject); + + protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect); + + internal class TimelineSelectionHandler : SelectionHandler + { + // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation + public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + } + + private class TimelineDragBox : DragBox + { + private Vector2 lastMouseDown; + private float localMouseDown; + + public TimelineDragBox(Action performSelect) + : base(performSelect) + { + } + + protected override Drawable CreateBox() => new Box + { + RelativeSizeAxes = Axes.Y, + Alpha = 0.3f + }; + + public override bool HandleDrag(MouseButtonEvent e) + { + // store the original position of the mouse down, as we may be scrolled during selection. + if (lastMouseDown != e.ScreenSpaceMouseDownPosition) + { + lastMouseDown = e.ScreenSpaceMouseDownPosition; + localMouseDown = e.MouseDownPosition.X; + } + + float selection1 = localMouseDown; + float selection2 = e.MousePosition.X; + + Box.X = Math.Min(selection1, selection2); + Box.Width = Math.Abs(selection1 - selection2); + + PerformSelection?.Invoke(Box.ScreenSpaceDrawQuad.AABBFloat); + return true; + } + } + + protected class TimelineSelectionBlueprintContainer : Container + { + protected override Container Content { get; } + + public TimelineSelectionBlueprintContainer() + { + AddInternal(new TimelinePart(Content = new Container { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both }); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs new file mode 100644 index 0000000000..2ed5471444 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -0,0 +1,116 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public class TimelineHitObjectBlueprint : SelectionBlueprint + { + private readonly Circle circle; + + private readonly Container extensionBar; + + [UsedImplicitly] + private readonly Bindable startTime; + + public const float THICKNESS = 3; + + private const float circle_size = 16; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || circle.ReceivePositionalInputAt(screenSpacePos); + + public TimelineHitObjectBlueprint(HitObject hitObject) + : base(hitObject) + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + startTime = hitObject.StartTimeBindable.GetBoundCopy(); + startTime.BindValueChanged(time => X = (float)time.NewValue, true); + + RelativePositionAxes = Axes.X; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + if (hitObject is IHasEndTime) + { + AddInternal(extensionBar = new Container + { + CornerRadius = 2, + Masking = true, + Size = new Vector2(1, THICKNESS), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativePositionAxes = Axes.X, + RelativeSizeAxes = Axes.X, + Colour = Color4.Black, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + } + }); + } + + AddInternal(circle = new Circle + { + Size = new Vector2(circle_size), + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + AlwaysPresent = true, + Colour = Color4.White, + BorderColour = Color4.Black, + BorderThickness = THICKNESS, + }); + } + + protected override void Update() + { + base.Update(); + + // no bindable so we perform this every update + Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); + } + + protected override void OnSelected() + { + circle.BorderColour = Color4.Orange; + if (extensionBar != null) + extensionBar.Colour = Color4.Orange; + } + + protected override void OnDeselected() + { + circle.BorderColour = Color4.Black; + if (extensionBar != null) + extensionBar.Colour = Color4.Black; + } + + public override Quad SelectionQuad + { + get + { + // correctly include the circle in the selection quad region, as it is usually outside the blueprint itself. + var circleQuad = circle.ScreenSpaceDrawQuad; + var actualQuad = ScreenSpaceDrawQuad; + + return new Quad(circleQuad.TopLeft, Vector2.ComponentMax(actualQuad.TopRight, circleQuad.TopRight), + circleQuad.BottomLeft, Vector2.ComponentMax(actualQuad.BottomRight, circleQuad.BottomRight)); + } + } + + public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft; + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs deleted file mode 100644 index b20f2fa11d..0000000000 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Screens.Edit.Compose.Components.Timeline -{ - internal class TimelineHitObjectDisplay : TimelinePart - { - private EditorBeatmap beatmap { get; } - - public TimelineHitObjectDisplay(EditorBeatmap beatmap) - { - RelativeSizeAxes = Axes.Both; - - this.beatmap = beatmap; - } - - [BackgroundDependencyLoader] - private void load() - { - foreach (var h in beatmap.HitObjects) - add(h); - - beatmap.HitObjectAdded += add; - beatmap.HitObjectRemoved += remove; - beatmap.StartTimeChanged += h => - { - remove(h); - add(h); - }; - } - - private void remove(HitObject h) - { - foreach (var d in Children.OfType().Where(c => c.HitObject == h)) - d.Expire(); - } - - private void add(HitObject h) - { - var yOffset = Children.Count(d => d.X == h.StartTime); - - Add(new TimelineHitObjectRepresentation(h) { Y = -yOffset * TimelineHitObjectRepresentation.THICKNESS }); - } - - private class TimelineHitObjectRepresentation : CompositeDrawable - { - public const float THICKNESS = 3; - - public readonly HitObject HitObject; - - public TimelineHitObjectRepresentation(HitObject hitObject) - { - HitObject = hitObject; - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - - Width = (float)(hitObject.GetEndTime() - hitObject.StartTime); - - X = (float)hitObject.StartTime; - - RelativePositionAxes = Axes.X; - RelativeSizeAxes = Axes.X; - - if (hitObject is IHasEndTime) - { - AddInternal(new Container - { - CornerRadius = 2, - Masking = true, - Size = new Vector2(1, THICKNESS), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativePositionAxes = Axes.X, - RelativeSizeAxes = Axes.X, - Colour = Color4.Black, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - } - }); - } - - AddInternal(new Circle - { - Size = new Vector2(16), - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - AlwaysPresent = true, - Colour = Color4.White, - BorderColour = Color4.Black, - BorderThickness = THICKNESS, - }); - } - } - } -} diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 1a6aae294a..cdea200e10 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -32,6 +32,6 @@ namespace osu.Game.Screens.Edit.Compose return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer)); } - protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineHitObjectDisplay(EditorBeatmap); + protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineBlueprintContainer(); } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index e212b429b9..eae94a3c8e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -26,6 +26,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; @@ -34,7 +35,8 @@ using osu.Game.Users; namespace osu.Game.Screens.Edit { - public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler + [Cached(typeof(IBeatSnapProvider))] + public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IBeatSnapProvider { public override float BackgroundParallaxAmount => 0.1f; @@ -44,6 +46,8 @@ namespace osu.Game.Screens.Edit public override bool DisallowExternalBeatmapRulesetChanges => true; + public override bool AllowRateAdjustments => false; + [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -77,11 +81,14 @@ namespace osu.Game.Screens.Edit clock.ChangeSource(sourceClock); playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); - editorBeatmap = new EditorBeatmap(playableBeatmap); + editorBeatmap = new EditorBeatmap(playableBeatmap, beatDivisor); dependencies.CacheAs(clock); dependencies.CacheAs(clock); + + // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); + dependencies.CacheAs(editorBeatmap); EditorMenuBar menuBar; @@ -253,12 +260,8 @@ namespace osu.Game.Screens.Edit return false; } - public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; - - public override void OnResuming(IScreen last) + public void OnReleased(GlobalAction action) { - base.OnResuming(last); - Beatmap.Value.Track?.Stop(); } public override void OnEntering(IScreen last) @@ -284,7 +287,6 @@ namespace osu.Game.Screens.Edit private void resetTrack(bool seekToStart = false) { - Beatmap.Value.Track?.ResetSpeedAdjustments(); Beatmap.Value.Track?.Stop(); if (seekToStart) @@ -345,5 +347,11 @@ namespace osu.Game.Screens.Edit saveBeatmap(); beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); } + + public double SnapTime(double referenceTime, double duration) => editorBeatmap.SnapTime(referenceTime, duration); + + public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); + + public int BeatDivisor => beatDivisor.Value; } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 6ed74dfdb0..9c75d40bec 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -8,11 +8,12 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit { - public class EditorBeatmap : IBeatmap + public class EditorBeatmap : IBeatmap, IBeatSnapProvider { /// /// Invoked when a is added to this . @@ -29,13 +30,18 @@ namespace osu.Game.Screens.Edit /// public event Action StartTimeChanged; + public BindableList SelectedHitObjects { get; } = new BindableList(); + public readonly IBeatmap PlayableBeatmap; + private readonly BindableBeatDivisor beatDivisor; + private readonly Dictionary> startTimeBindables = new Dictionary>(); - public EditorBeatmap(IBeatmap playableBeatmap) + public EditorBeatmap(IBeatmap playableBeatmap, BindableBeatDivisor beatDivisor = null) { PlayableBeatmap = playableBeatmap; + this.beatDivisor = beatDivisor; foreach (var obj in HitObjects) trackStartTime(obj); @@ -121,5 +127,17 @@ namespace osu.Game.Screens.Edit return list.Count - 1; } + + public double SnapTime(double referenceTime, double duration) + { + double beatLength = GetBeatLengthAtTime(referenceTime); + + // A 1ms offset prevents rounding errors due to minute variations in duration + return (int)((duration + 1) / beatLength) * beatLength; + } + + public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor; + + public int BeatDivisor => beatDivisor?.Value ?? 1; } } diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index aa8d99b517..8967f24185 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Edit private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private TimelineArea timelineArea; + private Container timelineContainer; [BackgroundDependencyLoader(true)] private void load([CanBeNull] BindableBeatDivisor beatDivisor) @@ -62,11 +62,10 @@ namespace osu.Game.Screens.Edit { new Drawable[] { - new Container + timelineContainer = new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 5 }, - Child = timelineArea = CreateTimelineArea() }, new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } }, @@ -100,14 +99,16 @@ namespace osu.Game.Screens.Edit mainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); - LoadComponentAsync(CreateTimelineContent(), timelineArea.Add); + LoadComponentAsync(new TimelineArea + { + RelativeSizeAxes = Axes.Both, + Child = CreateTimelineContent() + }, timelineContainer.Add); }); } protected abstract Drawable CreateMainContent(); protected virtual Drawable CreateTimelineContent() => new Container(); - - protected TimelineArea CreateTimelineArea() => new TimelineArea { RelativeSizeAxes = Axes.Both }; } } diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index fac6b69e1f..6708ce0ba0 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -194,10 +194,10 @@ namespace osu.Game.Screens.Menu return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { boxHoverLayer.FadeTo(0, 1000, Easing.OutQuint); - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index ed8e4c70f9..d94f8aa11a 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -211,7 +211,9 @@ namespace osu.Game.Screens.Menu } } - public bool OnReleased(GlobalAction action) => false; + public void OnReleased(GlobalAction action) + { + } private bool goBack() { diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs index aaa3a77e74..db2faeb60a 100644 --- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs +++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs @@ -24,16 +24,13 @@ namespace osu.Game.Screens.Menu return false; } - public bool OnReleased(GlobalAction action) + public void OnReleased(GlobalAction action) { if (action == GlobalAction.Back) { if (!Fired) AbortConfirm(); - return true; } - - return false; } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index b28d572b5c..cb5ceefb0f 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -69,6 +69,9 @@ namespace osu.Game.Screens.Menu private ExitConfirmOverlay exitConfirmOverlay; + private ParallaxContainer buttonsContainer; + private SongTicker songTicker; + [BackgroundDependencyLoader(true)] private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) { @@ -89,9 +92,9 @@ namespace osu.Game.Screens.Menu }); } - AddRangeInternal(new Drawable[] + AddRangeInternal(new[] { - new ParallaxContainer + buttonsContainer = new ParallaxContainer { ParallaxAmount = 0.01f, Children = new Drawable[] @@ -107,6 +110,13 @@ namespace osu.Game.Screens.Menu } }, sideFlashes = new MenuSideFlashes(), + songTicker = new SongTicker + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding { Right = 15, Top = 5 } + }, + exitConfirmOverlay?.CreateProxy() ?? Drawable.Empty() }); buttons.StateChanged += state => @@ -190,7 +200,7 @@ namespace osu.Game.Screens.Menu buttons.State = ButtonSystemState.TopLevel; this.FadeIn(FADE_IN_DURATION, Easing.OutQuint); - this.MoveTo(new Vector2(0, 0), FADE_IN_DURATION, Easing.OutQuint); + buttonsContainer.MoveTo(new Vector2(0, 0), FADE_IN_DURATION, Easing.OutQuint); sideFlashes.Delay(FADE_IN_DURATION).FadeIn(64, Easing.InQuint); } @@ -227,7 +237,7 @@ namespace osu.Game.Screens.Menu buttons.State = ButtonSystemState.EnteringMode; this.FadeOut(FADE_OUT_DURATION, Easing.InSine); - this.MoveTo(new Vector2(-800, 0), FADE_OUT_DURATION, Easing.InSine); + buttonsContainer.MoveTo(new Vector2(-800, 0), FADE_OUT_DURATION, Easing.InSine); sideFlashes.FadeOut(64, Easing.OutQuint); } @@ -262,6 +272,9 @@ namespace osu.Game.Screens.Menu } buttons.State = ButtonSystemState.Exit; + + songTicker.Hide(); + this.FadeOut(3000); return base.OnExiting(next); } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 33b6ee8025..be2f29cbe9 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -353,12 +353,11 @@ namespace osu.Game.Screens.Menu return true; } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { - if (e.Button != MouseButton.Left) return false; + if (e.Button != MouseButton.Left) return; logoBounceContainer.ScaleTo(1f, 500, Easing.OutElastic); - return true; } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Screens/Menu/SongTicker.cs b/osu.Game/Screens/Menu/SongTicker.cs new file mode 100644 index 0000000000..c4943e77d5 --- /dev/null +++ b/osu.Game/Screens/Menu/SongTicker.cs @@ -0,0 +1,72 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; +using osu.Game.Graphics; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Framework.Localisation; + +namespace osu.Game.Screens.Menu +{ + public class SongTicker : Container + { + private const int fade_duration = 800; + + [Resolved] + private Bindable beatmap { get; set; } + + private readonly OsuSpriteText title, artist; + + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + + public SongTicker() + { + AutoSizeAxes = Axes.Both; + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + Children = new Drawable[] + { + title = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light, italics: true) + }, + artist = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 16) + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmap.BindValueChanged(_ => Scheduler.AddOnce(show), true); + } + + private void show() + { + var metadata = beatmap.Value.Metadata; + + title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)); + artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)); + + this.FadeInFromZero(fade_duration / 2f) + .Delay(4000) + .Then().FadeOut(fade_duration); + } + } +} diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index adfbe977a4..98e2bc5a03 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -163,8 +163,6 @@ namespace osu.Game.Screens.Play // Don't let mouse down events through the overlay or people can click circles while paused. protected override bool OnMouseDown(MouseDownEvent e) => true; - protected override bool OnMouseUp(MouseUpEvent e) => true; - protected override bool OnMouseMove(MouseMoveEvent e) => true; protected void AddButton(string text, Color4 colour, Action action) @@ -247,16 +245,8 @@ namespace osu.Game.Screens.Play return false; } - public bool OnReleased(GlobalAction action) + public void OnReleased(GlobalAction action) { - switch (action) - { - case GlobalAction.Back: - case GlobalAction.Select: - return true; - } - - return false; } private void buttonSelectionChanged(DialogButton button, bool isSelected) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 6196ce4026..4d28f00f39 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -77,6 +77,19 @@ namespace osu.Game.Screens.Play.HUD case ScoreMeterType.HitErrorRight: createBar(true); break; + + case ScoreMeterType.ColourBoth: + createColour(false); + createColour(true); + break; + + case ScoreMeterType.ColourLeft: + createColour(false); + break; + + case ScoreMeterType.ColourRight: + createColour(true); + break; } } @@ -90,6 +103,24 @@ namespace osu.Game.Screens.Play.HUD Alpha = 0, }; + completeDisplayLoading(display); + } + + private void createColour(bool rightAligned) + { + var display = new ColourHitErrorMeter(hitWindows) + { + Margin = new MarginPadding(margin), + Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Alpha = 0, + }; + + completeDisplayLoading(display); + } + + private void completeDisplayLoading(HitErrorMeter display) + { Add(display); display.FadeInFromZero(fade_duration, Easing.OutQuint); } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 03a0f23fb6..208bdd17ad 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -163,30 +163,9 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters centre.Width = 2.5f; colourBars.Add(centre); - Color4 getColour(HitResult result) - { - switch (result) - { - case HitResult.Meh: - return colours.Yellow; - - case HitResult.Ok: - return colours.Green; - - case HitResult.Good: - return colours.GreenLight; - - case HitResult.Great: - return colours.Blue; - - default: - return colours.BlueLight; - } - } - Drawable createColourBar(HitResult result, float height, bool first = false) { - var colour = getColour(result); + var colour = GetColourForHitResult(result); if (first) { @@ -201,7 +180,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters new Box { RelativeSizeAxes = Axes.Both, - Colour = getColour(result), + Colour = colour, Height = height * gradient_start }, new Box diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs new file mode 100644 index 0000000000..657235bfd4 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -0,0 +1,92 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play.HUD.HitErrorMeters +{ + public class ColourHitErrorMeter : HitErrorMeter + { + private const int animation_duration = 200; + + private readonly JudgementFlow judgementsFlow; + + public ColourHitErrorMeter(HitWindows hitWindows) + : base(hitWindows) + { + AutoSizeAxes = Axes.Both; + InternalChild = judgementsFlow = new JudgementFlow(); + } + + public override void OnNewJudgement(JudgementResult judgement) => judgementsFlow.Push(GetColourForHitResult(HitWindows.ResultFor(judgement.TimeOffset))); + + private class JudgementFlow : FillFlowContainer + { + private const int max_available_judgements = 20; + private const int drawable_judgement_size = 8; + private const int spacing = 2; + + public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); + + public JudgementFlow() + { + AutoSizeAxes = Axes.X; + Height = max_available_judgements * (drawable_judgement_size + spacing) - spacing; + Spacing = new Vector2(0, spacing); + Direction = FillDirection.Vertical; + LayoutDuration = animation_duration; + LayoutEasing = Easing.OutQuint; + } + + public void Push(Color4 colour) + { + Add(new HitErrorCircle(colour, drawable_judgement_size)); + + if (Children.Count > max_available_judgements) + Children.FirstOrDefault(c => !c.IsRemoved)?.Remove(); + } + } + + private class HitErrorCircle : Container + { + public bool IsRemoved { get; private set; } + + private readonly Circle circle; + + public HitErrorCircle(Color4 colour, int size) + { + Size = new Vector2(size); + Child = circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = colour + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + circle.FadeInFromZero(animation_duration, Easing.OutQuint); + circle.MoveToY(-DrawSize.Y); + circle.MoveToY(0, animation_duration, Easing.OutQuint); + } + + public void Remove() + { + IsRemoved = true; + + this.FadeOut(animation_duration, Easing.OutQuint).Expire(); + } + } + } +} diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index dee25048ed..b3edfdedec 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -1,9 +1,12 @@ // 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.Allocation; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; +using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD.HitErrorMeters { @@ -11,11 +14,38 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { protected readonly HitWindows HitWindows; + [Resolved] + private OsuColour colours { get; set; } + protected HitErrorMeter(HitWindows hitWindows) { HitWindows = hitWindows; } public abstract void OnNewJudgement(JudgementResult judgement); + + protected Color4 GetColourForHitResult(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return colours.Red; + + case HitResult.Meh: + return colours.Yellow; + + case HitResult.Ok: + return colours.Green; + + case HitResult.Good: + return colours.GreenLight; + + case HitResult.Great: + return colours.Blue; + + default: + return colours.BlueLight; + } + } } } diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 7946e6d322..684834123b 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -259,16 +259,14 @@ namespace osu.Game.Screens.Play.HUD return false; } - public bool OnReleased(GlobalAction action) + public void OnReleased(GlobalAction action) { switch (action) { case GlobalAction.Back: AbortConfirm(); - return true; + break; } - - return false; } protected override bool OnMouseDown(MouseDownEvent e) @@ -278,11 +276,10 @@ namespace osu.Game.Screens.Play.HUD return true; } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { if (!e.HasAnyButtonPressed) AbortConfirm(); - return true; } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 236bdc8442..a5f8051557 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -131,7 +131,6 @@ namespace osu.Game.Screens.Play BindDrawableRuleset(drawableRuleset); Progress.Objects = drawableRuleset.Objects; - Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; Progress.RequestSeek = time => RequestSeek(time); Progress.ReferenceClock = drawableRuleset.FrameStableClock; } diff --git a/osu.Game/Screens/Play/HotkeyExitOverlay.cs b/osu.Game/Screens/Play/HotkeyExitOverlay.cs index c18aecda55..8d7e2481bf 100644 --- a/osu.Game/Screens/Play/HotkeyExitOverlay.cs +++ b/osu.Game/Screens/Play/HotkeyExitOverlay.cs @@ -17,12 +17,11 @@ namespace osu.Game.Screens.Play return true; } - public bool OnReleased(GlobalAction action) + public void OnReleased(GlobalAction action) { - if (action != GlobalAction.QuickExit) return false; + if (action != GlobalAction.QuickExit) return; AbortConfirm(); - return true; } } } diff --git a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs index f1b851f2d5..58fd941f36 100644 --- a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs +++ b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs @@ -17,12 +17,11 @@ namespace osu.Game.Screens.Play return true; } - public bool OnReleased(GlobalAction action) + public void OnReleased(GlobalAction action) { - if (action != GlobalAction.QuickRetry) return false; + if (action != GlobalAction.QuickRetry) return; AbortConfirm(); - return true; } } } diff --git a/osu.Game/Screens/Play/KeyCounterAction.cs b/osu.Game/Screens/Play/KeyCounterAction.cs index 33d675358c..00eddcc776 100644 --- a/osu.Game/Screens/Play/KeyCounterAction.cs +++ b/osu.Game/Screens/Play/KeyCounterAction.cs @@ -27,15 +27,14 @@ namespace osu.Game.Screens.Play return false; } - public bool OnReleased(T action, bool forwards) + public void OnReleased(T action, bool forwards) { if (!EqualityComparer.Default.Equals(action, Action)) - return false; + return; IsLit = false; if (!forwards) Decrement(); - return false; } } } diff --git a/osu.Game/Screens/Play/KeyCounterKeyboard.cs b/osu.Game/Screens/Play/KeyCounterKeyboard.cs index 29188b6b59..187dcc1264 100644 --- a/osu.Game/Screens/Play/KeyCounterKeyboard.cs +++ b/osu.Game/Screens/Play/KeyCounterKeyboard.cs @@ -27,10 +27,10 @@ namespace osu.Game.Screens.Play return base.OnKeyDown(e); } - protected override bool OnKeyUp(KeyUpEvent e) + protected override void OnKeyUp(KeyUpEvent e) { if (e.Key == Key) IsLit = false; - return base.OnKeyUp(e); + base.OnKeyUp(e); } } } diff --git a/osu.Game/Screens/Play/KeyCounterMouse.cs b/osu.Game/Screens/Play/KeyCounterMouse.cs index 828441de6e..e55525c5e8 100644 --- a/osu.Game/Screens/Play/KeyCounterMouse.cs +++ b/osu.Game/Screens/Play/KeyCounterMouse.cs @@ -45,10 +45,10 @@ namespace osu.Game.Screens.Play return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { if (e.Button == Button) IsLit = false; - return base.OnMouseUp(e); + base.OnMouseUp(e); } } } diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 772d326c7f..3daf5b1ff1 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -143,7 +143,9 @@ namespace osu.Game.Screens.Play return false; } - public bool OnReleased(GlobalAction action) => false; + public void OnReleased(GlobalAction action) + { + } private class FadeContainer : Container, IStateful { @@ -202,10 +204,9 @@ namespace osu.Game.Screens.Play return true; } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { Show(); - return true; } public override void Hide() => State = Visibility.Hidden; @@ -311,10 +312,10 @@ namespace osu.Game.Screens.Play return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { aspect.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 713d27bd16..aa745f5ba2 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Timing; +using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; @@ -18,8 +19,9 @@ namespace osu.Game.Screens.Play { public class SongProgress : OverlayContainer { + private const int info_height = 20; private const int bottom_bar_height = 5; - + private const float graph_height = SquareGraph.Column.WIDTH * 6; private static readonly Vector2 handle_size = new Vector2(10, 18); private const float transition_duration = 200; @@ -30,12 +32,19 @@ namespace osu.Game.Screens.Play public Action RequestSeek; - public override bool HandleNonPositionalInput => AllowSeeking; - public override bool HandlePositionalInput => AllowSeeking; + /// + /// Whether seeking is allowed and the progress bar should be shown. + /// + public readonly Bindable AllowSeeking = new Bindable(); + + public readonly Bindable ShowGraph = new Bindable(); //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). private double lastHitTime => objects.Last().GetEndTime() + 1; + public override bool HandleNonPositionalInput => AllowSeeking.Value; + public override bool HandlePositionalInput => AllowSeeking.Value; + private double firstHitTime => objects.First().StartTime; private IEnumerable objects; @@ -54,27 +63,14 @@ namespace osu.Game.Screens.Play } } - private readonly BindableBool replayLoaded = new BindableBool(); - public IClock ReferenceClock; private IClock gameplayClock; - [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, GameplayClock clock) - { - if (clock != null) - gameplayClock = clock; - - graph.FillColour = bar.FillColour = colours.BlueLighter; - } - public SongProgress() { - const float graph_height = SquareGraph.Column.WIDTH * 6; - - Height = bottom_bar_height + graph_height + handle_size.Y; - Y = bottom_bar_height; + Masking = true; + Height = bottom_bar_height + graph_height + handle_size.Y + info_height; Children = new Drawable[] { @@ -83,8 +79,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Bottom = bottom_bar_height + graph_height }, + Height = info_height, }, graph = new SongProgressGraph { @@ -96,7 +91,6 @@ namespace osu.Game.Screens.Play }, bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size) { - Alpha = 0, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, OnSeek = time => RequestSeek?.Invoke(time), @@ -104,46 +98,34 @@ namespace osu.Game.Screens.Play }; } - protected override void LoadComplete() + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, GameplayClock clock, OsuConfigManager config) { base.LoadComplete(); + if (clock != null) + gameplayClock = clock; + + config.BindWith(OsuSetting.ShowProgressGraph, ShowGraph); + + graph.FillColour = bar.FillColour = colours.BlueLighter; + } + + protected override void LoadComplete() + { Show(); - replayLoaded.ValueChanged += loaded => AllowSeeking = loaded.NewValue; - replayLoaded.TriggerChange(); + AllowSeeking.BindValueChanged(_ => updateBarVisibility(), true); + ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); } public void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); - } - - private bool allowSeeking; - - public bool AllowSeeking - { - get => allowSeeking; - set - { - if (allowSeeking == value) return; - - allowSeeking = value; - updateBarVisibility(); - } - } - - private void updateBarVisibility() - { - bar.FadeTo(allowSeeking ? 1 : 0, transition_duration, Easing.In); - this.MoveTo(new Vector2(0, allowSeeking ? 0 : bottom_bar_height), transition_duration, Easing.In); - - info.Margin = new MarginPadding { Bottom = Height - (allowSeeking ? 0 : handle_size.Y) }; + AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded); } protected override void PopIn() { - updateBarVisibility(); this.FadeIn(500, Easing.OutQuint); } @@ -167,5 +149,28 @@ namespace osu.Game.Screens.Play bar.CurrentTime = gameplayTime; graph.Progress = (int)(graph.ColumnCount * progress); } + + private void updateBarVisibility() + { + bar.ShowHandle = AllowSeeking.Value; + + updateInfoMargin(); + } + + private void updateGraphVisibility() + { + float barHeight = bottom_bar_height + handle_size.Y; + + bar.ResizeHeightTo(ShowGraph.Value ? barHeight + graph_height : barHeight, transition_duration, Easing.In); + graph.MoveToY(ShowGraph.Value ? 0 : bottom_bar_height + graph_height, transition_duration, Easing.In); + + updateInfoMargin(); + } + + private void updateInfoMargin() + { + float finalMargin = bottom_bar_height + (AllowSeeking.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0); + info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In); + } } } diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 9df36c9c2b..5052b32335 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -19,6 +19,23 @@ namespace osu.Game.Screens.Play private readonly Box fill; private readonly Container handleBase; + private readonly Container handleContainer; + + private bool showHandle; + + public bool ShowHandle + { + get => showHandle; + set + { + if (value == showHandle) + return; + + showHandle = value; + + handleBase.FadeTo(showHandle ? 1 : 0, 200); + } + } public Color4 FillColour { @@ -74,7 +91,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, Width = 2, - Height = barHeight + handleBarHeight, + Alpha = 0, Colour = Color4.White, Position = new Vector2(2, 0), Children = new Drawable[] @@ -84,7 +101,7 @@ namespace osu.Game.Screens.Play Name = "HandleBar box", RelativeSizeAxes = Axes.Both, }, - new Container + handleContainer = new Container { Name = "Handle container", Origin = Anchor.BottomCentre, @@ -116,6 +133,7 @@ namespace osu.Game.Screens.Play { base.Update(); + handleBase.Height = Height - handleContainer.Height; float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Math.Clamp(Time.Elapsed / 40, 0, 1)); fill.Width = newX; @@ -127,7 +145,11 @@ namespace osu.Game.Screens.Play protected override void OnUserChange(double value) { scheduledSeek?.Cancel(); - scheduledSeek = Schedule(() => OnSeek?.Invoke(value)); + scheduledSeek = Schedule(() => + { + if (showHandle) + OnSeek?.Invoke(value); + }); } } } diff --git a/osu.Game/Screens/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs index 38636b0c3b..d7eb5db125 100644 --- a/osu.Game/Screens/Ranking/ResultModeButton.cs +++ b/osu.Game/Screens/Ranking/ResultModeButton.cs @@ -92,6 +92,6 @@ namespace osu.Game.Screens.Ranking protected override void OnDeactivated() => colouredPart.FadeColour(inactiveColour, 200, Easing.OutQuint); - public string TooltipText { get; private set; } + public string TooltipText { get; } } } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 451708c1cf..cf49cf0228 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Select { private const float shear_width = 36.75f; - private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGED_CONTAINER_SIZE.Y, 0); + private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0); private readonly IBindable ruleset = new Bindable(); @@ -363,7 +363,7 @@ namespace osu.Game.Screens.Select public class InfoLabel : Container, IHasTooltip { - public string TooltipText { get; private set; } + public string TooltipText { get; } public InfoLabel(BeatmapStatistic statistic) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 68a6ad8845..2ffb73f226 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(Beatmap.Metadata.Artist) || criteria.Artist.Matches(Beatmap.Metadata.ArtistUnicode); + match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(Beatmap.StarDifficulty); + if (match) { var terms = new List(); diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 5b81303788..c851b201d7 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -42,9 +42,15 @@ namespace osu.Game.Screens.Select Group = groupMode.Value, Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, - Ruleset = ruleset.Value + Ruleset = ruleset.Value, }; + if (!minimumStars.IsDefault) + criteria.UserStarDifficulty.Min = minimumStars.Value; + + if (!maximumStars.IsDefault) + criteria.UserStarDifficulty.Max = maximumStars.Value; + FilterQueryParser.ApplyQueries(criteria, query); return criteria; } @@ -142,7 +148,9 @@ namespace osu.Game.Screens.Select private readonly IBindable ruleset = new Bindable(); - private Bindable showConverted; + private readonly Bindable showConverted = new Bindable(); + private readonly Bindable minimumStars = new Bindable(); + private readonly Bindable maximumStars = new Bindable(); public readonly Box Background; @@ -151,9 +159,15 @@ namespace osu.Game.Screens.Select { sortTabs.AccentColour = colours.GreenLight; - showConverted = config.GetBindable(OsuSetting.ShowConvertedBeatmaps); + config.BindWith(OsuSetting.ShowConvertedBeatmaps, showConverted); showConverted.ValueChanged += _ => updateCriteria(); + config.BindWith(OsuSetting.DisplayStarsMinimum, minimumStars); + minimumStars.ValueChanged += _ => updateCriteria(); + + config.BindWith(OsuSetting.DisplayStarsMaximum, maximumStars); + maximumStars.ValueChanged += _ => updateCriteria(); + ruleset.BindTo(parentRuleset); ruleset.BindValueChanged(_ => updateCriteria()); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index c4d9996377..9fa57af01d 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -26,6 +26,12 @@ namespace osu.Game.Screens.Select public OptionalTextFilter Creator; public OptionalTextFilter Artist; + public OptionalRange UserStarDifficulty = new OptionalRange + { + IsLowerInclusive = true, + IsUpperInclusive = true + }; + public string[] SearchTerms = Array.Empty(); public RulesetInfo Ruleset; diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index b77da36748..4dcab60548 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -118,10 +118,10 @@ namespace osu.Game.Screens.Select return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { box.FadeOut(Footer.TRANSITION_LENGTH, Easing.OutQuint); - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 14c9eb2035..9bc5181f6f 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -44,11 +44,11 @@ namespace osu.Game.Screens.Select return base.OnKeyDown(e); } - protected override bool OnKeyUp(KeyUpEvent e) + protected override void OnKeyUp(KeyUpEvent e) { secondaryActive = e.ShiftPressed; updateText(); - return base.OnKeyUp(e); + base.OnKeyUp(e); } private void updateText() diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index ff9beafb23..4e4653cb57 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -60,10 +60,10 @@ namespace osu.Game.Screens.Select.Options return base.OnMouseDown(e); } - protected override bool OnMouseUp(MouseUpEvent e) + protected override void OnMouseUp(MouseUpEvent e) { flash.FadeTo(0, 1000, Easing.OutQuint); - return base.OnMouseUp(e); + base.OnMouseUp(e); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8f7ad2022d..1a29f336ea 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Select { public abstract class SongSelect : OsuScreen, IKeyBindingHandler { - public static readonly Vector2 WEDGED_CONTAINER_SIZE = new Vector2(0.5f, 245); + public static readonly float WEDGE_HEIGHT = 245; protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; @@ -96,98 +96,116 @@ namespace osu.Game.Screens.Select AddRangeInternal(new Drawable[] { - new ParallaxContainer - { - Masking = true, - ParallaxAmount = 0.005f, - RelativeSizeAxes = Axes.Both, - Children = new[] - { - new WedgeBackground - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = -150 }, - Size = new Vector2(WEDGED_CONTAINER_SIZE.X, 1), - } - } - }, - new Container - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(WEDGED_CONTAINER_SIZE.X, 1), - Padding = new MarginPadding - { - Bottom = Footer.HEIGHT, - Top = WEDGED_CONTAINER_SIZE.Y + left_area_padding, - Left = left_area_padding, - Right = left_area_padding * 2, - }, - Child = BeatmapDetails = new BeatmapDetailArea - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 10, Right = 5 }, - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 2, //avoid horizontal masking so the panels don't clip when screen stack is pushed. - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 0.5f, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Top = FilterControl.HEIGHT, - Bottom = Footer.HEIGHT - }, - Child = Carousel = new BeatmapCarousel - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(1 - WEDGED_CONTAINER_SIZE.X, 1), - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - SelectionChanged = updateSelectedBeatmap, - BeatmapSetsChanged = carouselBeatmapsLoaded, - }, - }, - FilterControl = new FilterControl - { - RelativeSizeAxes = Axes.X, - Height = FilterControl.HEIGHT, - FilterChanged = ApplyFilterToCarousel, - Background = { Width = 2 }, - }, - } - }, - }, - beatmapInfoWedge = new BeatmapInfoWedge - { - Size = WEDGED_CONTAINER_SIZE, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding - { - Top = left_area_padding, - Right = left_area_padding, - }, - }, new ResetScrollContainer(() => Carousel.ScrollToSelected()) { RelativeSizeAxes = Axes.Y, Width = 250, - } + }, + new VerticalMaskingContainer + { + Children = new Drawable[] + { + new GridContainer // used for max width implementation + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 850), + }, + Content = new[] + { + new Drawable[] + { + new ParallaxContainer + { + ParallaxAmount = 0.005f, + RelativeSizeAxes = Axes.Both, + Child = new WedgeBackground + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = -150 }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Top = FilterControl.HEIGHT, + Bottom = Footer.HEIGHT + }, + Child = Carousel = new BeatmapCarousel + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + SelectionChanged = updateSelectedBeatmap, + BeatmapSetsChanged = carouselBeatmapsLoaded, + }, + } + }, + } + }, + FilterControl = new FilterControl + { + RelativeSizeAxes = Axes.X, + Height = FilterControl.HEIGHT, + FilterChanged = ApplyFilterToCarousel, + Background = { Width = 2 }, + }, + new GridContainer // used for max width implementation + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 650), + }, + Content = new[] + { + new Drawable[] + { + new Container + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + + Children = new Drawable[] + { + beatmapInfoWedge = new BeatmapInfoWedge + { + Height = WEDGE_HEIGHT, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding + { + Top = left_area_padding, + Right = left_area_padding, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Bottom = Footer.HEIGHT, + Top = WEDGE_HEIGHT + left_area_padding, + Left = left_area_padding, + Right = left_area_padding * 2, + }, + Child = BeatmapDetails = new BeatmapDetailArea + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 10, Right = 5 }, + }, + }, + } + }, + }, + } + } + } + }, }); if (ShowFooter) @@ -681,7 +699,9 @@ namespace osu.Game.Screens.Select return false; } - public bool OnReleased(GlobalAction action) => action == GlobalAction.Select; + public void OnReleased(GlobalAction action) + { + } protected override bool OnKeyDown(KeyDownEvent e) { @@ -703,6 +723,29 @@ namespace osu.Game.Screens.Select return base.OnKeyDown(e); } + private class VerticalMaskingContainer : Container + { + private const float panel_overflow = 1.2f; + + protected override Container Content { get; } + + public VerticalMaskingContainer() + { + RelativeSizeAxes = Axes.Both; + Masking = true; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Width = panel_overflow; //avoid horizontal masking so the panels don't clip when screen stack is pushed. + InternalChild = Content = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 1 / panel_overflow, + }; + } + } + private class ResetScrollContainer : Container { private readonly Action onHoverAction; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 7a84ac009a..94d7395ecf 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -14,7 +14,7 @@ namespace osu.Game.Storyboards.Drawables { public class DrawableStoryboard : Container { - public Storyboard Storyboard { get; private set; } + public Storyboard Storyboard { get; } protected override Container Content { get; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index ced3b9c1b6..eabb78bac5 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -15,7 +15,7 @@ namespace osu.Game.Storyboards.Drawables { public class DrawableStoryboardAnimation : TextureAnimation, IFlippable, IVectorScalable { - public StoryboardAnimation Animation { get; private set; } + public StoryboardAnimation Animation { get; } private bool flipH; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index fd2d441f34..39f5418902 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -10,7 +10,7 @@ namespace osu.Game.Storyboards.Drawables { public class DrawableStoryboardLayer : LifetimeManagementContainer { - public StoryboardLayer Layer { get; private set; } + public StoryboardLayer Layer { get; } public bool Enabled; public override bool IsPresent => Enabled && base.IsPresent; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index c0da0e9c0e..d8d3248659 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -15,7 +15,7 @@ namespace osu.Game.Storyboards.Drawables { public class DrawableStoryboardSprite : Sprite, IFlippable, IVectorScalable { - public StoryboardSprite Sprite { get; private set; } + public StoryboardSprite Sprite { get; } private bool flipH; diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 22e1929419..f411ad04f3 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -16,7 +16,7 @@ namespace osu.Game.Storyboards private readonly List loops = new List(); private readonly List triggers = new List(); - public string Path { get; set; } + public string Path { get; } public bool IsDrawable => HasCommands; public Anchor Origin; diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index 3233ee160d..6565f98666 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual }); } - protected void AddBlueprint(SelectionBlueprint blueprint) + protected void AddBlueprint(OverlaySelectionBlueprint blueprint) { Add(blueprint.With(d => { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f41c5fee7f..0ea558bbc7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index bb4a59a601..a215bc65e8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 12571be31d..6d131bf423 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -19,8 +19,8 @@ HINT DO_NOT_SHOW HINT - HINT - HINT + WARNING + WARNING WARNING WARNING WARNING @@ -298,6 +298,7 @@ GL GLSL HID + HTML HUD ID IL @@ -765,6 +766,7 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True True True