mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-13 11:20:28 +00:00
同步更新
This commit is contained in:
@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
|
||||
@@ -32,7 +33,7 @@ namespace osu.Desktop.Security
|
||||
{
|
||||
public ElevatedPrivilegesNotification()
|
||||
{
|
||||
Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance, may break integrations and poses a security risk. Please run the game as a normal user.";
|
||||
Text = NotificationsStrings.ElevatedPrivileges(RuntimeInfo.IsUnix ? "root" : "Administrator");
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
@@ -11,7 +11,6 @@ using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.LAsEzExtensions.Audio;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
@@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class PolygonGenerationPopover : OsuPopover
|
||||
{
|
||||
private SliderWithTextBoxInput<double> distanceSnapInput = null!;
|
||||
private SliderWithTextBoxInput<int> offsetAngleInput = null!;
|
||||
private SliderWithTextBoxInput<int> repeatCountInput = null!;
|
||||
private SliderWithTextBoxInput<int> pointInput = null!;
|
||||
private FormSliderBar<double> distanceSnapInput { get; set; } = null!;
|
||||
private FormSliderBar<int> offsetAngleInput { get; set; } = null!;
|
||||
private FormSliderBar<int> repeatCountInput { get; set; } = null!;
|
||||
private FormSliderBar<int> pointInput { get; set; } = null!;
|
||||
private RoundedButton commitButton = null!;
|
||||
|
||||
private readonly List<HitCircle> insertedCircles = new List<HitCircle>();
|
||||
@@ -64,11 +64,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
Width = 220,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
distanceSnapInput = new SliderWithTextBoxInput<double>("Distance snap:")
|
||||
distanceSnapInput = new FormSliderBar<double>
|
||||
{
|
||||
Caption = "Distance snap",
|
||||
Current = new BindableNumber<double>(1)
|
||||
{
|
||||
MinValue = 0.1,
|
||||
@@ -76,37 +77,40 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
Precision = 0.1,
|
||||
Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value,
|
||||
},
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
offsetAngleInput = new SliderWithTextBoxInput<int>("Offset angle:")
|
||||
offsetAngleInput = new FormSliderBar<int>
|
||||
{
|
||||
Caption = "Offset angle",
|
||||
Current = new BindableNumber<int>
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 180,
|
||||
Precision = 1
|
||||
},
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
repeatCountInput = new SliderWithTextBoxInput<int>("Repeats:")
|
||||
repeatCountInput = new FormSliderBar<int>
|
||||
{
|
||||
Caption = "Repeats",
|
||||
Current = new BindableNumber<int>(1)
|
||||
{
|
||||
MinValue = 1,
|
||||
MaxValue = 10,
|
||||
Precision = 1
|
||||
},
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
pointInput = new SliderWithTextBoxInput<int>("Vertices:")
|
||||
pointInput = new FormSliderBar<int>
|
||||
{
|
||||
Caption = "Vertices",
|
||||
Current = new BindableNumber<int>(3)
|
||||
{
|
||||
MinValue = 3,
|
||||
MaxValue = 32,
|
||||
Precision = 1,
|
||||
},
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
commitButton = new RoundedButton
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
private BindableNumber<float> xBindable = null!;
|
||||
private BindableNumber<float> yBindable = null!;
|
||||
|
||||
private SliderWithTextBoxInput<float> xInput = null!;
|
||||
private FormSliderBar<float> xInput { get; set; } = null!;
|
||||
private OsuCheckbox relativeCheckbox = null!;
|
||||
|
||||
public PreciseMovementPopover()
|
||||
@@ -52,31 +52,31 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
Width = 220,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
xInput = new SliderWithTextBoxInput<float>("X:")
|
||||
xInput = new FormSliderBar<float>
|
||||
{
|
||||
Caption = "X",
|
||||
Current = xBindable = new BindableNumber<float>
|
||||
{
|
||||
Precision = 1,
|
||||
},
|
||||
Instantaneous = true,
|
||||
TabbableContentContainer = this,
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
new SliderWithTextBoxInput<float>("Y:")
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Y",
|
||||
Current = yBindable = new BindableNumber<float>
|
||||
{
|
||||
Precision = 1,
|
||||
},
|
||||
Instantaneous = true,
|
||||
TabbableContentContainer = this,
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
relativeCheckbox = new OsuCheckbox(false)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
LabelText = "Relative movement",
|
||||
LabelText = "Relative movement"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private readonly Bindable<PreciseRotationInfo> rotationInfo = new Bindable<PreciseRotationInfo>(new PreciseRotationInfo(0, EditorOrigin.GridCentre));
|
||||
|
||||
private SliderWithTextBoxInput<float> angleInput = null!;
|
||||
private FormSliderBar<float> angleInput { get; set; } = null!;
|
||||
private EditorRadioButtonCollection rotationOrigin = null!;
|
||||
|
||||
private RadioButton gridCentreButton = null!;
|
||||
@@ -54,11 +54,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
Width = 220,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
angleInput = new SliderWithTextBoxInput<float>("Angle (degrees):")
|
||||
angleInput = new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Angle (degrees)",
|
||||
Current = new BindableNumber<float>
|
||||
{
|
||||
MinValue = -360,
|
||||
@@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
Precision = 1
|
||||
},
|
||||
KeyboardStep = 1f,
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
rotationOrigin = new EditorRadioButtonCollection
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, EditorOrigin.GridCentre, true, true));
|
||||
|
||||
private SliderWithTextBoxInput<float> scaleInput = null!;
|
||||
private FormSliderBar<float> scaleInput { get; set; } = null!;
|
||||
private BindableNumber<float> scaleInputBindable = null!;
|
||||
private EditorRadioButtonCollection scaleOrigin = null!;
|
||||
|
||||
@@ -66,11 +66,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
Width = 220,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
scaleInput = new SliderWithTextBoxInput<float>("Scale:")
|
||||
scaleInput = new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Scale",
|
||||
Current = scaleInputBindable = new BindableNumber<float>
|
||||
{
|
||||
MinValue = 0.05f,
|
||||
@@ -80,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
Default = 1,
|
||||
},
|
||||
KeyboardStep = 0.01f,
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
scaleOrigin = new EditorRadioButtonCollection
|
||||
{
|
||||
|
||||
@@ -287,9 +287,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
if (IsHovered)
|
||||
{
|
||||
// 记录延迟追踪按键输入
|
||||
osu.Game.LAsEzExtensions.Audio.InputAudioLatencyTracker.Instance?.RecordKeyPress(e.Action == OsuAction.LeftButton ? osuTK.Input.Key.Z : osuTK.Input.Key.X);
|
||||
|
||||
Hit();
|
||||
HitAction ??= e.Action;
|
||||
return true;
|
||||
|
||||
@@ -10,8 +10,6 @@ using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.LAsEzExtensions.Audio;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
@@ -96,31 +94,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
}
|
||||
}
|
||||
|
||||
// Record input for latency tracking (map TaikoAction to a representative Key)
|
||||
try
|
||||
{
|
||||
Key mapped = Key.Unknown;
|
||||
switch (e.Action)
|
||||
{
|
||||
case TaikoAction.LeftCentre:
|
||||
mapped = Key.D;
|
||||
break;
|
||||
case TaikoAction.RightCentre:
|
||||
mapped = Key.J;
|
||||
break;
|
||||
case TaikoAction.LeftRim:
|
||||
mapped = Key.F;
|
||||
break;
|
||||
case TaikoAction.RightRim:
|
||||
mapped = Key.K;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mapped != Key.Unknown)
|
||||
InputAudioLatencyTracker.Instance?.RecordKeyPress(mapped);
|
||||
}
|
||||
catch { }
|
||||
|
||||
Play(triggerSource, hitType, strong);
|
||||
|
||||
lastHitTime = Time.Current;
|
||||
|
||||
139
osu.Game.Tests/Visual/UserInterface/TestSceneFormButton.cs
Normal file
139
osu.Game.Tests/Visual/UserInterface/TestSceneFormButton.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
// 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.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneFormButton : ThemeComparisonTestScene
|
||||
{
|
||||
public TestSceneFormButton()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent() => new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BackgroundBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Width = 400,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5),
|
||||
Padding = new MarginPadding(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with default style",
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with default style",
|
||||
Enabled = { Value = false },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with custom style",
|
||||
BackgroundColour = new OsuColour().DangerousButtonColour,
|
||||
ButtonIcon = FontAwesome.Solid.Hamburger,
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with custom style",
|
||||
BackgroundColour = new OsuColour().DangerousButtonColour,
|
||||
ButtonIcon = FontAwesome.Solid.Hamburger,
|
||||
Enabled = { Value = false },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
|
||||
BackgroundColour = new OsuColour().Blue3,
|
||||
ButtonIcon = FontAwesome.Solid.Book,
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with text inside",
|
||||
ButtonText = "Text in button",
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with text inside",
|
||||
ButtonText = "Text in button",
|
||||
Enabled = { Value = false },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with text inside",
|
||||
ButtonText = "Text in button",
|
||||
BackgroundColour = new OsuColour().DangerousButtonColour,
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with text inside",
|
||||
ButtonText = "Text in button",
|
||||
BackgroundColour = new OsuColour().DangerousButtonColour,
|
||||
Enabled = { Value = false },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor",
|
||||
ButtonText = "Text in button",
|
||||
BackgroundColour = new OsuColour().Blue3,
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor",
|
||||
ButtonText = "Text in button",
|
||||
BackgroundColour = new OsuColour().Blue3,
|
||||
Enabled = { Value = false },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private partial class BackgroundBox : Box
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Colour = colourProvider.Background4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,9 +64,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNubDoubleClickRevertToDefault()
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestNubDoubleClickRevertToDefault(bool transferValueOnCommit)
|
||||
{
|
||||
OsuSpriteText text;
|
||||
FormSliderBar<float> slider = null!;
|
||||
|
||||
AddStep("create content", () =>
|
||||
@@ -81,9 +83,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
text = new OsuSpriteText(),
|
||||
slider = new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider",
|
||||
TransferValueOnCommit = transferValueOnCommit,
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
@@ -94,6 +98,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
},
|
||||
}
|
||||
};
|
||||
slider.Current.BindValueChanged(_ => text.Text = $"Current value is: {slider.Current.Value}", true);
|
||||
});
|
||||
AddStep("set slider to 1", () => slider.Current.Value = 1);
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private partial class EmptyToast : Toast
|
||||
{
|
||||
public EmptyToast()
|
||||
: base("", "", "")
|
||||
: base("", "")
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -104,8 +104,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private partial class LengthyToast : Toast
|
||||
{
|
||||
public LengthyToast()
|
||||
: base("Toast with a very very very long text", "A very very very very very very long text also", "A very very very very very long shortcut")
|
||||
: base("Toast with a very very very long text", "A very very very very very very long text also")
|
||||
{
|
||||
ExtraText = "A very very very very very long shortcut";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneSliderWithTextBoxInput : OsuManualInputManagerTestScene
|
||||
{
|
||||
private SliderWithTextBoxInput<float> sliderWithTextBoxInput = null!;
|
||||
|
||||
private OsuSliderBar<float> slider => sliderWithTextBoxInput.ChildrenOfType<OsuSliderBar<float>>().Single();
|
||||
private Nub nub => sliderWithTextBoxInput.ChildrenOfType<Nub>().Single();
|
||||
private OsuTextBox textBox => sliderWithTextBoxInput.ChildrenOfType<OsuTextBox>().Single();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create slider", () => Child = sliderWithTextBoxInput = new SliderWithTextBoxInput<float>("Test Slider")
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = -5,
|
||||
MaxValue = 5,
|
||||
Precision = 0.2f
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonInstantaneousMode()
|
||||
{
|
||||
AddStep("set instantaneous to false", () => sliderWithTextBoxInput.Instantaneous = false);
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("change text", () => textBox.Text = "3");
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.Zero);
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.Zero);
|
||||
|
||||
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3));
|
||||
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("move mouse to nub", () => InputManager.MoveMouseTo(nub));
|
||||
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddStep("move mouse to minimum", () => InputManager.MoveMouseTo(sliderWithTextBoxInput.ScreenSpaceDrawQuad.BottomLeft));
|
||||
AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("3"));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
||||
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("lose focus", () => ((IFocusManager)InputManager).ChangeFocus(null));
|
||||
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInstantaneousMode()
|
||||
{
|
||||
AddStep("set instantaneous to true", () => sliderWithTextBoxInput.Instantaneous = true);
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("change text", () => textBox.Text = "3");
|
||||
AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3));
|
||||
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(3));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("move mouse to nub", () => InputManager.MoveMouseTo(nub));
|
||||
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddStep("move mouse to minimum", () => InputManager.MoveMouseTo(sliderWithTextBoxInput.ScreenSpaceDrawQuad.BottomLeft));
|
||||
AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
||||
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("lose focus", () => ((IFocusManager)InputManager).ChangeFocus(null));
|
||||
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ namespace osu.Game.Beatmaps
|
||||
protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> storage, WorkingBeatmap? defaultBeatmap,
|
||||
GameHost? host)
|
||||
{
|
||||
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
|
||||
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host, Realm);
|
||||
}
|
||||
|
||||
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm) => new BeatmapImporter(storage, realm);
|
||||
|
||||
@@ -47,12 +47,13 @@ namespace osu.Game.Beatmaps
|
||||
private readonly LargeTextureStore beatmapPanelTextureStore;
|
||||
private readonly ITrackStore trackStore;
|
||||
private readonly IResourceStore<byte[]> files;
|
||||
private readonly RealmAccess realm;
|
||||
|
||||
[CanBeNull]
|
||||
private readonly GameHost host;
|
||||
|
||||
public WorkingBeatmapCache(ITrackStore trackStore, AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> files, WorkingBeatmap defaultBeatmap = null,
|
||||
GameHost host = null)
|
||||
GameHost host = null, RealmAccess realm = null)
|
||||
{
|
||||
DefaultBeatmap = defaultBeatmap;
|
||||
|
||||
@@ -63,6 +64,7 @@ namespace osu.Game.Beatmaps
|
||||
largeTextureStore = new LargeTextureStore(host?.Renderer ?? new DummyRenderer(), host?.CreateTextureLoaderStore(files));
|
||||
beatmapPanelTextureStore = new LargeTextureStore(host?.Renderer ?? new DummyRenderer(), new BeatmapPanelBackgroundTextureLoaderStore(host?.CreateTextureLoaderStore(files)));
|
||||
this.trackStore = trackStore;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public void Invalidate(BeatmapSetInfo info)
|
||||
@@ -118,7 +120,7 @@ namespace osu.Game.Beatmaps
|
||||
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
|
||||
IRenderer IStorageResourceProvider.Renderer => host?.Renderer ?? new DummyRenderer();
|
||||
AudioManager IStorageResourceProvider.AudioManager => audioManager;
|
||||
RealmAccess IStorageResourceProvider.RealmAccess => null!;
|
||||
RealmAccess IStorageResourceProvider.RealmAccess => realm;
|
||||
IResourceStore<byte[]> IStorageResourceProvider.Files => files;
|
||||
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
|
||||
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore);
|
||||
|
||||
@@ -18,10 +18,10 @@ namespace osu.Game.Configuration
|
||||
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.BeatmapWithStoryboard))]
|
||||
BeatmapWithStoryboard,
|
||||
|
||||
[Description("'EzResource/Webm/*.webm' from local folders")]
|
||||
[Description("'EzResource/Webm/*' from osu data folder ")]
|
||||
WebmSource,
|
||||
|
||||
[Description("'EzResource/BG/*.png' from local folders")]
|
||||
[Description("'EzResource/BG/*' from local folders")]
|
||||
Slides,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@@ -198,7 +199,7 @@ namespace osu.Game.Database
|
||||
ProgressNotification notification = new ProgressNotification
|
||||
{
|
||||
State = ProgressNotificationState.Active,
|
||||
Text = $"Exporting {itemFilename}...",
|
||||
Text = NotificationsStrings.FileExportOngoing(itemFilename),
|
||||
};
|
||||
|
||||
PostNotification?.Invoke(notification);
|
||||
@@ -225,7 +226,7 @@ namespace osu.Game.Database
|
||||
throw;
|
||||
}
|
||||
|
||||
notification.CompletionText = $"Exported {itemFilename}! Click to view.";
|
||||
notification.CompletionText = NotificationsStrings.FileExportFinished(itemFilename);
|
||||
notification.CompletionClickAction = () => ExportStorage.PresentFileExternally(filename);
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Utils;
|
||||
using Realms;
|
||||
@@ -83,7 +84,7 @@ namespace osu.Game.Database
|
||||
ProgressNotification notification = new ProgressNotification
|
||||
{
|
||||
State = ProgressNotificationState.Active,
|
||||
Text = $"Exporting {itemFilename}...",
|
||||
Text = NotificationsStrings.FileExportOngoing(itemFilename),
|
||||
};
|
||||
|
||||
PostNotification?.Invoke(notification);
|
||||
@@ -106,7 +107,7 @@ namespace osu.Game.Database
|
||||
throw;
|
||||
}
|
||||
|
||||
notification.CompletionText = $"Exported {itemFilename}! Click to view.";
|
||||
notification.CompletionText = NotificationsStrings.FileExportFinished(itemFilename);
|
||||
notification.CompletionClickAction = () => ExportStorage.PresentFileExternally(filename);
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
@@ -169,7 +170,7 @@ namespace osu.Game.Graphics
|
||||
|
||||
notificationOverlay.Post(new SimpleNotification
|
||||
{
|
||||
Text = $"Screenshot {filename} saved!",
|
||||
Text = NotificationsStrings.ScreenshotSaved(filename),
|
||||
Activated = () =>
|
||||
{
|
||||
storage.PresentFileExternally(filename);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
@@ -12,6 +13,7 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@@ -28,62 +30,133 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
public LocalisableString Caption { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets text inside the button.
|
||||
/// </summary>
|
||||
public LocalisableString ButtonText { get; init; }
|
||||
|
||||
public Action? Action { get; init; }
|
||||
/// <summary>
|
||||
/// Sets a custom button icon. Not shown when <see cref="ButtonText"/> is set.
|
||||
/// </summary>
|
||||
public IconUsage ButtonIcon { get; init; } = FontAwesome.Solid.ChevronRight;
|
||||
|
||||
private readonly Color4? backgroundColour;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom background colour for the button.
|
||||
/// </summary>
|
||||
public Color4? BackgroundColour
|
||||
{
|
||||
get => backgroundColour;
|
||||
init
|
||||
{
|
||||
backgroundColour = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The action to invoke when the button is clicked.
|
||||
/// </summary>
|
||||
public Action? Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the button is enabled.
|
||||
/// </summary>
|
||||
public readonly BindableBool Enabled = new BindableBool(true);
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
private Container content = null!;
|
||||
private Box background = null!;
|
||||
private OsuTextFlowContainer text = null!;
|
||||
private Button button = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 50;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
CornerExponent = 2.5f;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChild = content = new Container
|
||||
{
|
||||
new Box
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
CornerExponent = 2.5f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
background = new Box
|
||||
{
|
||||
Left = 9,
|
||||
Right = 5,
|
||||
Vertical = 5,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
new TrianglesV2
|
||||
{
|
||||
new OsuTextFlowContainer
|
||||
SpawnRatio = 0.5f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background5),
|
||||
},
|
||||
new HoverClickSounds(HoverSampleSet.Button)
|
||||
{
|
||||
Enabled = { BindTarget = Enabled },
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 0.45f,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = Caption,
|
||||
Left = 9,
|
||||
Right = 5,
|
||||
Vertical = 5,
|
||||
},
|
||||
new Button
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Action = Action,
|
||||
Text = ButtonText,
|
||||
RelativeSizeAxes = ButtonText == default ? Axes.None : Axes.X,
|
||||
Width = ButtonText == default ? 90 : 0.45f,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
}
|
||||
text = new OsuTextFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = Caption,
|
||||
},
|
||||
button = new Button
|
||||
{
|
||||
Action = () => Action?.Invoke(),
|
||||
Text = ButtonText,
|
||||
Icon = ButtonIcon,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Enabled = { BindTarget = Enabled },
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if (ButtonText == default)
|
||||
{
|
||||
text.Padding = new MarginPadding { Right = 100 };
|
||||
button.Width = 90;
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Width = 0.55f;
|
||||
text.Padding = new MarginPadding { Right = 10 };
|
||||
button.RelativeSizeAxes = Axes.X;
|
||||
button.Width = 0.45f;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Enabled.BindValueChanged(_ => updateState(), true);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
@@ -98,12 +171,34 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
updateState();
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (Enabled.Value)
|
||||
{
|
||||
background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint);
|
||||
button.TriggerClick();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
BorderThickness = IsHovered ? 2 : 0;
|
||||
text.Colour = Enabled.Value ? colourProvider.Content1 : colourProvider.Background1;
|
||||
|
||||
if (IsHovered)
|
||||
BorderColour = colourProvider.Light4;
|
||||
background.FadeColour(IsHovered
|
||||
? ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4)
|
||||
: colourProvider.Background5, 200, Easing.OutQuint);
|
||||
|
||||
content.BorderThickness = IsHovered ? 2 : 0;
|
||||
|
||||
if (BackgroundColour != null)
|
||||
{
|
||||
button.BackgroundColour = BackgroundColour.Value;
|
||||
content.BorderColour = Enabled.Value ? BackgroundColour.Value : Interpolation.ValueAt(0.75, BackgroundColour.Value, colourProvider.Dark1, 0, 1);
|
||||
}
|
||||
else
|
||||
content.BorderColour = Enabled.Value ? colourProvider.Light4 : colourProvider.Dark1;
|
||||
}
|
||||
|
||||
public partial class Button : OsuButton
|
||||
@@ -125,6 +220,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
}
|
||||
|
||||
public IconUsage Icon { get; init; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider overlayColourProvider)
|
||||
{
|
||||
@@ -135,7 +232,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
Add(new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Icon = Icon,
|
||||
Size = new Vector2(16),
|
||||
Shadow = true,
|
||||
Anchor = Anchor.Centre,
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private FormDropdownHeader header = null!;
|
||||
|
||||
private const float header_menu_spacing = 5;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@@ -47,6 +49,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
header.Caption = Caption;
|
||||
header.HintText = HintText;
|
||||
|
||||
// there's bottom margin applied inside the header to give spacing between the header and the menu.
|
||||
// however when the menu is closed the extra spacing remains present. to remove it, apply negative bottom padding here.
|
||||
Margin = new MarginPadding { Bottom = -header_menu_spacing };
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -145,6 +151,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
Margin = new MarginPadding { Bottom = header_menu_spacing };
|
||||
|
||||
Foreground.Padding = new MarginPadding(9);
|
||||
Foreground.Children = new Drawable[]
|
||||
{
|
||||
@@ -278,11 +286,26 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
ItemsContainer.Padding = new MarginPadding(9);
|
||||
Margin = new MarginPadding { Top = 5 };
|
||||
|
||||
MaskingContainer.BorderThickness = 2;
|
||||
MaskingContainer.BorderColour = colourProvider.Highlight1;
|
||||
}
|
||||
|
||||
protected override void AnimateOpen()
|
||||
{
|
||||
base.AnimateOpen();
|
||||
|
||||
// there's negative bottom margin applied on the whole dropdown control to remove extra spacing when the menu is closed.
|
||||
// however, when the menu is open, we want spacing between the menu and the next control below it. therefore apply bottom margin here.
|
||||
// we use a transform to keep the open animation smooth while margin is adjusted.
|
||||
this.TransformTo(nameof(Margin), new MarginPadding { Bottom = header_menu_spacing }, 300, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void AnimateClose()
|
||||
{
|
||||
base.AnimateClose();
|
||||
this.TransformTo(nameof(Margin), new MarginPadding { Bottom = 0 }, 300, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private readonly Bindable<Language> currentLanguage = new Bindable<Language>();
|
||||
|
||||
public bool TakeFocus() => GetContainingFocusManager()?.ChangeFocus(textBox) == true;
|
||||
|
||||
public FormSliderBar()
|
||||
{
|
||||
LabelFormat ??= defaultLabelFormat;
|
||||
@@ -233,6 +235,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
TooltipFormat = TooltipFormat,
|
||||
DisplayAsPercentage = DisplayAsPercentage,
|
||||
PlaySamplesOnAdjust = PlaySamplesOnAdjust,
|
||||
ResetToDefault = () =>
|
||||
{
|
||||
if (!IsDisabled)
|
||||
SetDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -323,7 +330,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
break;
|
||||
|
||||
case Bindable<double> bindableDouble:
|
||||
bindableDouble.Value = double.Parse(textBox.Current.Value);
|
||||
bindableDouble.Value = double.Parse(textBox.Current.Value) / (DisplayAsPercentage ? 100 : 1);
|
||||
break;
|
||||
|
||||
case Bindable<float> bindableFloat:
|
||||
bindableFloat.Value = float.Parse(textBox.Current.Value) / (DisplayAsPercentage ? 100 : 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -390,7 +401,18 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
if (updatingFromTextBox) return;
|
||||
|
||||
textBox.Text = currentNumberInstantaneous.Value.ToStandardFormattedString(OsuSliderBar<T>.MAX_DECIMAL_DIGITS);
|
||||
if (DisplayAsPercentage)
|
||||
{
|
||||
double floatValue = double.CreateTruncating(currentNumberInstantaneous.Value);
|
||||
|
||||
if (currentNumberInstantaneous.Value is int)
|
||||
floatValue /= 100;
|
||||
|
||||
textBox.Text = floatValue.ToStandardFormattedString(Math.Max(0, OsuSliderBar<T>.MAX_DECIMAL_DIGITS - 2));
|
||||
}
|
||||
else
|
||||
textBox.Text = currentNumberInstantaneous.Value.ToStandardFormattedString(OsuSliderBar<T>.MAX_DECIMAL_DIGITS);
|
||||
|
||||
valueLabel.Text = LabelFormat(currentNumberInstantaneous.Value);
|
||||
}
|
||||
|
||||
@@ -400,9 +422,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public BindableBool Focused { get; } = new BindableBool();
|
||||
|
||||
public BindableBool IsDragging { get; set; } = new BindableBool();
|
||||
public BindableBool IsDragging { get; } = new BindableBool();
|
||||
|
||||
public Action? OnCommit { get; set; }
|
||||
public Action? ResetToDefault { get; init; }
|
||||
|
||||
public Action? OnCommit { get; init; }
|
||||
|
||||
public sealed override LocalisableString TooltipText => base.TooltipText;
|
||||
|
||||
@@ -453,11 +477,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Padding = new MarginPadding { Horizontal = RangePadding, },
|
||||
Child = nub = new InnerSliderNub
|
||||
{
|
||||
ResetToDefault = () =>
|
||||
{
|
||||
if (!Current.Disabled)
|
||||
Current.SetDefault();
|
||||
}
|
||||
ResetToDefault = ResetToDefault,
|
||||
}
|
||||
},
|
||||
sounds = new HoverClickSounds()
|
||||
|
||||
@@ -146,14 +146,9 @@ Click to see what's new!", version);
|
||||
public static LocalisableString FriendOffline(string info) => new TranslatableString(getKey(@"friend_offline"), @"Offline: {0}", info);
|
||||
|
||||
/// <summary>
|
||||
/// "Connection to API was lost. Can't continue with online play."
|
||||
/// "Connection to online services was interrupted. osu! will be operating with limited functionality."
|
||||
/// </summary>
|
||||
public static LocalisableString APIDisconnect => new TranslatableString(getKey(@"api_disconnect"), @"Connection to API was lost. Can't continue with online play.");
|
||||
|
||||
/// <summary>
|
||||
/// "Connection to the multiplayer server was lost. Exiting multiplayer."
|
||||
/// </summary>
|
||||
public static LocalisableString MultiplayerDisconnect => new TranslatableString(getKey(@"multiplayer_disconnect"), @"Connection to the multiplayer server was lost. Exiting multiplayer.");
|
||||
public static LocalisableString APIConnectionInterrupted => new TranslatableString(getKey(@"api_connection_interrupted"), @"Connection to online services was interrupted. osu! will be operating with limited functionality.");
|
||||
|
||||
/// <summary>
|
||||
/// "You have been logged out on this device due to a login to your account on another device."
|
||||
@@ -215,6 +210,56 @@ Click to see what's new!", version);
|
||||
/// </summary>
|
||||
public static LocalisableString ActionInterruptedByDialog => new TranslatableString(getKey(@"action_interrupted_by_dialog"), @"An action was interrupted due to a dialog being displayed.");
|
||||
|
||||
/// <summary>
|
||||
/// "Exporting {0}..."
|
||||
/// </summary>
|
||||
public static LocalisableString FileExportOngoing(string filename) => new TranslatableString(getKey(@"file_export_ongoing"), @"Exporting {0}...", filename);
|
||||
|
||||
/// <summary>
|
||||
/// "Exported {0}! Click to view."
|
||||
/// </summary>
|
||||
public static LocalisableString FileExportFinished(string filename) => new TranslatableString(getKey(@"file_export_finished"), @"Exported {0}! Click to view.", filename);
|
||||
|
||||
/// <summary>
|
||||
/// "Exporting logs..."
|
||||
/// </summary>
|
||||
public static LocalisableString LogsExportOngoing => new TranslatableString(getKey(@"logs_export_ongoing"), @"Exporting logs...");
|
||||
|
||||
/// <summary>
|
||||
/// "Exported logs! Click to view."
|
||||
/// </summary>
|
||||
public static LocalisableString LogsExportFinished => new TranslatableString(getKey(@"logs_export_finished"), @"Exported logs! Click to view.");
|
||||
|
||||
/// <summary>
|
||||
/// "Running osu! as {0} does not improve performance, may break integrations and poses a security risk. Please run the game as a normal user."
|
||||
/// </summary>
|
||||
public static LocalisableString ElevatedPrivileges(LocalisableString user) => new TranslatableString(getKey(@"elevated_privileges"), @"Running osu! as {0} does not improve performance, may break integrations and poses a security risk. Please run the game as a normal user.", user);
|
||||
|
||||
/// <summary>
|
||||
/// "Screenshot {0} saved!"
|
||||
/// </summary>
|
||||
public static LocalisableString ScreenshotSaved(string filename) => new TranslatableString(getKey(@"screenshot_saved"), @"Screenshot {0} saved!", filename);
|
||||
|
||||
/// <summary>
|
||||
/// "The multiplayer server will be right back..."
|
||||
/// </summary>
|
||||
public static LocalisableString MultiplayerServerShuttingDownImmediately => new TranslatableString(getKey(@"multiplayer_server_shutting_down_immediately"), @"The multiplayer server will be right back...");
|
||||
|
||||
/// <summary>
|
||||
/// "The multiplayer server is restarting in {0}."
|
||||
/// </summary>
|
||||
public static LocalisableString MultiplayerServerShuttingDownRemaining(string remainingTime) => new TranslatableString(getKey(@"multiplayer_server_shutting_down_remaining"), @"The multiplayer server is restarting in {0}.", remainingTime);
|
||||
|
||||
/// <summary>
|
||||
/// "Created new collection "{0}" with {1} beatmaps."
|
||||
/// </summary>
|
||||
public static LocalisableString CollectionCreated(string name, int beatmapsCount) => new TranslatableString(getKey(@"collection_created"), @"Created new collection ""{0}"" with {1} beatmaps.", name, beatmapsCount);
|
||||
|
||||
/// <summary>
|
||||
/// "Added {0} beatmaps to collection "{1}"."
|
||||
/// </summary>
|
||||
public static LocalisableString CollectionBeatmapsAdded(string name, int beatmapsCount) => new TranslatableString(getKey(@"collection_beatmaps_added"), @"Added {0} beatmaps to collection ""{1}"".", beatmapsCount, name);
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using osu.Framework.Extensions.ExceptionExtensions;
|
||||
@@ -20,13 +21,23 @@ namespace osu.Game.Online.Multiplayer
|
||||
Debug.Assert(t.Exception != null);
|
||||
Exception exception = t.Exception.AsSingular();
|
||||
|
||||
onError?.Invoke(exception);
|
||||
|
||||
if (exception is WebSocketException wse && wse.Message == @"The remote party closed the WebSocket connection without completing the close handshake.")
|
||||
{
|
||||
// OnlineStatusNotifier is already letting users know about interruptions to connections.
|
||||
// Silence these because it gets very spammy otherwise.
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception.GetHubExceptionMessage() is string message)
|
||||
{
|
||||
// Hub exceptions generally contain something we can show the user directly.
|
||||
Logger.Log(message, level: LogLevel.Important);
|
||||
else
|
||||
Logger.Error(exception, $"Unobserved exception occurred via {nameof(FireAndForget)} call: {exception.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
onError?.Invoke(exception);
|
||||
Logger.Error(exception, $"Unobserved exception occurred via {nameof(FireAndForget)} call: {exception.Message}");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using Humanizer.Localisation;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Utils;
|
||||
|
||||
@@ -53,10 +54,10 @@ namespace osu.Game.Online.Multiplayer
|
||||
if (remaining.TotalSeconds <= 5)
|
||||
{
|
||||
updateDelegate?.Cancel();
|
||||
Text = "The multiplayer server will be right back...";
|
||||
Text = NotificationsStrings.MultiplayerServerShuttingDownImmediately;
|
||||
}
|
||||
else
|
||||
Text = $"The multiplayer server is restarting in {HumanizerUtils.Humanize(remaining, precision: 3, minUnit: TimeUnit.Second)}.";
|
||||
Text = NotificationsStrings.MultiplayerServerShuttingDownRemaining(HumanizerUtils.Humanize(remaining, precision: 3, minUnit: TimeUnit.Second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Game.Online.Spectator;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Online
|
||||
{
|
||||
@@ -75,22 +76,16 @@ namespace osu.Game.Online
|
||||
|
||||
apiState.BindValueChanged(state =>
|
||||
{
|
||||
if (state.NewValue == APIState.Online)
|
||||
switch (state.NewValue)
|
||||
{
|
||||
userNotified = false;
|
||||
return;
|
||||
}
|
||||
case APIState.Online:
|
||||
userNotified = false;
|
||||
return;
|
||||
|
||||
if (userNotified) return;
|
||||
|
||||
if (state.NewValue == APIState.Offline && getCurrentScreen() is OnlinePlayScreen)
|
||||
{
|
||||
userNotified = true;
|
||||
notificationOverlay?.Post(new SimpleErrorNotification
|
||||
{
|
||||
Icon = FontAwesome.Solid.ExclamationCircle,
|
||||
Text = NotificationsStrings.APIDisconnect,
|
||||
});
|
||||
case APIState.Offline:
|
||||
if (getCurrentScreen() is OnlinePlayScreen)
|
||||
notifyApiDisconnection();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -102,22 +97,37 @@ namespace osu.Game.Online
|
||||
return;
|
||||
}
|
||||
|
||||
if (userNotified) return;
|
||||
|
||||
if (multiplayerClient.Room != null)
|
||||
{
|
||||
userNotified = true;
|
||||
notificationOverlay?.Post(new SimpleErrorNotification
|
||||
{
|
||||
Icon = FontAwesome.Solid.ExclamationCircle,
|
||||
Text = NotificationsStrings.MultiplayerDisconnect,
|
||||
});
|
||||
}
|
||||
notifyApiDisconnection();
|
||||
}));
|
||||
|
||||
spectatorState.BindValueChanged(_ =>
|
||||
spectatorState.BindValueChanged(connected => Schedule(() =>
|
||||
{
|
||||
// TODO: handle spectator server failure somehow?
|
||||
if (connected.NewValue)
|
||||
{
|
||||
userNotified = false;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (getCurrentScreen())
|
||||
{
|
||||
case SpectatorPlayer: // obvious issues
|
||||
case SubmittingPlayer: // replay sending issues
|
||||
notifyApiDisconnection();
|
||||
break;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void notifyApiDisconnection()
|
||||
{
|
||||
if (userNotified) return;
|
||||
|
||||
userNotified = true;
|
||||
notificationOverlay?.Post(new SimpleErrorNotification
|
||||
{
|
||||
Icon = FontAwesome.Solid.ExclamationCircle,
|
||||
Text = NotificationsStrings.APIConnectionInterrupted,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
@@ -86,7 +85,7 @@ namespace osu.Game.Overlays.Music
|
||||
private readonly GlobalAction action;
|
||||
|
||||
public MusicActionToast(LocalisableString value, GlobalAction action)
|
||||
: base(ToastStrings.MusicPlayback, value, string.Empty)
|
||||
: base(ToastStrings.MusicPlayback, value)
|
||||
{
|
||||
this.action = action;
|
||||
}
|
||||
@@ -94,7 +93,7 @@ namespace osu.Game.Overlays.Music
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RealmKeyBindingStore keyBindingStore)
|
||||
{
|
||||
ShortcutText.Text = keyBindingStore.GetBindingsStringFor(action).ToUpper();
|
||||
ExtraText = keyBindingStore.GetBindingsStringFor(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace osu.Game.Overlays.OSD
|
||||
public partial class CopiedToClipboardToast : Toast
|
||||
{
|
||||
public CopiedToClipboardToast()
|
||||
: base(CommonStrings.General, ToastStrings.CopiedToClipboard, "")
|
||||
: base(CommonStrings.General, ToastStrings.CopiedToClipboard)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.Game.Input;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
@@ -9,9 +10,15 @@ namespace osu.Game.Overlays.OSD
|
||||
{
|
||||
public partial class SpeedChangeToast : Toast
|
||||
{
|
||||
public SpeedChangeToast(RealmKeyBindingStore keyBindingStore, double newSpeed)
|
||||
: base(ModSelectOverlayStrings.ModCustomisation, ToastStrings.SpeedChangedTo(newSpeed), keyBindingStore.GetBindingsStringFor(GlobalAction.IncreaseModSpeed) + " / " + keyBindingStore.GetBindingsStringFor(GlobalAction.DecreaseModSpeed))
|
||||
public SpeedChangeToast(double newSpeed)
|
||||
: base(ModSelectOverlayStrings.ModCustomisation, ToastStrings.SpeedChangedTo(newSpeed))
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RealmKeyBindingStore keyBindingStore)
|
||||
{
|
||||
ExtraText = keyBindingStore.GetBindingsStringFor(GlobalAction.IncreaseModSpeed) + " / " + keyBindingStore.GetBindingsStringFor(GlobalAction.DecreaseModSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,23 +10,30 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Overlays.OSD
|
||||
{
|
||||
public abstract partial class Toast : Container
|
||||
{
|
||||
/// <summary>
|
||||
/// Extra text to be shown at the bottom of the toast. Usually a key binding if available.
|
||||
/// </summary>
|
||||
public LocalisableString ExtraText
|
||||
{
|
||||
get => extraText.Text;
|
||||
set => extraText.Text = value.ToUpper();
|
||||
}
|
||||
|
||||
private const int toast_minimum_width = 240;
|
||||
|
||||
private readonly Container content;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
protected readonly OsuSpriteText ValueText;
|
||||
protected readonly OsuSpriteText ValueSpriteText;
|
||||
private readonly OsuSpriteText extraText;
|
||||
|
||||
protected readonly OsuSpriteText ShortcutText;
|
||||
|
||||
protected Toast(LocalisableString description, LocalisableString value, LocalisableString shortcut)
|
||||
protected Toast(LocalisableString description, LocalisableString value)
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
@@ -65,7 +72,7 @@ namespace osu.Game.Overlays.OSD
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = description.ToUpper()
|
||||
},
|
||||
ValueText = new OsuSpriteText
|
||||
ValueSpriteText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light),
|
||||
Padding = new MarginPadding { Horizontal = 10 },
|
||||
@@ -74,15 +81,14 @@ namespace osu.Game.Overlays.OSD
|
||||
Origin = Anchor.Centre,
|
||||
Text = value
|
||||
},
|
||||
ShortcutText = new OsuSpriteText
|
||||
extraText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Name = "Shortcut",
|
||||
Name = "Extra Text",
|
||||
Alpha = 0.3f,
|
||||
Margin = new MarginPadding { Bottom = 15, Horizontal = 10 },
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
|
||||
Text = string.IsNullOrEmpty(shortcut.ToString()) ? ToastStrings.NoKeyBound.ToUpper() : shortcut.ToUpper()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,8 +35,10 @@ namespace osu.Game.Overlays.OSD
|
||||
private Bindable<double?> lastPlaybackTime;
|
||||
|
||||
public TrackedSettingToast(SettingDescription description)
|
||||
: base(description.Name, description.Value, description.Shortcut)
|
||||
: base(description.Name, description.Value)
|
||||
{
|
||||
ExtraText = description.Shortcut;
|
||||
|
||||
FillFlowContainer<OptionLight> optionLights;
|
||||
|
||||
Children = new Drawable[]
|
||||
@@ -75,7 +77,7 @@ namespace osu.Game.Overlays.OSD
|
||||
break;
|
||||
}
|
||||
|
||||
ValueText.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre;
|
||||
ValueSpriteText.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre;
|
||||
|
||||
for (int i = 0; i < optionCount; i++)
|
||||
optionLights.Add(new OptionLight { Glowing = i == selectedOption });
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
ProgressNotification notification = new ProgressNotification
|
||||
{
|
||||
State = ProgressNotificationState.Active,
|
||||
Text = "Exporting logs...",
|
||||
Text = NotificationsStrings.LogsExportOngoing,
|
||||
};
|
||||
|
||||
notifications?.Post(notification);
|
||||
@@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
throw;
|
||||
}
|
||||
|
||||
notification.CompletionText = "Exported logs! Click to view.";
|
||||
notification.CompletionText = NotificationsStrings.LogsExportFinished;
|
||||
notification.CompletionClickAction = () => exportStorage.PresentFileExternally(archive_filename);
|
||||
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
|
||||
@@ -772,8 +772,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
private partial class SkinEditorToast : Toast
|
||||
{
|
||||
public SkinEditorToast(LocalisableString value, string skinDisplayName)
|
||||
: base(SkinSettingsStrings.SkinLayoutEditor, value, skinDisplayName)
|
||||
: base(SkinSettingsStrings.SkinLayoutEditor, value)
|
||||
{
|
||||
ExtraText = skinDisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -308,7 +308,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
private readonly ValueChangedEvent<double> change;
|
||||
|
||||
public DistanceSpacingToast(LocalisableString value, ValueChangedEvent<double> change)
|
||||
: base(getAction(change).GetLocalisableDescription(), value, string.Empty)
|
||||
: base(getAction(change).GetLocalisableDescription(), value)
|
||||
{
|
||||
this.change = change;
|
||||
}
|
||||
@@ -316,7 +316,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RealmKeyBindingStore keyBindingStore)
|
||||
{
|
||||
ShortcutText.Text = keyBindingStore.GetBindingsStringFor(getAction(change)).ToUpper();
|
||||
ExtraText = keyBindingStore.GetBindingsStringFor(getAction(change));
|
||||
}
|
||||
|
||||
private static GlobalAction getAction(ValueChangedEvent<double> change) => change.NewValue - change.OldValue > 0
|
||||
|
||||
@@ -19,7 +19,6 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.LAsEzExtensions.Audio;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Pooling;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@@ -741,9 +740,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
Result.GameplayRate = (Clock as IGameplayClock)?.GetTrueGameplayRate() ?? Clock.Rate;
|
||||
|
||||
if (Result.HasResult)
|
||||
{
|
||||
UpdateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss);
|
||||
}
|
||||
|
||||
OnNewResult?.Invoke(this, Result);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace osu.Game.Screens.Edit.Components
|
||||
set => current.Current = value;
|
||||
}
|
||||
|
||||
public Func<FileInfo, string>? SampleAddRequested { get; init; }
|
||||
public Func<FileInfo, string, string>? SampleAddRequested { get; init; }
|
||||
public Action<string>? SampleRemoveRequested { get; init; }
|
||||
|
||||
private readonly BindableWithCurrent<EditorBeatmapSkin.SampleSet?> current = new BindableWithCurrent<EditorBeatmapSkin.SampleSet?>();
|
||||
@@ -194,7 +194,7 @@ namespace osu.Game.Screens.Edit.Components
|
||||
/// <summary>
|
||||
/// Invoked when a new sample is selected via this button.
|
||||
/// </summary>
|
||||
public Func<FileInfo, string>? SampleAddRequested { get; init; }
|
||||
public Func<FileInfo, string, string>? SampleAddRequested { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a sample removal is selected via this button.
|
||||
@@ -294,7 +294,18 @@ namespace osu.Game.Screens.Edit.Components
|
||||
|
||||
AddInternal(hoverSounds = (ActualFilename.Value == null ? new HoverClickSounds(HoverSampleSet.Button) : new HoverSounds(HoverSampleSet.Button)));
|
||||
|
||||
sample = ActualFilename.Value == null ? null : editorBeatmap?.BeatmapSkin?.Skin.Samples?.Get(ActualFilename.Value);
|
||||
if (ActualFilename.Value != null)
|
||||
{
|
||||
// to cover all bases, invalidate the extensionless filename (which gameplay is most likely to use)
|
||||
// as well as the filename with extension (which we are using here).
|
||||
editorBeatmap?.BeatmapSkin?.Skin.Samples?.Invalidate(ExpectedFilename.Value);
|
||||
editorBeatmap?.BeatmapSkin?.Skin.Samples?.Invalidate(ActualFilename.Value);
|
||||
sample = editorBeatmap?.BeatmapSkin?.Skin.Samples?.Get(ActualFilename.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
@@ -317,7 +328,7 @@ namespace osu.Game.Screens.Edit.Components
|
||||
return;
|
||||
|
||||
this.HidePopover();
|
||||
ActualFilename.Value = SampleAddRequested?.Invoke(selectedFile.Value) ?? selectedFile.Value.ToString();
|
||||
ActualFilename.Value = SampleAddRequested?.Invoke(selectedFile.Value, ExpectedFilename.Value) ?? selectedFile.Value.ToString();
|
||||
}
|
||||
|
||||
private void deleteSample()
|
||||
|
||||
@@ -1620,8 +1620,9 @@ namespace osu.Game.Screens.Edit
|
||||
private partial class BeatmapEditorToast : Toast
|
||||
{
|
||||
public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName)
|
||||
: base(InputSettingsStrings.EditorSection, value, beatmapDisplayName)
|
||||
: base(InputSettingsStrings.EditorSection, value)
|
||||
{
|
||||
ExtraText = beatmapDisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,11 +101,8 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
this.beatmapInfo = beatmapInfo ?? playableBeatmap.BeatmapInfo;
|
||||
|
||||
if (beatmapSkin is Skin skin)
|
||||
{
|
||||
BeatmapSkin = new EditorBeatmapSkin(skin);
|
||||
BeatmapSkin.BeatmapSkinChanged += SaveState;
|
||||
}
|
||||
if (beatmapSkin is LegacyBeatmapSkin skin)
|
||||
BeatmapSkin = new EditorBeatmapSkin(this, skin);
|
||||
|
||||
beatmapProcessor = new EditorBeatmapProcessor(this, playableBeatmap.BeatmapInfo.Ruleset.CreateInstance());
|
||||
|
||||
@@ -532,5 +529,11 @@ namespace osu.Game.Screens.Edit
|
||||
public double GetBeatLengthAtTime(double referenceTime) => ControlPointInfo.TimingPointAt(referenceTime).BeatLength / BeatDivisor;
|
||||
|
||||
public int BeatDivisor => beatDivisor?.Value ?? 1;
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
BeatmapSkin?.Dispose();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,14 @@ namespace osu.Game.Screens.Edit
|
||||
/// <summary>
|
||||
/// A beatmap skin which is being edited.
|
||||
/// </summary>
|
||||
public class EditorBeatmapSkin : ISkin
|
||||
public class EditorBeatmapSkin : ISkin, IDisposable
|
||||
{
|
||||
public event Action? BeatmapSkinChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying beatmap skin.
|
||||
/// </summary>
|
||||
protected internal readonly Skin Skin;
|
||||
protected internal readonly LegacyBeatmapSkin Skin;
|
||||
|
||||
/// <summary>
|
||||
/// The combo colours of this skin.
|
||||
@@ -33,10 +33,13 @@ namespace osu.Game.Screens.Edit
|
||||
/// </summary>
|
||||
public BindableList<Colour4> ComboColours { get; }
|
||||
|
||||
public EditorBeatmapSkin(Skin skin)
|
||||
{
|
||||
Skin = skin;
|
||||
private readonly EditorBeatmap editorBeatmap;
|
||||
|
||||
public EditorBeatmapSkin(EditorBeatmap editorBeatmap, LegacyBeatmapSkin skin)
|
||||
{
|
||||
this.editorBeatmap = editorBeatmap;
|
||||
|
||||
Skin = skin;
|
||||
ComboColours = new BindableList<Colour4>();
|
||||
|
||||
if (Skin.Configuration.ComboColours is IReadOnlyList<Color4> comboColours)
|
||||
@@ -50,9 +53,14 @@ namespace osu.Game.Screens.Edit
|
||||
}
|
||||
|
||||
ComboColours.BindCollectionChanged((_, _) => updateColours());
|
||||
|
||||
if (skin.BeatmapSetResources != null)
|
||||
skin.BeatmapSetResources.CacheInvalidated += InvokeSkinChanged;
|
||||
}
|
||||
|
||||
private void invokeSkinChanged() => BeatmapSkinChanged?.Invoke();
|
||||
public void InvokeSkinChanged() => BeatmapSkinChanged?.Invoke();
|
||||
|
||||
#region Combo colours
|
||||
|
||||
private void updateColours()
|
||||
{
|
||||
@@ -60,9 +68,14 @@ namespace osu.Game.Screens.Edit
|
||||
Skin.Configuration.CustomComboColours.Clear();
|
||||
for (int i = 0; i < ComboColours.Count; ++i)
|
||||
Skin.Configuration.CustomComboColours.Add(ComboColours[(ComboColours.Count + i - 1) % ComboColours.Count]);
|
||||
invokeSkinChanged();
|
||||
InvokeSkinChanged();
|
||||
editorBeatmap.SaveState();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sample sets
|
||||
|
||||
public record SampleSet(int SampleSetIndex, string Name)
|
||||
{
|
||||
public SampleSet(int sampleSetIndex)
|
||||
@@ -88,7 +101,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
string[] possiblePrefixes = possibleSounds.SelectMany(sound => possibleBanks.Select(bank => $@"{bank}-{sound}")).ToArray();
|
||||
|
||||
HashSet<int> indices = new HashSet<int>();
|
||||
Dictionary<int, SampleSet> sampleSets = new Dictionary<int, SampleSet>();
|
||||
|
||||
if (Skin.Samples != null)
|
||||
{
|
||||
@@ -96,19 +109,39 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
foreach (string possiblePrefix in possiblePrefixes)
|
||||
{
|
||||
if (!sample.StartsWith(possiblePrefix, StringComparison.InvariantCultureIgnoreCase))
|
||||
if (!sample.StartsWith(possiblePrefix, StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
string indexString = Path.GetFileNameWithoutExtension(sample)[possiblePrefix.Length..];
|
||||
int? index = null;
|
||||
|
||||
if (string.IsNullOrEmpty(indexString))
|
||||
indices.Add(1);
|
||||
if (int.TryParse(indexString, out int index))
|
||||
indices.Add(index);
|
||||
index = 1;
|
||||
if (int.TryParse(indexString, out int parsed))
|
||||
index = parsed;
|
||||
|
||||
if (!index.HasValue)
|
||||
continue;
|
||||
|
||||
SampleSet? sampleSet;
|
||||
if (!sampleSets.TryGetValue(index.Value, out sampleSet))
|
||||
sampleSet = sampleSets[index.Value] = new SampleSet(index.Value);
|
||||
|
||||
sampleSet.Filenames.Add(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return indices.OrderBy(i => i).Select(i => new SampleSet(i));
|
||||
return sampleSets.OrderBy(i => i.Key).Select(i => i.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Skin.BeatmapSetResources != null)
|
||||
Skin.BeatmapSetResources.CacheInvalidated -= InvokeSkinChanged;
|
||||
Skin.Dispose();
|
||||
}
|
||||
|
||||
#region Delegated ISkin implementation
|
||||
|
||||
144
osu.Game/Screens/Edit/Setup/FormSampleSetChooser.cs
Normal file
144
osu.Game/Screens/Edit/Setup/FormSampleSetChooser.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
// 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.FirstOrDefault(i => i?.SampleSetIndex > 0);
|
||||
Current.BindValueChanged(val =>
|
||||
{
|
||||
if (val.NewValue?.SampleSetIndex == -1)
|
||||
this.ShowPopover();
|
||||
});
|
||||
}
|
||||
|
||||
private void populateItems()
|
||||
{
|
||||
var items = beatmapSkin?.GetAvailableSampleSets().ToList() ?? [];
|
||||
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 += (_, _) =>
|
||||
{
|
||||
committedIndex = int.Parse(numberBox.Current.Value);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ using osu.Game.Localisation;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
@@ -23,6 +24,8 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
private FormBeatmapFileSelector audioTrackChooser = null!;
|
||||
private FormBeatmapFileSelector backgroundChooser = null!;
|
||||
|
||||
private readonly Bindable<EditorBeatmapSkin.SampleSet?> currentSampleSet = new Bindable<EditorBeatmapSkin.SampleSet?>();
|
||||
|
||||
public override LocalisableString Title => EditorSetupStrings.ResourcesHeader;
|
||||
|
||||
[Resolved]
|
||||
@@ -65,6 +68,27 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
Caption = EditorSetupStrings.AudioTrack,
|
||||
PlaceholderText = EditorSetupStrings.ClickToSelectTrack,
|
||||
},
|
||||
new FormSampleSetChooser
|
||||
{
|
||||
Current = { BindTarget = currentSampleSet },
|
||||
},
|
||||
new FormSampleSet
|
||||
{
|
||||
Current = { BindTarget = currentSampleSet },
|
||||
SampleAddRequested = (file, targetName) =>
|
||||
{
|
||||
string actualFilename = string.Concat(targetName, file.Extension);
|
||||
using var stream = file.OpenRead();
|
||||
beatmaps.AddFile(working.Value.BeatmapSetInfo, stream, actualFilename);
|
||||
return actualFilename;
|
||||
},
|
||||
SampleRemoveRequested = filename =>
|
||||
{
|
||||
var file = working.Value.BeatmapSetInfo.GetFile(filename);
|
||||
if (file != null)
|
||||
beatmaps.DeleteFile(working.Value.BeatmapSetInfo, file);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
backgroundChooser.PreviewContainer.Add(headerBackground);
|
||||
|
||||
@@ -11,21 +11,24 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
internal partial class EffectSection : Section<EffectControlPoint>
|
||||
{
|
||||
private LabelledSwitchButton kiai = null!;
|
||||
private FormCheckBox kiai = null!;
|
||||
|
||||
private SliderWithTextBoxInput<double> scrollSpeedSlider = null!;
|
||||
private FormSliderBar<double> scrollSpeedSlider { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Flow.AddRange(new Drawable[]
|
||||
{
|
||||
kiai = new LabelledSwitchButton { Label = "Kiai Time" },
|
||||
scrollSpeedSlider = new SliderWithTextBoxInput<double>("Scroll Speed")
|
||||
kiai = new FormCheckBox { Caption = "Kiai Time" },
|
||||
scrollSpeedSlider = new FormSliderBar<double>
|
||||
{
|
||||
Caption = "Scroll Speed",
|
||||
Current = new EffectControlPoint().ScrollSpeedBindable,
|
||||
KeyboardStep = 0.1f
|
||||
}
|
||||
KeyboardStep = 0.1f,
|
||||
TransferValueOnCommit = true,
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ using Vector2 = osuTK.Vector2;
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
/// <summary>
|
||||
/// Analogous to <see cref="SliderWithTextBoxInput{T}"/>, but supports scenarios
|
||||
/// Analogous to SliderWithTextBoxInput, but supports scenarios
|
||||
/// where multiple objects with multiple different property values are selected
|
||||
/// by providing an "indeterminate state".
|
||||
/// </summary>
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
@@ -69,10 +70,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
countAfter = c.BeatmapMD5Hashes.Count;
|
||||
}).ContinueWith(_ => Schedule(() =>
|
||||
{
|
||||
LocalisableString message;
|
||||
|
||||
if (countBefore == 0)
|
||||
notifications?.Post(new SimpleNotification { Text = $"Created new collection \"{room.Name}\" with {countAfter} beatmaps." });
|
||||
message = NotificationsStrings.CollectionCreated(room.Name, countAfter);
|
||||
else
|
||||
notifications?.Post(new SimpleNotification { Text = $"Added {countAfter - countBefore} beatmaps to collection \"{room.Name}\"." });
|
||||
message = NotificationsStrings.CollectionBeatmapsAdded(room.Name, countAfter - countBefore);
|
||||
|
||||
notifications?.Post(new SimpleNotification { Text = message });
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.OSD;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -21,9 +20,6 @@ namespace osu.Game.Screens.Select
|
||||
[Resolved]
|
||||
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RealmKeyBindingStore keyBindingStore { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OnScreenDisplay? onScreenDisplay { get; set; }
|
||||
|
||||
@@ -56,7 +52,7 @@ namespace osu.Game.Screens.Select
|
||||
if (Precision.AlmostEquals(targetSpeed, 1, 0.005))
|
||||
{
|
||||
selectedMods.Value = selectedMods.Value.Where(m => m is not ModRateAdjust).ToList();
|
||||
onScreenDisplay?.Display(new SpeedChangeToast(keyBindingStore, targetSpeed));
|
||||
onScreenDisplay?.Display(new SpeedChangeToast(targetSpeed));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -109,7 +105,7 @@ namespace osu.Game.Screens.Select
|
||||
return false;
|
||||
|
||||
selectedMods.Value = intendedMods;
|
||||
onScreenDisplay?.Display(new SpeedChangeToast(keyBindingStore, targetMod.SpeedChange.Value));
|
||||
onScreenDisplay?.Display(new SpeedChangeToast(targetMod.SpeedChange.Value));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,8 +206,13 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
beatmap.BindValueChanged(_ => updateDisplay());
|
||||
ruleset.BindValueChanged(_ => updateDisplay());
|
||||
// it is not uncommon for the beatmap and the ruleset to change in conjunction during a single update frame.
|
||||
// in that process, it is possible for the global bindable triad (beatmap / ruleset / mods) to briefly be partially invalid in combination (e.g. mods invalid for given ruleset).
|
||||
// `updateDisplay()` will initiate a difficulty calculation, and if it is allowed to run in that invalid intermediate state, it will loudly fail.
|
||||
// therefore, all changes that may initiate a difficulty calculation are debounced until the next frame to ensure the global bindable state is fully consistent -
|
||||
// and it's what you'd want to do anyway for performance reasons.
|
||||
beatmap.BindValueChanged(_ => Scheduler.AddOnce(updateDisplay));
|
||||
ruleset.BindValueChanged(_ => Scheduler.AddOnce(updateDisplay));
|
||||
|
||||
mods.BindValueChanged(m =>
|
||||
{
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace osu.Game.Skinning
|
||||
// 2. https://github.com/peppy/osu-stable-reference/blob/dc0994645801010d4b628fff5ff79cd3c286ca83/osu!/Graphics/Textures/TextureManager.cs#L158-L196 (user skin textures lookup)
|
||||
protected override bool AllowHighResolutionSprites => false;
|
||||
|
||||
public RealmBackedResourceStore<BeatmapSetInfo>? BeatmapSetResources => FallbackStore as RealmBackedResourceStore<BeatmapSetInfo>;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new legacy beatmap skin instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace osu.Game.Skinning
|
||||
public class RealmBackedResourceStore<T> : ResourceStore<byte[]>
|
||||
where T : RealmObject, IHasRealmFiles, IHasGuidPrimaryKey
|
||||
{
|
||||
public event Action? CacheInvalidated;
|
||||
|
||||
private Lazy<Dictionary<string, string>> fileToStoragePathMapping;
|
||||
|
||||
private readonly Live<T> liveSource;
|
||||
@@ -56,7 +58,11 @@ namespace osu.Game.Skinning
|
||||
private string? getPathForFile(string filename) =>
|
||||
fileToStoragePathMapping.Value.GetValueOrDefault(filename.ToLowerInvariant());
|
||||
|
||||
private void invalidateCache() => fileToStoragePathMapping = new Lazy<Dictionary<string, string>>(initialiseFileCache);
|
||||
private void invalidateCache()
|
||||
{
|
||||
fileToStoragePathMapping = new Lazy<Dictionary<string, string>>(initialiseFileCache);
|
||||
CacheInvalidated?.Invoke();
|
||||
}
|
||||
|
||||
private Dictionary<string, string> initialiseFileCache() => liveSource.PerformRead(source =>
|
||||
{
|
||||
|
||||
@@ -63,6 +63,8 @@ namespace osu.Game.Skinning
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
protected IResourceStore<byte[]>? FallbackStore { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new skin.
|
||||
/// </summary>
|
||||
@@ -102,6 +104,7 @@ namespace osu.Game.Skinning
|
||||
SkinInfo = skin.ToLiveUnmanaged();
|
||||
}
|
||||
|
||||
FallbackStore = fallbackStore;
|
||||
if (fallbackStore != null)
|
||||
store.AddStore(fallbackStore);
|
||||
|
||||
@@ -339,6 +342,7 @@ namespace osu.Game.Skinning
|
||||
|
||||
Textures?.Dispose();
|
||||
Samples?.Dispose();
|
||||
FallbackStore?.Dispose();
|
||||
|
||||
store.Dispose();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user