From 8f3eb9a422c25472c35f08cc6c82f98881eadf84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Sep 2020 17:57:57 +0900 Subject: [PATCH 01/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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 b70a20e7f198b236b4b404cb3eea54dca3ae105e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 16:56:39 +0900 Subject: [PATCH 11/12] Avoid consuming keystrokes in editor when a modifier key is held down --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b81e0ce159..7c9d996039 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -192,6 +192,9 @@ namespace osu.Game.Rulesets.Edit protected override bool OnKeyDown(KeyDownEvent e) { + if (e.ControlPressed || e.AltPressed || e.SuperPressed) + return false; + if (checkLeftToggleFromKey(e.Key, out var leftIndex)) { var item = toolboxCollection.Items.ElementAtOrDefault(leftIndex); From 51cc644b7b203110f88b7bee33cd6321ab1fb66b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:42:49 +0900 Subject: [PATCH 12/12] Fix set access to SelectionHandler Co-authored-by: Dan Balasescu --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8934a1b6d3..8908520cd7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public Container SelectionBlueprints { get; private set; } - protected SelectionHandler SelectionHandler; + protected SelectionHandler SelectionHandler { get; private set; } [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; }