[同步官方更新]

This commit is contained in:
LA
2025-12-21 01:28:16 +08:00
parent 1b7b92a551
commit ac488b9ae7
19 changed files with 335 additions and 162 deletions

View File

@@ -21,7 +21,7 @@
] ]
}, },
"ppy.localisationanalyser.tools": { "ppy.localisationanalyser.tools": {
"version": "2024.802.0", "version": "2025.1208.0",
"commands": [ "commands": [
"localisation" "localisation"
] ]

View File

@@ -5,7 +5,6 @@ using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using Humanizer; using Humanizer;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Input;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
@@ -196,11 +195,6 @@ namespace osu.Game.Tests.Visual.Editing
clickSamplePiece(1); clickSamplePiece(1);
samplePopoverHasSingleBank(HitSampleInfo.BANK_SOFT); samplePopoverHasSingleBank(HitSampleInfo.BANK_SOFT);
setBankViaPopover(string.Empty);
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT);
samplePopoverHasSingleBank(HitSampleInfo.BANK_SOFT);
setBankViaPopover(HitSampleInfo.BANK_DRUM); setBankViaPopover(HitSampleInfo.BANK_DRUM);
hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM); hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM);
@@ -219,11 +213,6 @@ namespace osu.Game.Tests.Visual.Editing
clickSamplePiece(1); clickSamplePiece(1);
samplePopoverHasIndeterminateBank(); samplePopoverHasIndeterminateBank();
setBankViaPopover(string.Empty);
hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT);
samplePopoverHasIndeterminateBank();
setBankViaPopover(HitSampleInfo.BANK_NORMAL); setBankViaPopover(HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL); hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleBank(1, HitSampleInfo.BANK_NORMAL); hitObjectHasSampleBank(1, HitSampleInfo.BANK_NORMAL);
@@ -878,17 +867,17 @@ namespace osu.Game.Tests.Visual.Editing
private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () => private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () =>
{ {
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault(); var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
var textBox = popover?.ChildrenOfType<OsuTextBox>().First(); var dropdown = popover?.ChildrenOfType<LabelledDropdown<string>>().First();
return textBox?.Current.Value == bank && string.IsNullOrEmpty(textBox.PlaceholderText.ToString()); return dropdown?.Current.Value == bank;
}); });
private void samplePopoverHasIndeterminateBank() => AddUntilStep("sample popover has indeterminate bank", () => private void samplePopoverHasIndeterminateBank() => AddUntilStep("sample popover has indeterminate bank", () =>
{ {
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault(); var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
var textBox = popover?.ChildrenOfType<OsuTextBox>().First(); var dropdown = popover?.ChildrenOfType<LabelledDropdown<string>>().First();
return textBox != null && string.IsNullOrEmpty(textBox.Current.Value) && !string.IsNullOrEmpty(textBox.PlaceholderText.ToString()); return dropdown?.Current.Value == "(multiple)";
}); });
private void dismissPopover() private void dismissPopover()
@@ -920,23 +909,15 @@ namespace osu.Game.Tests.Visual.Editing
private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () => private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () =>
{ {
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single(); var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
var textBox = popover.ChildrenOfType<LabelledTextBox>().First(); var textBox = popover.ChildrenOfType<LabelledDropdown<string>>().First();
textBox.Current.Value = bank; textBox.Current.Value = bank;
// force a commit via keyboard.
// this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit.
((IFocusManager)InputManager).ChangeFocus(textBox);
InputManager.Key(Key.Enter);
}); });
private void setAdditionBankViaPopover(string bank) => AddStep($"set addition bank {bank} via popover", () => private void setAdditionBankViaPopover(string bank) => AddStep($"set addition bank {bank} via popover", () =>
{ {
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single(); var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
var textBox = popover.ChildrenOfType<LabelledTextBox>().ToArray()[1]; var textBox = popover.ChildrenOfType<LabelledDropdown<string>>().ToArray()[1];
textBox.Current.Value = bank; textBox.Current.Value = bank;
// force a commit via keyboard.
// this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit.
((IFocusManager)InputManager).ChangeFocus(textBox);
InputManager.Key(Key.Enter);
}); });
private void toggleAdditionViaPopover(int index) => AddStep($"toggle addition {index} via popover", () => private void toggleAdditionViaPopover(int index) => AddStep($"toggle addition {index} via popover", () =>

View File

@@ -11,7 +11,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
[Test] [Test]
public void TestLabelledDropdown() public void TestLabelledDropdown()
=> AddStep(@"create dropdown", () => Child = new LabelledDropdown<string> => AddStep(@"create dropdown", () => Child = new LabelledDropdown<string>(true)
{ {
Label = @"Countdown speed", Label = @"Countdown speed",
Items = new[] Items = new[]
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test] [Test]
public void TestLabelledEnumDropdown() public void TestLabelledEnumDropdown()
=> AddStep(@"create dropdown", () => Child = new LabelledEnumDropdown<BeatmapOnlineStatus> => AddStep(@"create dropdown", () => Child = new LabelledEnumDropdown<BeatmapOnlineStatus>(true)
{ {
Label = @"Beatmap status", Label = @"Beatmap status",
Description = @"This is a description" Description = @"This is a description"

View File

@@ -116,12 +116,13 @@ namespace osu.Game.Tournament.Screens.Setup
Failing = api.IsLoggedIn != true, Failing = api.IsLoggedIn != true,
Description = "In order to access the API and display metadata, signing in is required." Description = "In order to access the API and display metadata, signing in is required."
}, },
new LabelledDropdown<RulesetInfo?> new LabelledDropdown<RulesetInfo?>(padded: true)
{ {
Label = "Ruleset", Label = "Ruleset",
Description = "Decides what stats are displayed and which ranks are retrieved for players. This requires a restart to reload data for an existing bracket.", Description = "Decides what stats are displayed and which ranks are retrieved for players. This requires a restart to reload data for an existing bracket.",
Items = rulesets.AvailableRulesets, Items = rulesets.AvailableRulesets,
Current = LadderInfo.Ruleset, Current = LadderInfo.Ruleset,
DropdownWidth = 0.5f,
}, },
new TournamentSwitcher new TournamentSwitcher
{ {

View File

@@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@@ -72,7 +71,9 @@ namespace osu.Game.Collections
var createdItem = flow.Children.SingleOrDefault(item => item.Model.Value.ID == lastCreated); var createdItem = flow.Children.SingleOrDefault(item => item.Model.Value.ID == lastCreated);
if (createdItem != null) if (createdItem != null)
scroll.ScrollTo(createdItem); {
ScheduleAfterChildren(() => scroll.ScrollIntoView(createdItem));
}
lastCreated = null; lastCreated = null;
} }
@@ -104,13 +105,8 @@ namespace osu.Game.Collections
} }
} }
protected override OsuRearrangeableListItem<Live<BeatmapCollection>> CreateOsuDrawable(Live<BeatmapCollection> item) protected override OsuRearrangeableListItem<Live<BeatmapCollection>> CreateOsuDrawable(Live<BeatmapCollection> item) =>
{ new DrawableCollectionListItem(item, true);
if (item.ID == scroll.PlaceholderItem.Model.ID)
return scroll.ReplacePlaceholder();
return new DrawableCollectionListItem(item, true);
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
@@ -122,91 +118,22 @@ namespace osu.Game.Collections
/// The scroll container for this <see cref="DrawableCollectionList"/>. /// The scroll container for this <see cref="DrawableCollectionList"/>.
/// Contains the main flow of <see cref="DrawableCollectionListItem"/> and attaches a placeholder item to the end of the list. /// Contains the main flow of <see cref="DrawableCollectionListItem"/> and attaches a placeholder item to the end of the list.
/// </summary> /// </summary>
/// <remarks>
/// Use <see cref="ReplacePlaceholder"/> to transfer the placeholder into the main list.
/// </remarks>
private partial class Scroll : OsuScrollContainer private partial class Scroll : OsuScrollContainer
{ {
/// <summary>
/// The currently-displayed placeholder item.
/// </summary>
public DrawableCollectionListItem PlaceholderItem { get; private set; } = null!;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private readonly Container content; private readonly FillFlowContainer content;
private readonly Container<DrawableCollectionListItem> placeholderContainer;
public Scroll() public Scroll()
{ {
ScrollbarOverlapsContent = false; ScrollbarOverlapsContent = false;
base.Content.Add(new FillFlowContainer base.Content.Add(content = new FillFlowContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
LayoutDuration = 200, LayoutDuration = 200,
LayoutEasing = Easing.OutQuint, LayoutEasing = Easing.OutQuint,
Children = new Drawable[]
{
content = new Container { RelativeSizeAxes = Axes.X },
placeholderContainer = new Container<DrawableCollectionListItem>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}
}
}); });
ReplacePlaceholder();
Debug.Assert(PlaceholderItem != null);
}
protected override void Update()
{
base.Update();
// AutoSizeAxes cannot be used as the height should represent the post-layout-transform height at all times, so that the placeholder doesn't bounce around.
content.Height = ((Flow)Child).Children.Sum(c => c.IsPresent ? c.DrawHeight + 5 : 0);
}
/// <summary>
/// Replaces the current <see cref="PlaceholderItem"/> with a new one, and returns the previous.
/// </summary>
/// <returns>The current <see cref="PlaceholderItem"/>.</returns>
public DrawableCollectionListItem ReplacePlaceholder()
{
var previous = PlaceholderItem;
placeholderContainer.Clear(false);
placeholderContainer.Add(PlaceholderItem = new NewCollectionEntryItem());
return previous;
}
}
private partial class NewCollectionEntryItem : DrawableCollectionListItem
{
[Resolved]
private RealmAccess realm { get; set; } = null!;
public NewCollectionEntryItem()
: base(new BeatmapCollection().ToLiveUnmanaged(), false)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
TextBox.OnCommit += (sender, newText) =>
{
if (string.IsNullOrEmpty(TextBox.Text))
return;
realm.Write(r => r.Add(new BeatmapCollection(TextBox.Text)));
TextBox.Text = string.Empty;
};
} }
} }

