1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-04 22:57:46 +08:00
Files
osu-lazer/osu.Game/Screens/Edit/Setup/FormSampleSetChooser.cs
T
Bartłomiej Dach 4ee4aac708 Fix changing combo colours in beatmap without custom samples opening new sample set popover (#36400)
Reported [on reddit of all
places](https://www.reddit.com/r/osugame/comments/1qhnfdn/every_time_i_make_an_adjustment_to_the_hitcircle/).

The reason that this is happening is as follows:

- Changing a combo colour raises `BeatmapSkinChanged`
- `BeatmapSkinChanged` getting raised triggers the sample chooser
dropdown to repopulate its items (as intended and correctly)
- Setting items of a dropdown can also change its `Current.Value`,
because the relevant logic attempts to ensure that `Current.Value` is
valid in line with the new items
- Therefore the `Current.Value` of the dropdown changes from `null` to
sample set `-1` which corresponds to the "Add new..." item as it's the
only item in the dropdown...
- which is indistinguishable from the sequence of events that happens if
the user manually opens the dropdown and clicks the "Add new..." item.

This change sidesteps this entire car crash by just ensuring that even
beatmaps without custom samples have at least set number 1 initialised
(even if it's empty and has no samples). This means that the initial
value of the dropdown is never `null`, and that every time that the
value changes to the set `-1` it actually is due to user action.

Should have known better than to play these dumb games.
2026-01-20 17:10:40 +09:00

146 lines
4.7 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Globalization;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Screens.Edit.Setup
{
public partial class FormSampleSetChooser : FormDropdown<EditorBeatmapSkin.SampleSet?>, IHasPopover
{
private EditorBeatmapSkin? beatmapSkin;
public FormSampleSetChooser()
{
Caption = "Custom sample sets";
}
[BackgroundDependencyLoader]
private void load(EditorBeatmap editorBeatmap)
{
beatmapSkin = editorBeatmap.BeatmapSkin;
}
protected override void LoadComplete()
{
base.LoadComplete();
populateItems();
if (beatmapSkin != null)
beatmapSkin.BeatmapSkinChanged += populateItems;
Current.Value = Items.First(i => i?.SampleSetIndex > 0);
Current.BindValueChanged(val =>
{
if (val.NewValue?.SampleSetIndex == -1)
this.ShowPopover();
});
}
private void populateItems()
{
var items = beatmapSkin?.GetAvailableSampleSets().ToList() ?? [new EditorBeatmapSkin.SampleSet(1)];
items.Add(new EditorBeatmapSkin.SampleSet(-1, "Add new..."));
Items = items;
}
protected override LocalisableString GenerateItemText(EditorBeatmapSkin.SampleSet? item)
{
if (item == null)
return string.Empty;
return base.GenerateItemText(item);
}
public Popover GetPopover() => new NewSampleSetPopover(
Items.Any(i => i?.SampleSetIndex > 0) ? Items.Max(i => i!.SampleSetIndex) : 0,
idx =>
{
if (idx == null)
{
Current.Value = Items.FirstOrDefault(i => i?.SampleSetIndex > 0);
return;
}
if (Items.SingleOrDefault(i => i?.SampleSetIndex == idx) is EditorBeatmapSkin.SampleSet existing)
{
Current.Value = existing;
return;
}
var sampleSet = new EditorBeatmapSkin.SampleSet(idx.Value, $@"Custom #{idx}");
var newItems = Items.ToList();
newItems.Insert(newItems.Count - 1, sampleSet);
Items = newItems;
Current.Value = sampleSet;
});
protected override void Dispose(bool isDisposing)
{
if (beatmapSkin != null)
beatmapSkin.BeatmapSkinChanged -= populateItems;
base.Dispose(isDisposing);
}
private partial class NewSampleSetPopover : OsuPopover
{
private readonly int currentLargestIndex;
private readonly Action<int?> onCommit;
private int? committedIndex;
private LabelledNumberBox numberBox = null!;
public NewSampleSetPopover(int currentLargestIndex, Action<int?> onCommit)
{
this.currentLargestIndex = currentLargestIndex;
this.onCommit = onCommit;
}
[BackgroundDependencyLoader]
private void load()
{
Child = numberBox = new LabelledNumberBox
{
RelativeSizeAxes = Axes.None,
Width = 250,
Label = "Sample set index",
Current = { Value = (currentLargestIndex + 1).ToString(CultureInfo.InvariantCulture) }
};
numberBox.OnCommit += (_, _) =>
{
if (int.TryParse(numberBox.Current.Value, out int parsed))
committedIndex = parsed;
Hide();
};
}
protected override void OnFocus(FocusEvent e)
{
base.OnFocus(e);
// avoids infinite refocus loop
if (committedIndex == null)
GetContainingFocusManager()?.ChangeFocus(numberBox);
}
public override void Hide()
{
if (State.Value == Visibility.Visible)
onCommit.Invoke(committedIndex > 0 ? committedIndex : null);
base.Hide();
}
}
}
}