From 11ae1da65a092266786f4eae988bd0713584dbc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 May 2022 19:30:06 +0200 Subject: [PATCH] Hoist reference replacement logic to overlay level --- osu.Game/Overlays/Mods/ModColumn.cs | 116 ++------------------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 85 ++++++++++----- 2 files changed, 64 insertions(+), 137 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 8b3896a88c..deaaacd775 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -13,7 +13,6 @@ using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -54,49 +53,18 @@ namespace osu.Game.Overlays.Mods } } - private Func? filter; - /// /// A function determining whether each mod in the column should be displayed. /// A return value of means that the mod is not filtered and therefore its corresponding panel should be displayed. /// A return value of means that the mod is filtered out and therefore its corresponding panel should be hidden. /// - public Func? Filter - { - get => filter; - set - { - filter = value; - updateState(); - } - } + public Func? Filter { get; set; } // TODO: remove later /// /// Determines whether this column should accept user input. /// public Bindable Active = new BindableBool(true); - private readonly Bindable allFiltered = new BindableBool(); - - /// - /// True if all of the panels in this column have been filtered out by the current . - /// - public IBindable AllFiltered => allFiltered; - - /// - /// List of mods marked as selected in this column. - /// - /// - /// Note that the mod instances returned by this property are owned solely by this column - /// (as in, they are locally-managed clones, to ensure proper isolation from any other external instances). - /// - public IReadOnlyList SelectedMods { get; private set; } = Array.Empty(); - - /// - /// Invoked when a mod panel has been selected interactively by the user. - /// - public event Action? SelectionChangedByUser; - protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); @@ -299,7 +267,11 @@ namespace osu.Game.Overlays.Mods Task? loadTask; - latestLoadTask = loadTask = LoadComponentsAsync(panels, onPanelsLoaded, (cancellationTokenSource = new CancellationTokenSource()).Token); + latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded => + { + panelFlow.ChildrenEnumerable = loaded; + updateState(); + }, (cancellationTokenSource = new CancellationTokenSource()).Token); loadTask.ContinueWith(_ => { if (loadTask == latestLoadTask) @@ -307,27 +279,9 @@ namespace osu.Game.Overlays.Mods }); } - private void onPanelsLoaded(IEnumerable loaded) - { - panelFlow.ChildrenEnumerable = loaded; - - updateState(); - - foreach (var panel in panelFlow) - { - panel.Active.BindValueChanged(_ => panelStateChanged(panel)); - } - } - private void updateState() { - foreach (var panel in panelFlow) - { - panel.Active.Value = SelectedMods.Contains(panel.Mod); - panel.ApplyFilter(Filter); - } - - allFiltered.Value = panelFlow.All(panel => panel.Filtered.Value); + Alpha = availableMods.All(mod => mod.Filtered.Value) ? 0 : 1; if (toggleAllCheckbox != null && !SelectionAnimationRunning) { @@ -336,62 +290,6 @@ namespace osu.Game.Overlays.Mods } } - /// - /// This flag helps to determine the source of changes to . - /// If the value is false, then are changing due to a user selection on the UI. - /// If the value is true, then are changing due to an external call. - /// - private bool externalSelectionUpdateInProgress; - - private void panelStateChanged(ModPanel panel) - { - if (externalSelectionUpdateInProgress) - return; - - var newSelectedMods = panel.Active.Value - ? SelectedMods.Append(panel.Mod) - : SelectedMods.Except(panel.Mod.Yield()); - - SelectedMods = newSelectedMods.ToArray(); - updateState(); - SelectionChangedByUser?.Invoke(); - } - - /// - /// Adjusts the set of selected mods in this column to match the passed in . - /// - /// - /// This method exists to be able to receive mod instances that come from potentially-external sources and to copy the changes across to this column's state. - /// uses this to substitute any external mod references in - /// to references that are owned by this column. - /// - internal void SetSelection(IReadOnlyList mods) - { - externalSelectionUpdateInProgress = true; - - var newSelection = new List(); - - foreach (var modState in this.availableMods) - { - var matchingSelectedMod = mods.SingleOrDefault(selected => selected.GetType() == modState.GetType()); - - if (matchingSelectedMod != null) - { - modState.Mod.CopyFrom(matchingSelectedMod); - newSelection.Add(modState.Mod); - } - else - { - modState.Mod.ResetSettingsToDefaults(); - } - } - - SelectedMods = newSelection; - updateState(); - - externalSelectionUpdateInProgress = false; - } - #region Bulk select / deselect private const double initial_multiple_selection_delay = 120; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ace0576b96..567e91e3fa 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -207,14 +207,9 @@ namespace osu.Game.Overlays.Mods { updateMultiplier(); updateCustomisation(val); - updateSelectionFromBindable(); + updateFromExternalSelection(); }, true); - foreach (var column in columnFlow.Columns) - { - column.SelectionChangedByUser += updateBindableFromSelection; - } - customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); // Start scrolled slightly to the right to give the user a sense that @@ -248,7 +243,6 @@ namespace osu.Game.Overlays.Mods { var column = CreateModColumn(modType, toggleKeys).With(column => { - column.Filter = IsValidMod; // spacing applied here rather than via `columnFlow.Spacing` to avoid uneven gaps when some of the columns are hidden. column.Margin = new MarginPadding { Right = 10 }; }); @@ -286,6 +280,9 @@ namespace osu.Game.Overlays.Mods .Select(mod => new ModState(mod.DeepClone())) .ToArray(); + foreach (var modState in modStates) + modState.Active.BindValueChanged(_ => updateFromInternalSelection()); + localAvailableMods[modType] = modStates; } @@ -362,20 +359,50 @@ namespace osu.Game.Overlays.Mods TopLevelContent.MoveToY(-modAreaHeight, transition_duration, Easing.InOutCubic); } - private void updateSelectionFromBindable() - { - // `SelectedMods` may contain mod references that come from external sources. - // to ensure isolation, first pull in the potentially-external change into the mod columns... - foreach (var column in columnFlow.Columns) - column.SetSelection(SelectedMods.Value); + /// + /// This flag helps to determine the source of changes to . + /// If the value is false, then are changing due to a user selection on the UI. + /// If the value is true, then are changing due to an external change. + /// + private bool externalSelectionUpdateInProgress; - // and then, when done, replace the potentially-external mod references in `SelectedMods` with ones we own. - updateBindableFromSelection(); + private void updateFromExternalSelection() + { + externalSelectionUpdateInProgress = true; + + var newSelection = new List(); + + foreach (var modState in localAvailableMods.SelectMany(pair => pair.Value)) + { + var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType()); + + if (matchingSelectedMod != null) + { + modState.Mod.CopyFrom(matchingSelectedMod); + modState.Active.Value = true; + newSelection.Add(modState.Mod); + } + else + { + modState.Mod.ResetSettingsToDefaults(); + modState.Active.Value = false; + } + } + + SelectedMods.Value = newSelection; + + externalSelectionUpdateInProgress = false; } - private void updateBindableFromSelection() + private void updateFromInternalSelection() { - var candidateSelection = columnFlow.Columns.SelectMany(column => column.SelectedMods).ToArray(); + if (externalSelectionUpdateInProgress) + return; + + var candidateSelection = localAvailableMods.SelectMany(pair => pair.Value) + .Where(modState => modState.Active.Value) + .Select(modState => modState.Mod) + .ToArray(); // the following guard intends to check cases where we've already replaced potentially-external mod references with our own and avoid endless recursion. // TODO: replace custom comparer with System.Collections.Generic.ReferenceEqualityComparer when fully on .NET 6 @@ -406,10 +433,12 @@ namespace osu.Game.Overlays.Mods { var column = columnFlow[i].Column; - double delay = column.AllFiltered.Value ? 0 : nonFilteredColumnCount * 30; - double duration = column.AllFiltered.Value ? 0 : fade_in_duration; + bool allFiltered = column.AvailableMods.All(modState => modState.Filtered.Value); + + double delay = allFiltered ? 0 : nonFilteredColumnCount * 30; + double duration = allFiltered ? 0 : fade_in_duration; float startingYPosition = 0; - if (!column.AllFiltered.Value) + if (!allFiltered) startingYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance; column.TopLevelContent @@ -418,7 +447,7 @@ namespace osu.Game.Overlays.Mods .MoveToY(0, duration, Easing.OutQuint) .FadeIn(duration, Easing.OutQuint); - if (!column.AllFiltered.Value) + if (!allFiltered) nonFilteredColumnCount += 1; } } @@ -439,9 +468,11 @@ namespace osu.Game.Overlays.Mods { var column = columnFlow[i].Column; - double duration = column.AllFiltered.Value ? 0 : fade_out_duration; + bool allFiltered = column.AvailableMods.All(modState => modState.Filtered.Value); + + double duration = allFiltered ? 0 : fade_out_duration; float newYPosition = 0; - if (!column.AllFiltered.Value) + if (!allFiltered) newYPosition = nonFilteredColumnCount % 2 == 0 ? -distance : distance; column.FlushPendingSelections(); @@ -449,7 +480,7 @@ namespace osu.Game.Overlays.Mods .MoveToY(newYPosition, duration, Easing.OutQuint) .FadeOut(duration, Easing.OutQuint); - if (!column.AllFiltered.Value) + if (!allFiltered) nonFilteredColumnCount += 1; } } @@ -593,8 +624,8 @@ namespace osu.Game.Overlays.Mods protected override void LoadComplete() { base.LoadComplete(); - Active.BindValueChanged(_ => updateState()); - Column.AllFiltered.BindValueChanged(_ => updateState(), true); + + Active.BindValueChanged(_ => updateState(), true); FinishTransforms(); } @@ -604,8 +635,6 @@ namespace osu.Game.Overlays.Mods { Colour4 targetColour; - Column.Alpha = Column.AllFiltered.Value ? 0 : 1; - if (Column.Active.Value) targetColour = Colour4.White; else