View File

@@ -53,7 +53,11 @@ namespace osu.Game.Collections
ShowDragHandle.Value = false; ShowDragHandle.Value = false;
Masking = true; Masking = true;
CornerRadius = item_height / 2;
// This doesn't match the latest design spec (should be 5) but is an in-between that feels right to the eye
// until we move everything over to Form controls.
CornerRadius = 10;
CornerExponent = 2.5f;
} }
protected override Drawable CreateContent() => content = new ItemContent(Model); protected override Drawable CreateContent() => content = new ItemContent(Model);
@@ -135,7 +139,8 @@ namespace osu.Game.Collections
{ {
this.collection = collection; this.collection = collection;
CornerRadius = item_height / 2; CornerRadius = 10;
CornerExponent = 2.5f;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Database;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@@ -102,12 +103,13 @@ namespace osu.Game.Collections
new Container new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = colours.GreySeaFoamDarker Colour = colours.GreySeaFoamDarker,
}, },
new Container new Container
{ {
@@ -115,23 +117,30 @@ namespace osu.Game.Collections
Padding = new MarginPadding(10), Padding = new MarginPadding(10),
Children = new Drawable[] Children = new Drawable[]
{ {
list = new DrawableCollectionList
{
Padding = new MarginPadding { Vertical = 50 },
RelativeSizeAxes = Axes.Both,
},
searchTextBox = new BasicSearchTextBox searchTextBox = new BasicSearchTextBox
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Y = 10,
Height = 40, Height = 40,
ReleaseFocusOnCommit = false, ReleaseFocusOnCommit = false,
HoldFocus = true, HoldFocus = true,
PlaceholderText = HomeStrings.SearchPlaceholder, PlaceholderText = HomeStrings.SearchPlaceholder,
}, },
list = new DrawableCollectionList new Container
{ {
Padding = new MarginPadding RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Children = new Drawable[]
{ {
Top = 60, new NewCollectionEntryItem()
}, }
RelativeSizeAxes = Axes.Both, },
}
} }
}, },
} }
@@ -184,5 +193,30 @@ namespace osu.Game.Collections
// Ensure that textboxes commit // Ensure that textboxes commit
GetContainingFocusManager()?.TriggerFocusContention(this); GetContainingFocusManager()?.TriggerFocusContention(this);
} }
private partial class NewCollectionEntryItem : DrawableCollectionListItem
{
[Resolved]
private RealmAccess realm { get; set; } = null!;
public NewCollectionEntryItem()
: base(new BeatmapCollection().ToLiveUnmanaged(), false)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
TextBox.OnCommit += (_, _) =>
{
if (string.IsNullOrEmpty(TextBox.Text))
return;
realm.Write(r => r.Add(new BeatmapCollection(TextBox.Text)));
TextBox.Text = string.Empty;
};
}
}
} }
} }

