mirror of
https://github.com/ppy/osu.git
synced 2025-03-22 21:00:33 +08:00
Hoist reference replacement logic to overlay level
This commit is contained in:
parent
05a21fbbe0
commit
11ae1da65a
@ -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<Mod, bool>? filter;
|
||||
|
||||
/// <summary>
|
||||
/// A function determining whether each mod in the column should be displayed.
|
||||
/// A return value of <see langword="true"/> means that the mod is not filtered and therefore its corresponding panel should be displayed.
|
||||
/// A return value of <see langword="false"/> means that the mod is filtered out and therefore its corresponding panel should be hidden.
|
||||
/// </summary>
|
||||
public Func<Mod, bool>? Filter
|
||||
{
|
||||
get => filter;
|
||||
set
|
||||
{
|
||||
filter = value;
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
public Func<Mod, bool>? Filter { get; set; } // TODO: remove later
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this column should accept user input.
|
||||
/// </summary>
|
||||
public Bindable<bool> Active = new BindableBool(true);
|
||||
|
||||
private readonly Bindable<bool> allFiltered = new BindableBool();
|
||||
|
||||
/// <summary>
|
||||
/// True if all of the panels in this column have been filtered out by the current <see cref="Filter"/>.
|
||||
/// </summary>
|
||||
public IBindable<bool> AllFiltered => allFiltered;
|
||||
|
||||
/// <summary>
|
||||
/// List of mods marked as selected in this column.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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).
|
||||
/// </remarks>
|
||||
public IReadOnlyList<Mod> SelectedMods { get; private set; } = Array.Empty<Mod>();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a mod panel has been selected interactively by the user.
|
||||
/// </summary>
|
||||
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<ModPanel> 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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This flag helps to determine the source of changes to <see cref="SelectedMods"/>.
|
||||
/// If the value is false, then <see cref="SelectedMods"/> are changing due to a user selection on the UI.
|
||||
/// If the value is true, then <see cref="SelectedMods"/> are changing due to an external <see cref="SetSelection"/> call.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the set of selected mods in this column to match the passed in <paramref name="mods"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// <see cref="ModSelectOverlay"/> uses this to substitute any external mod references in <see cref="ModSelectOverlay.SelectedMods"/>
|
||||
/// to references that are owned by this column.
|
||||
/// </remarks>
|
||||
internal void SetSelection(IReadOnlyList<Mod> mods)
|
||||
{
|
||||
externalSelectionUpdateInProgress = true;
|
||||
|
||||
var newSelection = new List<Mod>();
|
||||
|
||||
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;
|
||||
|
@ -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);
|
||||
/// <summary>
|
||||
/// This flag helps to determine the source of changes to <see cref="SelectedMods"/>.
|
||||
/// If the value is false, then <see cref="SelectedMods"/> are changing due to a user selection on the UI.
|
||||
/// If the value is true, then <see cref="SelectedMods"/> are changing due to an external <see cref="SelectedMods"/> change.
|
||||
/// </summary>
|
||||
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<Mod>();
|
||||
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user