From 8f3eb9a422c25472c35f08cc6c82f98881eadf84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 17:57:57 +0900 Subject: [PATCH 01/13] Fix taiko sample selection not updating when changing strong/rim type --- .../Objects/Drawables/DrawableHit.cs | 32 +++++++++++++++---- .../Drawables/DrawableTaikoHitObject.cs | 28 +++++++++++++++- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 92ae7e0fd3..f4234445d6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables : base(hit) { FillMode = FillMode.Fit; + + updateActionsFromType(); } [BackgroundDependencyLoader] @@ -50,21 +52,39 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables type = HitObject.TypeBindable.GetBoundCopy(); type.BindValueChanged(_ => { - updateType(); + updateActionsFromType(); + + // will overwrite samples, should only be called on change. + updateSamplesFromTypeChange(); + RecreatePieces(); }); - - updateType(); } - private void updateType() + private void updateSamplesFromTypeChange() + { + var rimSamples = HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); + + bool isRimType = HitObject.Type == HitType.Rim; + + if (isRimType != rimSamples.Any()) + { + if (isRimType) + HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }); + else + { + foreach (var sample in rimSamples) + HitObject.Samples.Remove(sample); + } + } + } + + private void updateActionsFromType() { HitActions = HitObject.Type == HitType.Centre ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre } : new[] { TaikoAction.LeftRim, TaikoAction.RightRim }; - - RecreatePieces(); } protected override SkinnableDrawable CreateMainPiece() => HitObject.Type == HitType.Centre diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 929cf8a937..0474de8453 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -141,7 +141,31 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private void load() { isStrong = HitObject.IsStrongBindable.GetBoundCopy(); - isStrong.BindValueChanged(_ => RecreatePieces(), true); + isStrong.BindValueChanged(_ => + { + // will overwrite samples, should only be called on change. + updateSamplesFromStrong(); + + RecreatePieces(); + }); + + RecreatePieces(); + } + + private void updateSamplesFromStrong() + { + var strongSamples = HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); + + if (isStrong.Value != strongSamples.Any()) + { + if (isStrong.Value) + HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH }); + else + { + foreach (var sample in strongSamples) + HitObject.Samples.Remove(sample); + } + } } protected virtual void RecreatePieces() @@ -150,6 +174,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece?.Expire(); Content.Add(MainPiece = CreateMainPiece()); + + LoadSamples(); } protected override void AddNestedHitObject(DrawableHitObject hitObject) From 9a0e5ac154242ee6fd1764582899e6c1c48114b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 18:09:40 +0900 Subject: [PATCH 02/13] Handle type/strength changes from samples changes --- .../Objects/Drawables/DrawableSlider.cs | 4 +-- .../Objects/Drawables/DrawableSpinner.cs | 4 +-- .../Objects/Drawables/DrawableHit.cs | 16 ++++++++-- .../Drawables/DrawableTaikoHitObject.cs | 30 ++++++++++++------- .../Objects/Drawables/DrawableHitObject.cs | 10 +++++-- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 07f40f763b..fc3bb76ae1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private SkinnableSound slidingSample; - protected override void LoadSamples() + protected override void LoadSamples(bool changed) { - base.LoadSamples(); + base.LoadSamples(changed); slidingSample?.Expire(); slidingSample = null; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a57bb466c7..e78c886beb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -88,9 +88,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; - protected override void LoadSamples() + protected override void LoadSamples(bool changed) { - base.LoadSamples(); + base.LoadSamples(changed); spinningSample?.Expire(); spinningSample = null; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index f4234445d6..7608514817 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -61,19 +61,29 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables }); } + private HitSampleInfo[] rimSamples => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); + + protected override void LoadSamples(bool changed) + { + base.LoadSamples(changed); + + if (changed) + type.Value = rimSamples.Any() ? HitType.Rim : HitType.Centre; + } + private void updateSamplesFromTypeChange() { - var rimSamples = HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); + var samples = rimSamples; bool isRimType = HitObject.Type == HitType.Rim; - if (isRimType != rimSamples.Any()) + if (isRimType != samples.Any()) { if (isRimType) HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }); else { - foreach (var sample in rimSamples) + foreach (var sample in samples) HitObject.Samples.Remove(sample); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 0474de8453..4b1cd80967 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -1,19 +1,19 @@ // 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; -using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Objects.Drawables; -using osuTK; -using System.Linq; -using osu.Game.Audio; using System.Collections.Generic; +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; +using osu.Framework.Input.Bindings; +using osu.Game.Audio; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { @@ -152,17 +152,27 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RecreatePieces(); } + private HitSampleInfo[] strongSamples => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); + + protected override void LoadSamples(bool changed) + { + base.LoadSamples(changed); + + if (changed) + isStrong.Value = strongSamples.Any(); + } + private void updateSamplesFromStrong() { - var strongSamples = HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); + var samples = strongSamples; - if (isStrong.Value != strongSamples.Any()) + if (isStrong.Value != samples.Any()) { if (isStrong.Value) HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH }); else { - foreach (var sample in strongSamples) + foreach (var sample in samples) HitObject.Samples.Remove(sample); } } @@ -174,8 +184,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece?.Expire(); Content.Add(MainPiece = CreateMainPiece()); - - LoadSamples(); } protected override void AddNestedHitObject(DrawableHitObject hitObject) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 581617b567..4521556182 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (Result == null) throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); - LoadSamples(); + LoadSamples(false); } protected override void LoadAsyncComplete() @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } samplesBindable = HitObject.SamplesBindable.GetBoundCopy(); - samplesBindable.CollectionChanged += (_, __) => LoadSamples(); + samplesBindable.CollectionChanged += (_, __) => LoadSamples(true); apply(HitObject); } @@ -157,7 +157,11 @@ namespace osu.Game.Rulesets.Objects.Drawables updateState(ArmedState.Idle, true); } - protected virtual void LoadSamples() + /// + /// Called to perform sample-related logic. + /// + /// True if triggered from a post-load change to samples. + protected virtual void LoadSamples(bool changed) { if (Samples != null) { From fee379b4b9b997bd53e73e76fe626a2bd71fff93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 18:12:07 +0900 Subject: [PATCH 03/13] Reword xmldoc for legibility --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4521556182..08235e4ff8 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -158,9 +158,9 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Called to perform sample-related logic. + /// Invoked by the base to populate samples, once on initial load and potentially again on any change to the samples collection. /// - /// True if triggered from a post-load change to samples. + /// True if triggered from a change to the samples collection. protected virtual void LoadSamples(bool changed) { if (Samples != null) From 156edf24c2736b28d134b9675e64aab08fd1f708 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 13:22:14 +0900 Subject: [PATCH 04/13] Change properties to methods and improve naming --- .../Objects/Drawables/DrawableHit.cs | 10 +++++----- .../Objects/Drawables/DrawableTaikoHitObject.cs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 7608514817..7cf77885cf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -61,29 +61,29 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables }); } - private HitSampleInfo[] rimSamples => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); + private HitSampleInfo[] getRimSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); protected override void LoadSamples(bool changed) { base.LoadSamples(changed); if (changed) - type.Value = rimSamples.Any() ? HitType.Rim : HitType.Centre; + type.Value = getRimSamples().Any() ? HitType.Rim : HitType.Centre; } private void updateSamplesFromTypeChange() { - var samples = rimSamples; + var rimSamples = getRimSamples(); bool isRimType = HitObject.Type == HitType.Rim; - if (isRimType != samples.Any()) + if (isRimType != rimSamples.Any()) { if (isRimType) HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP }); else { - foreach (var sample in samples) + foreach (var sample in rimSamples) HitObject.Samples.Remove(sample); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 4b1cd80967..f3790e65af 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -152,27 +152,27 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RecreatePieces(); } - private HitSampleInfo[] strongSamples => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); + private HitSampleInfo[] getStrongSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); protected override void LoadSamples(bool changed) { base.LoadSamples(changed); if (changed) - isStrong.Value = strongSamples.Any(); + isStrong.Value = getStrongSamples().Any(); } private void updateSamplesFromStrong() { - var samples = strongSamples; + var strongSamples = getStrongSamples(); - if (isStrong.Value != samples.Any()) + if (isStrong.Value != strongSamples.Any()) { if (isStrong.Value) HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH }); else { - foreach (var sample in samples) + foreach (var sample in strongSamples) HitObject.Samples.Remove(sample); } } From 33fad27ec21705999cb6ad123d6c588fe60ff802 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 13:28:29 +0900 Subject: [PATCH 05/13] Avoid API change to DrawableHitObject --- .../Objects/Drawables/DrawableSlider.cs | 4 ++-- .../Objects/Drawables/DrawableSpinner.cs | 4 ++-- .../Objects/Drawables/DrawableHit.cs | 11 +++++------ .../Objects/Drawables/DrawableTaikoHitObject.cs | 11 +++++------ .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 7 +++---- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index fc3bb76ae1..07f40f763b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private SkinnableSound slidingSample; - protected override void LoadSamples(bool changed) + protected override void LoadSamples() { - base.LoadSamples(changed); + base.LoadSamples(); slidingSample?.Expire(); slidingSample = null; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index e78c886beb..a57bb466c7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -88,9 +88,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; - protected override void LoadSamples(bool changed) + protected override void LoadSamples() { - base.LoadSamples(changed); + base.LoadSamples(); spinningSample?.Expire(); spinningSample = null; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 7cf77885cf..3a6eaa83db 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -36,11 +36,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private bool pressHandledThisFrame; - private Bindable type; + private readonly Bindable type; public DrawableHit(Hit hit) : base(hit) { + type = HitObject.TypeBindable.GetBoundCopy(); FillMode = FillMode.Fit; updateActionsFromType(); @@ -49,7 +50,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables [BackgroundDependencyLoader] private void load() { - type = HitObject.TypeBindable.GetBoundCopy(); type.BindValueChanged(_ => { updateActionsFromType(); @@ -63,12 +63,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private HitSampleInfo[] getRimSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray(); - protected override void LoadSamples(bool changed) + protected override void LoadSamples() { - base.LoadSamples(changed); + base.LoadSamples(); - if (changed) - type.Value = getRimSamples().Any() ? HitType.Rim : HitType.Centre; + type.Value = getRimSamples().Any() ? HitType.Rim : HitType.Centre; } private void updateSamplesFromTypeChange() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index f3790e65af..9cd23383c4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected Vector2 BaseSize; protected SkinnableDrawable MainPiece; - private Bindable isStrong; + private readonly Bindable isStrong; private readonly Container strongHitContainer; @@ -128,6 +128,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables : base(hitObject) { HitObject = hitObject; + isStrong = HitObject.IsStrongBindable.GetBoundCopy(); Anchor = Anchor.CentreLeft; Origin = Anchor.Custom; @@ -140,7 +141,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables [BackgroundDependencyLoader] private void load() { - isStrong = HitObject.IsStrongBindable.GetBoundCopy(); isStrong.BindValueChanged(_ => { // will overwrite samples, should only be called on change. @@ -154,12 +154,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private HitSampleInfo[] getStrongSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray(); - protected override void LoadSamples(bool changed) + protected override void LoadSamples() { - base.LoadSamples(changed); + base.LoadSamples(); - if (changed) - isStrong.Value = getStrongSamples().Any(); + isStrong.Value = getStrongSamples().Any(); } private void updateSamplesFromStrong() diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 08235e4ff8..28d3a39096 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (Result == null) throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); - LoadSamples(false); + LoadSamples(); } protected override void LoadAsyncComplete() @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } samplesBindable = HitObject.SamplesBindable.GetBoundCopy(); - samplesBindable.CollectionChanged += (_, __) => LoadSamples(true); + samplesBindable.CollectionChanged += (_, __) => LoadSamples(); apply(HitObject); } @@ -160,8 +160,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Invoked by the base to populate samples, once on initial load and potentially again on any change to the samples collection. /// - /// True if triggered from a change to the samples collection. - protected virtual void LoadSamples(bool changed) + protected virtual void LoadSamples() { if (Samples != null) { From 937d5870b3bde89750e79e3f0f5237641114a026 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 18:18:34 +0900 Subject: [PATCH 06/13] Add a basic file selector with extension filtering support --- .../Settings/TestSceneDirectorySelector.cs | 3 +- .../Visual/Settings/TestSceneFileSelector.cs | 18 +++++ .../Screens/StablePathSelectScreen.cs | 2 +- .../UserInterfaceV2/DirectorySelector.cs | 79 ++++++++++++------- .../Graphics/UserInterfaceV2/FileSelector.cs | 68 ++++++++++++++++ .../Maintenance/MigrationSelectScreen.cs | 2 +- 6 files changed, 138 insertions(+), 34 deletions(-) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/FileSelector.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs index 0cd0f13b5f..082d85603e 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneDirectorySelector.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Platform; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Tests.Visual.Settings @@ -11,7 +10,7 @@ namespace osu.Game.Tests.Visual.Settings public class TestSceneDirectorySelector : OsuTestScene { [BackgroundDependencyLoader] - private void load(GameHost host) + private void load() { Add(new DirectorySelector { RelativeSizeAxes = Axes.Both }); } diff --git a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs new file mode 100644 index 0000000000..08173148b0 --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs @@ -0,0 +1,18 @@ +// 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.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Tests.Visual.Settings +{ + public class TestSceneFileSelector : OsuTestScene + { + [BackgroundDependencyLoader] + private void load() + { + Add(new FileSelector { RelativeSizeAxes = Axes.Both }); + } + } +} diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index b4d56f60c7..717b43f704 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -129,7 +129,7 @@ namespace osu.Game.Tournament.Screens protected virtual void ChangePath() { - var target = directorySelector.CurrentDirectory.Value.FullName; + var target = directorySelector.CurrentPath.Value.FullName; var fileBasedIpc = ipc as FileBasedIPC; Logger.Log($"Changing Stable CE location to {target}"); diff --git a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs b/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs index ae34281bfb..a1cd074619 100644 --- a/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/DirectorySelector.cs @@ -28,11 +28,11 @@ namespace osu.Game.Graphics.UserInterfaceV2 private GameHost host { get; set; } [Cached] - public readonly Bindable CurrentDirectory = new Bindable(); + public readonly Bindable CurrentPath = new Bindable(); public DirectorySelector(string initialPath = null) { - CurrentDirectory.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); + CurrentPath.Value = new DirectoryInfo(initialPath ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)); } [BackgroundDependencyLoader] @@ -74,7 +74,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 } }; - CurrentDirectory.BindValueChanged(updateDisplay, true); + CurrentPath.BindValueChanged(updateDisplay, true); } private void updateDisplay(ValueChangedEvent directory) @@ -92,22 +92,27 @@ namespace osu.Game.Graphics.UserInterfaceV2 } else { - directoryFlow.Add(new ParentDirectoryPiece(CurrentDirectory.Value.Parent)); + directoryFlow.Add(new ParentDirectoryPiece(CurrentPath.Value.Parent)); - foreach (var dir in CurrentDirectory.Value.GetDirectories().OrderBy(d => d.Name)) - { - if ((dir.Attributes & FileAttributes.Hidden) == 0) - directoryFlow.Add(new DirectoryPiece(dir)); - } + directoryFlow.AddRange(GetEntriesForPath(CurrentPath.Value)); } } catch (Exception) { - CurrentDirectory.Value = directory.OldValue; + CurrentPath.Value = directory.OldValue; this.FlashColour(Color4.Red, 300); } } + protected virtual IEnumerable GetEntriesForPath(DirectoryInfo path) + { + foreach (var dir in path.GetDirectories().OrderBy(d => d.Name)) + { + if ((dir.Attributes & FileAttributes.Hidden) == 0) + yield return new DirectoryPiece(dir); + } + } + private class CurrentDirectoryDisplay : CompositeDrawable { [Resolved] @@ -126,7 +131,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Spacing = new Vector2(5), - Height = DirectoryPiece.HEIGHT, + Height = DisplayPiece.HEIGHT, Direction = FillDirection.Horizontal, }, }; @@ -150,7 +155,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 flow.ChildrenEnumerable = new Drawable[] { - new OsuSpriteText { Text = "Current Directory: ", Font = OsuFont.Default.With(size: DirectoryPiece.HEIGHT), }, + new OsuSpriteText { Text = "Current Directory: ", Font = OsuFont.Default.With(size: DisplayPiece.HEIGHT), }, new ComputerPiece(), }.Concat(pathPieces); } @@ -198,24 +203,44 @@ namespace osu.Game.Graphics.UserInterfaceV2 } } - private class DirectoryPiece : CompositeDrawable + protected class DirectoryPiece : DisplayPiece { - public const float HEIGHT = 20; - - protected const float FONT_SIZE = 16; - protected readonly DirectoryInfo Directory; - private readonly string displayName; - - protected FillFlowContainer Flow; - [Resolved] private Bindable currentDirectory { get; set; } public DirectoryPiece(DirectoryInfo directory, string displayName = null) + : base(displayName) { Directory = directory; + } + + protected override bool OnClick(ClickEvent e) + { + currentDirectory.Value = Directory; + return true; + } + + protected override string FallbackName => Directory.Name; + + protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) + ? FontAwesome.Solid.Database + : FontAwesome.Regular.Folder; + } + + protected abstract class DisplayPiece : CompositeDrawable + { + public const float HEIGHT = 20; + + protected const float FONT_SIZE = 16; + + private readonly string displayName; + + protected FillFlowContainer Flow; + + protected DisplayPiece(string displayName = null) + { this.displayName = displayName; } @@ -259,20 +284,14 @@ namespace osu.Game.Graphics.UserInterfaceV2 { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = displayName ?? Directory.Name, + Text = displayName ?? FallbackName, Font = OsuFont.Default.With(size: FONT_SIZE) }); } - protected override bool OnClick(ClickEvent e) - { - currentDirectory.Value = Directory; - return true; - } + protected abstract string FallbackName { get; } - protected virtual IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) - ? FontAwesome.Solid.Database - : FontAwesome.Regular.Folder; + protected abstract IconUsage? Icon { get; } } } } diff --git a/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs new file mode 100644 index 0000000000..861d1887e1 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs @@ -0,0 +1,68 @@ +// 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.IO; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class FileSelector : DirectorySelector + { + private readonly string[] validFileExtensions; + + [Cached] + public readonly Bindable CurrentFile = new Bindable(); + + public FileSelector(string initialPath = null, string[] validFileExtensions = null) + : base(initialPath) + { + this.validFileExtensions = validFileExtensions ?? Array.Empty(); + } + + protected override IEnumerable GetEntriesForPath(DirectoryInfo path) + { + foreach (var dir in base.GetEntriesForPath(path)) + yield return dir; + + IEnumerable files = path.GetFiles(); + + if (validFileExtensions.Length > 0) + files = files.Where(f => validFileExtensions.Contains(f.Extension)); + + foreach (var file in files.OrderBy(d => d.Name)) + { + if ((file.Attributes & FileAttributes.Hidden) == 0) + yield return new FilePiece(file); + } + } + + protected class FilePiece : DisplayPiece + { + private readonly FileInfo file; + + [Resolved] + private Bindable currentFile { get; set; } + + public FilePiece(FileInfo file) + { + this.file = file; + } + + protected override bool OnClick(ClickEvent e) + { + currentFile.Value = file; + return true; + } + + protected override string FallbackName => file.Name; + + protected override IconUsage? Icon => FontAwesome.Regular.FileAudio; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 79d842a617..ad540e3691 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private void start() { - var target = directorySelector.CurrentDirectory.Value; + var target = directorySelector.CurrentPath.Value; try { From a890e5830d8e2af9a200fd170d91202bc87fd514 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:42:28 +0900 Subject: [PATCH 07/13] Add more file icons --- .../Graphics/UserInterfaceV2/FileSelector.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs index 861d1887e1..e10b8f7033 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FileSelector.cs @@ -62,7 +62,33 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override string FallbackName => file.Name; - protected override IconUsage? Icon => FontAwesome.Regular.FileAudio; + protected override IconUsage? Icon + { + get + { + switch (file.Extension) + { + case ".ogg": + case ".mp3": + case ".wav": + return FontAwesome.Regular.FileAudio; + + case ".jpg": + case ".jpeg": + case ".png": + return FontAwesome.Regular.FileImage; + + case ".mp4": + case ".avi": + case ".mov": + case ".flv": + return FontAwesome.Regular.FileVideo; + + default: + return FontAwesome.Regular.File; + } + } + } } } } From a8c85ed882f2e7c7424e86d4a79ac7a6e05387ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:42:37 +0900 Subject: [PATCH 08/13] Add test for filtered mode --- .../Visual/Settings/TestSceneFileSelector.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs index 08173148b0..311e4c3362 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneFileSelector.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterfaceV2; @@ -9,10 +9,16 @@ namespace osu.Game.Tests.Visual.Settings { public class TestSceneFileSelector : OsuTestScene { - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestAllFiles() { - Add(new FileSelector { RelativeSizeAxes = Axes.Both }); + AddStep("create", () => Child = new FileSelector { RelativeSizeAxes = Axes.Both }); + } + + [Test] + public void TestJpgFilesOnly() + { + AddStep("create", () => Child = new FileSelector(validFileExtensions: new[] { ".jpg" }) { RelativeSizeAxes = Axes.Both }); } } } From 325bfdbf7139a779341d7ecd48fb758b2de4556a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 13:25:42 +0900 Subject: [PATCH 09/13] Fix hard crash on hitting an out of range key (Q~P) --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b81e0ce159..e2a49221c0 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Edit if (checkRightToggleFromKey(e.Key, out var rightIndex)) { - var item = togglesCollection.Children[rightIndex]; + var item = togglesCollection.ElementAtOrDefault(rightIndex); if (item is SettingsCheckbox checkbox) { From 7f9a5f5f0df1bceb5c8f1d3d1aedf2be9d6acadf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 14:25:24 +0900 Subject: [PATCH 10/13] Ensure setup screen text boxes commit on losing focus --- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 290aba3468..902c23c3c6 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -46,6 +46,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override OsuTextBox CreateComponent() => new OsuTextBox { + CommitOnFocusLost = true, Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, From 50290f3cb4730ca93a1ed388ce432241520bd8e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 15:09:47 +0900 Subject: [PATCH 11/13] Rework ternary states to fix context menus not updating after already displayed --- .../Compose/Components/SelectionHandler.cs | 170 ++++++++++-------- 1 file changed, 93 insertions(+), 77 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6e2c8bd01c..1c564c6605 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Humanizer; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -59,6 +61,8 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { + createStateBindables(); + InternalChild = content = new Container { Children = new Drawable[] @@ -308,6 +312,90 @@ namespace osu.Game.Screens.Edit.Compose.Components #endregion + #region Selection State + + private readonly Bindable selectionNewComboState = new Bindable(); + + private readonly Dictionary> selectionSampleStates = new Dictionary>(); + + /// + /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) + /// + private void createStateBindables() + { + // hit samples + var sampleTypes = new[] { HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_FINISH }; + + foreach (var sampleName in sampleTypes) + { + var bindable = new Bindable + { + Description = sampleName.Replace("hit", string.Empty).Titleize() + }; + + bindable.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + RemoveHitSample(sampleName); + break; + + case TernaryState.True: + AddHitSample(sampleName); + break; + } + }; + + selectionSampleStates[sampleName] = bindable; + } + + // new combo + selectionNewComboState.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + SetNewCombo(false); + break; + + case TernaryState.True: + SetNewCombo(true); + break; + } + }; + + // bring in updates from selection changes + EditorBeatmap.HitObjectUpdated += _ => updateTernaryStates(); + EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => updateTernaryStates(); + } + + private void updateTernaryStates() + { + selectionNewComboState.Value = getStateFromBlueprints(selectedBlueprints.Select(b => (IHasComboInformation)b.HitObject).Count(h => h.NewCombo)); + + foreach (var (sampleName, bindable) in selectionSampleStates) + { + bindable.Value = getStateFromBlueprints(SelectedHitObjects.Count(h => h.Samples.Any(s => s.Name == sampleName))); + } + } + + /// + /// Given a count of "true" blueprints, retrieve the correct ternary display state. + /// + private TernaryState getStateFromBlueprints(int count) + { + if (count == 0) + return TernaryState.False; + + if (count < SelectedHitObjects.Count()) + return TernaryState.Indeterminate; + + return TernaryState.True; + } + + #endregion + #region Context Menu public MenuItem[] ContextMenuItems @@ -322,7 +410,9 @@ namespace osu.Game.Screens.Edit.Compose.Components items.AddRange(GetContextMenuItemsForSelection(selectedBlueprints)); if (selectedBlueprints.All(b => b.HitObject is IHasComboInformation)) - items.Add(createNewComboMenuItem()); + { + items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = selectionNewComboState } }); + } if (selectedBlueprints.Count == 1) items.AddRange(selectedBlueprints[0].ContextMenuItems); @@ -331,12 +421,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { new OsuMenuItem("Sound") { - Items = new[] - { - createHitSampleMenuItem("Whistle", HitSampleInfo.HIT_WHISTLE), - createHitSampleMenuItem("Clap", HitSampleInfo.HIT_CLAP), - createHitSampleMenuItem("Finish", HitSampleInfo.HIT_FINISH) - } + Items = selectionSampleStates.Select(kvp => + new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() }, new OsuMenuItem("Delete", MenuItemType.Destructive, deleteSelected), }); @@ -353,76 +439,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) => Enumerable.Empty(); - private MenuItem createNewComboMenuItem() - { - return new TernaryStateMenuItem("New combo", MenuItemType.Standard, setNewComboState) - { - State = { Value = getHitSampleState() } - }; - - void setNewComboState(TernaryState state) - { - switch (state) - { - case TernaryState.False: - SetNewCombo(false); - break; - - case TernaryState.True: - SetNewCombo(true); - break; - } - } - - TernaryState getHitSampleState() - { - int countExisting = selectedBlueprints.Select(b => (IHasComboInformation)b.HitObject).Count(h => h.NewCombo); - - if (countExisting == 0) - return TernaryState.False; - - if (countExisting < SelectedHitObjects.Count()) - return TernaryState.Indeterminate; - - return TernaryState.True; - } - } - - private MenuItem createHitSampleMenuItem(string name, string sampleName) - { - return new TernaryStateMenuItem(name, MenuItemType.Standard, setHitSampleState) - { - State = { Value = getHitSampleState() } - }; - - void setHitSampleState(TernaryState state) - { - switch (state) - { - case TernaryState.False: - RemoveHitSample(sampleName); - break; - - case TernaryState.True: - AddHitSample(sampleName); - break; - } - } - - TernaryState getHitSampleState() - { - int countExisting = SelectedHitObjects.Count(h => h.Samples.Any(s => s.Name == sampleName)); - - if (countExisting == 0) - return TernaryState.False; - - if (countExisting < SelectedHitObjects.Count()) - return TernaryState.Indeterminate; - - return TernaryState.True; - } - } - #endregion } } From a859fe78ee52841fd458f4949e2b1e72d3c59a1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 15:27:45 +0900 Subject: [PATCH 12/13] Expose update ternary state method and use better state determination function --- .../Compose/Components/SelectionHandler.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 1c564c6605..8a152a9c57 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -366,32 +366,32 @@ namespace osu.Game.Screens.Edit.Compose.Components }; // bring in updates from selection changes - EditorBeatmap.HitObjectUpdated += _ => updateTernaryStates(); - EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => updateTernaryStates(); + EditorBeatmap.HitObjectUpdated += _ => UpdateTernaryStates(); + EditorBeatmap.SelectedHitObjects.CollectionChanged += (sender, args) => UpdateTernaryStates(); } - private void updateTernaryStates() + /// + /// Called when context menu ternary states may need to be recalculated (selection changed or hitobject updated). + /// + protected virtual void UpdateTernaryStates() { - selectionNewComboState.Value = getStateFromBlueprints(selectedBlueprints.Select(b => (IHasComboInformation)b.HitObject).Count(h => h.NewCombo)); + selectionNewComboState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.NewCombo); foreach (var (sampleName, bindable) in selectionSampleStates) { - bindable.Value = getStateFromBlueprints(SelectedHitObjects.Count(h => h.Samples.Any(s => s.Name == sampleName))); + bindable.Value = GetStateFromSelection(SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); } } /// - /// Given a count of "true" blueprints, retrieve the correct ternary display state. + /// Given a selection target and a function of truth, retrieve the correct ternary state for display. /// - private TernaryState getStateFromBlueprints(int count) + protected TernaryState GetStateFromSelection(IEnumerable selection, Func func) { - if (count == 0) - return TernaryState.False; + if (selection.Any(func)) + return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; - if (count < SelectedHitObjects.Count()) - return TernaryState.Indeterminate; - - return TernaryState.True; + return TernaryState.False; } #endregion From 727ab98d22d2b748f1148f8bcc1f7cb27b41f538 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 15:32:45 +0900 Subject: [PATCH 13/13] Update taiko selection handler with new logic --- .../Edit/TaikoSelectionHandler.cs | 128 +++++++++--------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index 40565048c2..a3ecf7ed95 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -1,9 +1,10 @@ // 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 osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; @@ -14,75 +15,80 @@ namespace osu.Game.Rulesets.Taiko.Edit { public class TaikoSelectionHandler : SelectionHandler { + private readonly Bindable selectionRimState = new Bindable(); + private readonly Bindable selectionStrongState = new Bindable(); + + [BackgroundDependencyLoader] + private void load() + { + selectionStrongState.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + SetStrongState(false); + break; + + case TernaryState.True: + SetStrongState(true); + break; + } + }; + + selectionRimState.ValueChanged += state => + { + switch (state.NewValue) + { + case TernaryState.False: + SetRimState(false); + break; + + case TernaryState.True: + SetRimState(true); + break; + } + }; + } + + public void SetStrongState(bool state) + { + var hits = SelectedHitObjects.OfType(); + + ChangeHandler.BeginChange(); + + foreach (var h in hits) + h.IsStrong = state; + + ChangeHandler.EndChange(); + } + + public void SetRimState(bool state) + { + var hits = SelectedHitObjects.OfType(); + + ChangeHandler.BeginChange(); + + foreach (var h in hits) + h.Type = state ? HitType.Rim : HitType.Centre; + + ChangeHandler.EndChange(); + } + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) { if (selection.All(s => s.HitObject is Hit)) - { - var hits = selection.Select(s => s.HitObject).OfType(); - - yield return new TernaryStateMenuItem("Rim", action: state => - { - ChangeHandler.BeginChange(); - - foreach (var h in hits) - { - switch (state) - { - case TernaryState.True: - h.Type = HitType.Rim; - break; - - case TernaryState.False: - h.Type = HitType.Centre; - break; - } - } - - ChangeHandler.EndChange(); - }) - { - State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) } - }; - } + yield return new TernaryStateMenuItem("Rim") { State = { BindTarget = selectionRimState } }; if (selection.All(s => s.HitObject is TaikoHitObject)) - { - var hits = selection.Select(s => s.HitObject).OfType(); - - yield return new TernaryStateMenuItem("Strong", action: state => - { - ChangeHandler.BeginChange(); - - foreach (var h in hits) - { - switch (state) - { - case TernaryState.True: - h.IsStrong = true; - break; - - case TernaryState.False: - h.IsStrong = false; - break; - } - - EditorBeatmap?.UpdateHitObject(h); - } - - ChangeHandler.EndChange(); - }) - { - State = { Value = getTernaryState(hits, h => h.IsStrong) } - }; - } + yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; } - private TernaryState getTernaryState(IEnumerable selection, Func func) + protected override void UpdateTernaryStates() { - if (selection.Any(func)) - return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; + base.UpdateTernaryStates(); - return TernaryState.False; + selectionRimState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.Type == HitType.Rim); + selectionStrongState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.IsStrong); } } }