View File

@@ -9,8 +9,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
{ {
public partial class LabelledDropdown<TItem> : LabelledComponent<OsuDropdown<TItem>, TItem> public partial class LabelledDropdown<TItem> : LabelledComponent<OsuDropdown<TItem>, TItem>
{ {
public LabelledDropdown() public LabelledDropdown(bool padded)
: base(true) : base(padded)
{ {
} }
@@ -20,12 +20,22 @@ namespace osu.Game.Graphics.UserInterfaceV2
set => Component.Items = value; set => Component.Items = value;
} }
public float DropdownWidth
{
get => Component.Width;
set => Component.Width = value;
}
protected sealed override OsuDropdown<TItem> CreateComponent() => CreateDropdown().With(d => protected sealed override OsuDropdown<TItem> CreateComponent() => CreateDropdown().With(d =>
{ {
d.RelativeSizeAxes = Axes.X; d.RelativeSizeAxes = Axes.X;
d.Width = 0.5f;
}); });
protected virtual OsuDropdown<TItem> CreateDropdown() => new OsuDropdown<TItem>(); protected virtual OsuDropdown<TItem> CreateDropdown() => new Dropdown();
private partial class Dropdown : OsuDropdown<TItem>
{
protected override DropdownMenu CreateMenu() => base.CreateMenu().With(menu => menu.MaxHeight = 200);
}
} }
} }

View File

@@ -9,6 +9,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
public partial class LabelledEnumDropdown<TEnum> : LabelledDropdown<TEnum> public partial class LabelledEnumDropdown<TEnum> : LabelledDropdown<TEnum>
where TEnum : struct, Enum where TEnum : struct, Enum
{ {
public LabelledEnumDropdown(bool padded)
: base(padded)
{
}
protected override OsuDropdown<TEnum> CreateDropdown() => new OsuEnumDropdown<TEnum>(); protected override OsuDropdown<TEnum> CreateDropdown() => new OsuEnumDropdown<TEnum>();
} }
} }

View File

@@ -63,7 +63,6 @@ using osu.Game.Resources;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Utils; using osu.Game.Utils;
using RuntimeInfo = osu.Framework.RuntimeInfo; using RuntimeInfo = osu.Framework.RuntimeInfo;

View File

@@ -32,7 +32,7 @@ namespace osu.Game.Overlays
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Font = OsuFont.Numeric.With(weight: FontWeight.Bold, size: 12), Font = OsuFont.Numeric.With(weight: FontWeight.Bold, size: 12),
Colour = colours.YellowDark, Colour = colours.YellowDark,
Text = @"DEVELOPER BUILD For Ez2Lazer", Text = @"DEVELOPER BUILD",
}, },
new Sprite new Sprite
{ {

View File

@@ -35,7 +35,6 @@ using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.LAsEzExtensions.Configuration;
using osu.Game.LAsEzExtensions.Screens; using osu.Game.LAsEzExtensions.Screens;
using osu.Game.Utils; using osu.Game.Utils;

View File

@@ -129,8 +129,11 @@ namespace osu.Game.Rulesets.Edit
// Inherit the bank from the previous hit object // Inherit the bank from the previous hit object
HitObject.Samples = HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastHitNormal.Bank) : s).ToList(); HitObject.Samples = HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastHitNormal.Bank) : s).ToList();
// Inherit the volume from the previous hit object // Inherit the volume and sample set info from the previous hit object
HitObject.Samples = HitObject.Samples.Select(s => s.With(newVolume: lastHitNormal.Volume)).ToList(); HitObject.Samples = HitObject.Samples.Select(s => s.With(
newVolume: lastHitNormal.Volume,
newSuffix: lastHitNormal.Suffix,
newUseBeatmapSamples: lastHitNormal.UseBeatmapSamples)).ToList();
} }
if (HitObject is IHasRepeats hasRepeats) if (HitObject is IHasRepeats hasRepeats)

View File

@@ -49,7 +49,8 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
public Drawable Icon { get; private set; } = null!; public Drawable Icon { get; private set; } = null!;
public DrawableTernaryButton() public DrawableTernaryButton(HoverSampleSet? hoverSampleSet = HoverSampleSet.Button)
: base(hoverSampleSet)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
} }

View File

@@ -0,0 +1,58 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Edit.Components.TernaryButtons
{
public partial class SampleSetTernaryButton : DrawableTernaryButton
{
public EditorBeatmapSkin.SampleSet SampleSet { get; }
public SampleSetTernaryButton(EditorBeatmapSkin.SampleSet sampleSet)
: base(null)
{
SampleSet = sampleSet;
CreateIcon = () => sampleSet.SampleSetIndex == 0
? new SpriteIcon { Icon = OsuIcon.SkinA }
: new Container
{
Child = new OsuSpriteText
{
Text = sampleSet.SampleSetIndex.ToString(),
Font = OsuFont.Style.Body.With(weight: FontWeight.Bold),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
switch (sampleSet.SampleSetIndex)
{
case 0:
RelativeSizeAxes = Axes.X;
Width = 1;
break;
default:
RelativeSizeAxes = Axes.None;
Width = Height;
break;
}
}
[BackgroundDependencyLoader]
private void load()
{
AddRangeInternal(new Drawable[]
{
new HoverSounds(HoverSampleSet.Button),
});
}
}
}

View File

@@ -36,13 +36,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
private double? lastSeekTime; private double? lastSeekTime;
protected override bool OnDragStart(DragStartEvent e) protected override bool OnDragStart(DragStartEvent e) => true;
{
var localPos = ToLocalSpace(e.ScreenSpaceMousePosition);
if (localPos.Y <= DrawHeight / 2) return false;
return true;
}
protected override void OnDrag(DragEvent e) protected override void OnDrag(DragEvent e)
{ {
@@ -58,9 +52,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
protected override bool OnMouseDown(MouseDownEvent e) protected override bool OnMouseDown(MouseDownEvent e)
{ {
var localPos = ToLocalSpace(e.ScreenSpaceMousePosition);
if (localPos.Y <= DrawHeight / 2) return false; // only lower half
seekToPosition(e.ScreenSpaceMousePosition, instant: true); seekToPosition(e.ScreenSpaceMousePosition, instant: true);
return true; return true;
} }

View File

@@ -20,10 +20,11 @@ using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Timing;
using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
@@ -127,7 +128,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private void updateText() private void updateText()
{ {
Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}"; Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))}{GetSuffix(GetSamples())} {GetVolumeValue(GetSamples())}";
if (!contracted.Value) if (!contracted.Value)
LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint); LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint);
@@ -149,6 +150,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
return samples.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL)?.Bank; return samples.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL)?.Bank;
} }
public static string GetSuffix(IEnumerable<HitSampleInfo> samples)
{
var suffixes = samples.Select(o => o.Suffix).Distinct().ToList();
// having multiple values should never happen, but just for safety...
if (suffixes.Count != 1 || suffixes.Single() is not string commonSuffix)
return string.Empty;
return $@":{commonSuffix}";
}
public static string? GetAdditionBankValue(IEnumerable<HitSampleInfo> samples) public static string? GetAdditionBankValue(IEnumerable<HitSampleInfo> samples)
{ {
var firstAddition = samples.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL); var firstAddition = samples.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL);
@@ -176,9 +188,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
private readonly HitObject hitObject; private readonly HitObject hitObject;
private LabelledTextBox bank = null!; private LabelledDropdown<string> bank = null!;
private LabelledTextBox additionBank = null!; private LabelledDropdown<string> additionBank = null!;
private FillFlowContainer<SampleSetTernaryButton>? sampleSetsFlow;
private LabelledDropdown<EditorBeatmapSkin.SampleSet>? sampleSetDropdown;
private IndeterminateSliderWithTextBoxInput<int> volume = null!; private IndeterminateSliderWithTextBoxInput<int> volume = null!;
private SkinnableSound demoSample = null!;
private FillFlowContainer togglesCollection = null!; private FillFlowContainer togglesCollection = null!;
@@ -229,11 +244,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
flow = new FillFlowContainer flow = new FillFlowContainer
{ {
Width = 200, Width = 220,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 10), Spacing = new Vector2(0, 10),
Children = new Drawable[] Children = new[]
{ {
togglesCollection = new FillFlowContainer togglesCollection = new FillFlowContainer
{ {
@@ -242,27 +257,30 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Spacing = new Vector2(5, 5), Spacing = new Vector2(5, 5),
}, },
bank = new LabelledTextBox bank = new LabelledDropdown<string>(padded: false)
{ {
Label = "Bank Name", Label = "Normal Bank",
SelectAllOnFocus = true, Items = HitSampleInfo.ALL_BANKS,
}, },
additionBank = new LabelledTextBox additionBank = new LabelledDropdown<string>(padded: false)
{ {
Label = "Addition Bank", Label = "Addition Bank",
SelectAllOnFocus = true, Items = HitSampleInfo.ALL_BANKS,
}, },
createSampleSetContent(),
volume = new IndeterminateSliderWithTextBoxInput<int>("Volume", new BindableInt(100) volume = new IndeterminateSliderWithTextBoxInput<int>("Volume", new BindableInt(100)
{ {
MinValue = DrawableHitObject.MINIMUM_SAMPLE_VOLUME, MinValue = DrawableHitObject.MINIMUM_SAMPLE_VOLUME,
MaxValue = 100, MaxValue = 100,
}) })
} }
},
new EditorSkinProvidingContainer(beatmap)
{
Child = demoSample = new SkinnableSound()
} }
}; };
bank.TabbableContentContainer = flow;
additionBank.TabbableContentContainer = flow;
volume.TabbableContentContainer = flow; volume.TabbableContentContainer = flow;
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
@@ -283,10 +301,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
setBank(val.NewValue); setBank(val.NewValue);
updatePrimaryBankState(); updatePrimaryBankState();
playDemoSample();
}); });
// on commit, ensure that the value is correct by sourcing it from the objects' samples again.
// this ensures that committing empty text causes a revert to the previous value.
bank.OnCommit += (_, _) => updatePrimaryBankState();
updateAdditionBankState(); updateAdditionBankState();
additionBank.Current.BindValueChanged(val => additionBank.Current.BindValueChanged(val =>
@@ -296,8 +312,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
setAdditionBank(val.NewValue); setAdditionBank(val.NewValue);
updateAdditionBankState(); updateAdditionBankState();
playDemoSample();
}); });
additionBank.OnCommit += (_, _) => updateAdditionBankState();
updateSampleSetState();
volume.Current.BindValueChanged(val => volume.Current.BindValueChanged(val =>
{ {
@@ -310,6 +328,58 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
togglesCollection.AddRange(createTernaryButtons()); togglesCollection.AddRange(createTernaryButtons());
} }
private Drawable createSampleSetContent()
{
if (beatmap.BeatmapSkin == null)
return Empty();
var sampleSets = beatmap.BeatmapSkin.GetAvailableSampleSets().ToList();
if (sampleSets.Count == 0)
return Empty();
sampleSets.Insert(0, new EditorBeatmapSkin.SampleSet(0, "User skin"));
if (sampleSets.Count < 20)
{
sampleSetsFlow = new FillFlowContainer<SampleSetTernaryButton>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(5),
ChildrenEnumerable = sampleSets.Select(set => new SampleSetTernaryButton(set) { Description = set.Name }),
};
foreach (var ternary in sampleSetsFlow)
{
ternary.Current.BindValueChanged(val =>
{
if (val.NewValue == TernaryState.True)
setSampleSet(ternary.SampleSet);
updateSampleSetState();
playDemoSample();
});
}
return sampleSetsFlow;
}
sampleSetDropdown = new LabelledDropdown<EditorBeatmapSkin.SampleSet>(padded: false)
{
Label = "Sample Set",
Items = sampleSets,
};
sampleSetDropdown.Current.BindValueChanged(val =>
{
setSampleSet(val.NewValue);
updateSampleSetState();
playDemoSample();
});
return sampleSetDropdown;
}
private string? getCommonBank() => allRelevantSamples.Select(h => GetBankValue(h.samples)).Distinct().Count() == 1 private string? getCommonBank() => allRelevantSamples.Select(h => GetBankValue(h.samples)).Distinct().Count() == 1
? GetBankValue(allRelevantSamples.First().samples) ? GetBankValue(allRelevantSamples.First().samples)
: null; : null;
@@ -327,15 +397,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private void updatePrimaryBankState() private void updatePrimaryBankState()
{ {
string? commonBank = getCommonBank(); string? commonBank = getCommonBank();
bank.Current.Value = commonBank; bank.Current.Value = !string.IsNullOrEmpty(commonBank) ? commonBank : "(multiple)";
bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty;
} }
private void updateAdditionBankState() private void updateAdditionBankState()
{ {
string? commonAdditionBank = getCommonAdditionBank(); string? commonAdditionBank = getCommonAdditionBank();
additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty; additionBank.Current.Value = !string.IsNullOrEmpty(commonAdditionBank) ? commonAdditionBank : "(multiple)";
additionBank.Current.Value = commonAdditionBank;
bool anyAdditions = allRelevantSamples.Any(o => o.samples.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); bool anyAdditions = allRelevantSamples.Any(o => o.samples.Any(s => s.Name != HitSampleInfo.HIT_NORMAL));
if (anyAdditions) if (anyAdditions)
@@ -344,6 +412,40 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
additionBank.Hide(); additionBank.Hide();
} }
private void updateSampleSetState()
{
HashSet<int> activeSets = new HashSet<int>();
foreach (var sample in allRelevantSamples.SelectMany(h => h.samples))
{
if (sample.Suffix == null)
activeSets.Add(sample.UseBeatmapSamples ? 1 : 0);
else if (int.TryParse(sample.Suffix, out int suffix))
activeSets.Add(suffix);
}
if (sampleSetsFlow != null)
{
var onState = activeSets.Count > 1 ? TernaryState.Indeterminate : TernaryState.True;
foreach (var ternary in sampleSetsFlow)
ternary.Current.Value = activeSets.Contains(ternary.SampleSet.SampleSetIndex) ? onState : TernaryState.False;
}
if (sampleSetDropdown != null)
{
sampleSetDropdown.Current.Value = activeSets.Count == 1
? sampleSetDropdown.Items.Single(i => i.SampleSetIndex == activeSets.Single())
: new EditorBeatmapSkin.SampleSet(-1, "(multiple)");
}
}
private void playDemoSample() => Scheduler.AddOnce(() =>
{
demoSample.Samples = allRelevantSamples.First().samples.Cast<ISampleInfo>().ToArray();
demoSample.Play();
});
/// <summary> /// <summary>
/// Applies the given update action on all samples of <see cref="allRelevantSamples"/> /// Applies the given update action on all samples of <see cref="allRelevantSamples"/>
/// and invokes the necessary update notifiers for the beatmap and hit objects. /// and invokes the necessary update notifiers for the beatmap and hit objects.
@@ -397,6 +499,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}); });
} }
private void setSampleSet(EditorBeatmapSkin.SampleSet newSampleSet)
{
updateAllRelevantSamples((_, relevantSamples) =>
{
for (int i = 0; i < relevantSamples.Count; i++)
{
relevantSamples[i] = relevantSamples[i].With(
newSuffix: newSampleSet.SampleSetIndex >= 2 ? newSampleSet.SampleSetIndex.ToString() : null,
newUseBeatmapSamples: newSampleSet.SampleSetIndex >= 1);
}
});
}
private void setVolume(int newVolume) private void setVolume(int newVolume)
{ {
updateAllRelevantSamples((_, relevantSamples) => updateAllRelevantSamples((_, relevantSamples) =>
@@ -435,6 +550,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
addHitSample(sampleName); addHitSample(sampleName);
break; break;
} }
playDemoSample();
}; };
selectionSampleStates[sampleName] = bindable; selectionSampleStates[sampleName] = bindable;
@@ -455,7 +572,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
foreach ((string sampleName, var bindable) in selectionSampleStates) foreach ((string sampleName, var bindable) in selectionSampleStates)
{ {
yield return new DrawableTernaryButton yield return new DrawableTernaryButton(null)
{ {
Current = bindable, Current = bindable,
Description = string.Empty, Description = string.Empty,

View File

@@ -3,6 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@@ -61,6 +63,46 @@ namespace osu.Game.Screens.Edit
invokeSkinChanged(); invokeSkinChanged();
} }
public record SampleSet(int SampleSetIndex, string Name)
{
public SampleSet(int sampleSetIndex)
: this(sampleSetIndex, $@"Custom #{sampleSetIndex}")
{
}
public override string ToString() => Name;
}
public IEnumerable<SampleSet> GetAvailableSampleSets()
{
string[] possibleSounds = HitSampleInfo.ALL_ADDITIONS.Prepend(HitSampleInfo.HIT_NORMAL).ToArray();
string[] possibleBanks = HitSampleInfo.ALL_BANKS;
string[] possiblePrefixes = possibleSounds.SelectMany(sound => possibleBanks.Select(bank => $@"{bank}-{sound}")).ToArray();
HashSet<int> indices = new HashSet<int>();
if (Skin.Samples != null)
{
foreach (string sample in Skin.Samples.GetAvailableResources())
{
foreach (string possiblePrefix in possiblePrefixes)
{
if (!sample.StartsWith(possiblePrefix, StringComparison.InvariantCultureIgnoreCase))
continue;
string indexString = Path.GetFileNameWithoutExtension(sample)[possiblePrefix.Length..];
if (string.IsNullOrEmpty(indexString))
indices.Add(1);
if (int.TryParse(indexString, out int index))
indices.Add(index);
}
}
}
return indices.OrderBy(i => i).Select(i => new SampleSet(i));
}
#region Delegated ISkin implementation #region Delegated ISkin implementation
public Drawable? GetDrawableComponent(ISkinComponentLookup lookup) => Skin.GetDrawableComponent(lookup); public Drawable? GetDrawableComponent(ISkinComponentLookup lookup) => Skin.GetDrawableComponent(lookup);

View File

@@ -38,7 +38,7 @@ namespace osu.Game.Skinning
/// <summary> /// <summary>
/// A sample store which can be used to perform user file lookups for this skin. /// A sample store which can be used to perform user file lookups for this skin.
/// </summary> /// </summary>
protected ISampleStore? Samples { get; } protected internal ISampleStore? Samples { get; }
public readonly Live<SkinInfo> SkinInfo; public readonly Live<SkinInfo> SkinInfo;