mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 07:33:55 +09:00
Merge branch 'master' into playlist-item-iruleset
This commit is contained in:
@ -1,9 +1,11 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
@ -14,19 +16,20 @@ using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
public class DifficultyPointPiece : HitObjectPointPiece, IHasPopover
|
||||
{
|
||||
private readonly HitObject hitObject;
|
||||
public readonly HitObject HitObject;
|
||||
|
||||
private readonly BindableNumber<double> speedMultiplier;
|
||||
|
||||
public DifficultyPointPiece(HitObject hitObject)
|
||||
: base(hitObject.DifficultyControlPoint)
|
||||
{
|
||||
this.hitObject = hitObject;
|
||||
HitObject = hitObject;
|
||||
|
||||
speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy();
|
||||
}
|
||||
@ -44,14 +47,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
return true;
|
||||
}
|
||||
|
||||
public Popover GetPopover() => new DifficultyEditPopover(hitObject);
|
||||
public Popover GetPopover() => new DifficultyEditPopover(HitObject);
|
||||
|
||||
public class DifficultyEditPopover : OsuPopover
|
||||
{
|
||||
private readonly HitObject hitObject;
|
||||
private readonly DifficultyControlPoint point;
|
||||
|
||||
private SliderWithTextBoxInput<double> sliderVelocitySlider;
|
||||
private IndeterminateSliderWithTextBoxInput<double> sliderVelocitySlider;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private EditorBeatmap beatmap { get; set; }
|
||||
@ -59,7 +61,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
public DifficultyEditPopover(HitObject hitObject)
|
||||
{
|
||||
this.hitObject = hitObject;
|
||||
point = hitObject.DifficultyControlPoint;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -72,11 +73,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
Width = 200,
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0, 15),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
sliderVelocitySlider = new SliderWithTextBoxInput<double>("Velocity")
|
||||
sliderVelocitySlider = new IndeterminateSliderWithTextBoxInput<double>("Velocity", new DifficultyControlPoint().SliderVelocityBindable)
|
||||
{
|
||||
Current = new DifficultyControlPoint().SliderVelocityBindable,
|
||||
KeyboardStep = 0.1f
|
||||
},
|
||||
new OsuTextFlowContainer
|
||||
@ -89,17 +90,37 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}
|
||||
};
|
||||
|
||||
var selectedPointBindable = point.SliderVelocityBindable;
|
||||
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
||||
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
||||
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
||||
var relevantControlPoints = relevantObjects.Select(h => h.DifficultyControlPoint).ToArray();
|
||||
|
||||
// there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint).
|
||||
// generally that level of precision could only be set by externally editing the .osu file, so at the point
|
||||
// a user is looking to update this within the editor it should be safe to obliterate this additional precision.
|
||||
double expectedPrecision = new DifficultyControlPoint().SliderVelocityBindable.Precision;
|
||||
if (selectedPointBindable.Precision < expectedPrecision)
|
||||
selectedPointBindable.Precision = expectedPrecision;
|
||||
// even if there are multiple objects selected, we can still display a value if they all have the same value.
|
||||
var selectedPointBindable = relevantControlPoints.Select(point => point.SliderVelocity).Distinct().Count() == 1 ? relevantControlPoints.First().SliderVelocityBindable : null;
|
||||
|
||||
sliderVelocitySlider.Current = selectedPointBindable;
|
||||
sliderVelocitySlider.Current.BindValueChanged(_ => beatmap?.Update(hitObject));
|
||||
if (selectedPointBindable != null)
|
||||
{
|
||||
// there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint).
|
||||
// generally that level of precision could only be set by externally editing the .osu file, so at the point
|
||||
// a user is looking to update this within the editor it should be safe to obliterate this additional precision.
|
||||
sliderVelocitySlider.Current.Value = selectedPointBindable.Value;
|
||||
}
|
||||
|
||||
sliderVelocitySlider.Current.BindValueChanged(val =>
|
||||
{
|
||||
if (val.NewValue == null)
|
||||
return;
|
||||
|
||||
beatmap.BeginChange();
|
||||
|
||||
foreach (var h in relevantObjects)
|
||||
{
|
||||
h.DifficultyControlPoint.SliderVelocity = val.NewValue.Value;
|
||||
beatmap.Update(h);
|
||||
}
|
||||
|
||||
beatmap.EndChange();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
@ -14,12 +19,13 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
public class SamplePointPiece : HitObjectPointPiece, IHasPopover
|
||||
{
|
||||
private readonly HitObject hitObject;
|
||||
public readonly HitObject HitObject;
|
||||
|
||||
private readonly Bindable<string> bank;
|
||||
private readonly BindableNumber<int> volume;
|
||||
@ -27,7 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
public SamplePointPiece(HitObject hitObject)
|
||||
: base(hitObject.SampleControlPoint)
|
||||
{
|
||||
this.hitObject = hitObject;
|
||||
HitObject = hitObject;
|
||||
volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy();
|
||||
bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy();
|
||||
}
|
||||
@ -50,23 +56,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
Label.Text = $"{bank.Value} {volume.Value}";
|
||||
}
|
||||
|
||||
public Popover GetPopover() => new SampleEditPopover(hitObject);
|
||||
public Popover GetPopover() => new SampleEditPopover(HitObject);
|
||||
|
||||
public class SampleEditPopover : OsuPopover
|
||||
{
|
||||
private readonly HitObject hitObject;
|
||||
private readonly SampleControlPoint point;
|
||||
|
||||
private LabelledTextBox bank;
|
||||
private SliderWithTextBoxInput<int> volume;
|
||||
private LabelledTextBox bank = null!;
|
||||
private IndeterminateSliderWithTextBoxInput<int> volume = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private EditorBeatmap beatmap { get; set; }
|
||||
private EditorBeatmap beatmap { get; set; } = null!;
|
||||
|
||||
public SampleEditPopover(HitObject hitObject)
|
||||
{
|
||||
this.hitObject = hitObject;
|
||||
point = hitObject.SampleControlPoint;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -79,25 +83,84 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
Width = 200,
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0, 10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
bank = new LabelledTextBox
|
||||
{
|
||||
Label = "Bank Name",
|
||||
},
|
||||
volume = new SliderWithTextBoxInput<int>("Volume")
|
||||
{
|
||||
Current = new SampleControlPoint().SampleVolumeBindable,
|
||||
}
|
||||
volume = new IndeterminateSliderWithTextBoxInput<int>("Volume", new SampleControlPoint().SampleVolumeBindable)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bank.Current = point.SampleBankBindable;
|
||||
bank.Current.BindValueChanged(_ => beatmap.Update(hitObject));
|
||||
// if the piece belongs to a currently selected object, assume that the user wants to change all selected objects.
|
||||
// if the piece belongs to an unselected object, operate on that object alone, independently of the selection.
|
||||
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray();
|
||||
var relevantControlPoints = relevantObjects.Select(h => h.SampleControlPoint).ToArray();
|
||||
|
||||
volume.Current = point.SampleVolumeBindable;
|
||||
volume.Current.BindValueChanged(_ => beatmap.Update(hitObject));
|
||||
// even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value.
|
||||
string? commonBank = getCommonBank(relevantControlPoints);
|
||||
if (!string.IsNullOrEmpty(commonBank))
|
||||
bank.Current.Value = commonBank;
|
||||
|
||||
int? commonVolume = getCommonVolume(relevantControlPoints);
|
||||
if (commonVolume != null)
|
||||
volume.Current.Value = commonVolume.Value;
|
||||
|
||||
updateBankPlaceholderText(relevantObjects);
|
||||
bank.Current.BindValueChanged(val =>
|
||||
{
|
||||
updateBankFor(relevantObjects, val.NewValue);
|
||||
updateBankPlaceholderText(relevantObjects);
|
||||
});
|
||||
// on commit, ensure that the value is correct by sourcing it from the objects' control points again.
|
||||
// this ensures that committing empty text causes a revert to the previous value.
|
||||
bank.OnCommit += (_, __) => bank.Current.Value = getCommonBank(relevantControlPoints);
|
||||
|
||||
volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue));
|
||||
}
|
||||
|
||||
private static string? getCommonBank(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null;
|
||||
private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null;
|
||||
|
||||
private void updateBankFor(IEnumerable<HitObject> objects, string? newBank)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newBank))
|
||||
return;
|
||||
|
||||
beatmap.BeginChange();
|
||||
|
||||
foreach (var h in objects)
|
||||
{
|
||||
h.SampleControlPoint.SampleBank = newBank;
|
||||
beatmap.Update(h);
|
||||
}
|
||||
|
||||
beatmap.EndChange();
|
||||
}
|
||||
|
||||
private void updateBankPlaceholderText(IEnumerable<HitObject> objects)
|
||||
{
|
||||
string? commonBank = getCommonBank(objects.Select(h => h.SampleControlPoint).ToArray());
|
||||
bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : null;
|
||||
}
|
||||
|
||||
private void updateVolumeFor(IEnumerable<HitObject> objects, int? newVolume)
|
||||
{
|
||||
if (newVolume == null)
|
||||
return;
|
||||
|
||||
beatmap.BeginChange();
|
||||
|
||||
foreach (var h in objects)
|
||||
{
|
||||
h.SampleControlPoint.SampleVolume = newVolume.Value;
|
||||
beatmap.Update(h);
|
||||
}
|
||||
|
||||
beatmap.EndChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ using osu.Game.Screens.Edit.Components.Menus;
|
||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
||||
using osu.Game.Screens.Edit.Compose;
|
||||
using osu.Game.Screens.Edit.Design;
|
||||
using osu.Game.Screens.Edit.GameplayTest;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
using osu.Game.Screens.Edit.Verify;
|
||||
@ -324,6 +325,19 @@ namespace osu.Game.Screens.Edit
|
||||
/// </summary>
|
||||
public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EditorState"/> instance representing the current state of the editor.
|
||||
/// </summary>
|
||||
/// <param name="nextBeatmap">
|
||||
/// The next beatmap to be shown, in the case of difficulty switch.
|
||||
/// <see langword="null"/> indicates that the beatmap will not be changing.
|
||||
/// </param>
|
||||
public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState
|
||||
{
|
||||
Time = clock.CurrentTimeAccurate,
|
||||
ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Restore the editor to a provided state.
|
||||
/// </summary>
|
||||
@ -486,7 +500,18 @@ namespace osu.Game.Screens.Edit
|
||||
public override void OnEntering(IScreen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
dimBackground();
|
||||
resetTrack(true);
|
||||
}
|
||||
|
||||
public override void OnResuming(IScreen last)
|
||||
{
|
||||
base.OnResuming(last);
|
||||
dimBackground();
|
||||
}
|
||||
|
||||
private void dimBackground()
|
||||
{
|
||||
ApplyToBackground(b =>
|
||||
{
|
||||
// todo: temporary. we want to be applying dim using the UserDimContainer eventually.
|
||||
@ -495,8 +520,6 @@ namespace osu.Game.Screens.Edit
|
||||
b.IgnoreUserSettings.Value = true;
|
||||
b.BlurAmount.Value = 0;
|
||||
});
|
||||
|
||||
resetTrack(true);
|
||||
}
|
||||
|
||||
public override bool OnExiting(IScreen next)
|
||||
@ -535,9 +558,9 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
public override void OnSuspending(IScreen next)
|
||||
{
|
||||
refetchBeatmap();
|
||||
|
||||
base.OnSuspending(next);
|
||||
clock.Stop();
|
||||
refetchBeatmap();
|
||||
}
|
||||
|
||||
private void refetchBeatmap()
|
||||
@ -770,11 +793,7 @@ namespace osu.Game.Screens.Edit
|
||||
return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty);
|
||||
}
|
||||
|
||||
protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState
|
||||
{
|
||||
Time = clock.CurrentTimeAccurate,
|
||||
ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty
|
||||
});
|
||||
protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, GetState(nextBeatmap));
|
||||
|
||||
private void cancelExit()
|
||||
{
|
||||
@ -797,7 +816,7 @@ namespace osu.Game.Screens.Edit
|
||||
pushEditorPlayer();
|
||||
}
|
||||
|
||||
void pushEditorPlayer() => this.Push(new PlayerLoader(() => new EditorPlayer()));
|
||||
void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this));
|
||||
}
|
||||
|
||||
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);
|
||||
|
@ -3,21 +3,30 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
namespace osu.Game.Screens.Edit.GameplayTest
|
||||
{
|
||||
public class EditorPlayer : Player
|
||||
{
|
||||
public EditorPlayer()
|
||||
: base(new PlayerConfiguration { ShowResults = false })
|
||||
{
|
||||
}
|
||||
private readonly Editor editor;
|
||||
private readonly EditorState editorState;
|
||||
|
||||
[Resolved]
|
||||
private MusicController musicController { get; set; }
|
||||
|
||||
public EditorPlayer(Editor editor)
|
||||
: base(new PlayerConfiguration { ShowResults = false })
|
||||
{
|
||||
this.editor = editor;
|
||||
editorState = editor.GetState();
|
||||
}
|
||||
|
||||
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
|
||||
=> new MasterGameplayClockContainer(beatmap, editorState.Time, true);
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -35,9 +44,22 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
protected override bool CheckModsAllowFailure() => false; // never fail.
|
||||
|
||||
public override void OnEntering(IScreen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
|
||||
// finish alpha transforms on entering to avoid gameplay starting in a half-hidden state.
|
||||
// the finish calls are purposefully not propagated to children to avoid messing up their state.
|
||||
FinishTransforms();
|
||||
GameplayClockContainer.FinishTransforms(false, nameof(Alpha));
|
||||
}
|
||||
|
||||
public override bool OnExiting(IScreen next)
|
||||
{
|
||||
musicController.Stop();
|
||||
|
||||
editorState.Time = GameplayClockContainer.CurrentTime;
|
||||
editor.RestoreState(editorState);
|
||||
return base.OnExiting(next);
|
||||
}
|
||||
}
|
44
osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs
Normal file
44
osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs
Normal file
@ -0,0 +1,44 @@
|
||||
// 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.Screens;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Screens.Edit.GameplayTest
|
||||
{
|
||||
public class EditorPlayerLoader : PlayerLoader
|
||||
{
|
||||
[Resolved]
|
||||
private OsuLogo osuLogo { get; set; }
|
||||
|
||||
public EditorPlayerLoader(Editor editor)
|
||||
: base(() => new EditorPlayer(editor))
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnEntering(IScreen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
|
||||
MetadataInfo.FinishTransforms(true);
|
||||
}
|
||||
|
||||
protected override void LogoArriving(OsuLogo logo, bool resuming)
|
||||
{
|
||||
// call base with resuming forcefully set to true to reduce logo movements.
|
||||
base.LogoArriving(logo, true);
|
||||
logo.FinishTransforms(true, nameof(Scale));
|
||||
}
|
||||
|
||||
protected override void ContentOut()
|
||||
{
|
||||
base.ContentOut();
|
||||
osuLogo.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override double PlayerPushDelay => 0;
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ using System;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
namespace osu.Game.Screens.Edit.GameplayTest
|
||||
{
|
||||
public class SaveBeforeGameplayTestDialog : PopupDialog
|
||||
{
|
@ -0,0 +1,123 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
/// <summary>
|
||||
/// Analogous to <see cref="SliderWithTextBoxInput{T}"/>, but supports scenarios
|
||||
/// where multiple objects with multiple different property values are selected
|
||||
/// by providing an "indeterminate state".
|
||||
/// </summary>
|
||||
public class IndeterminateSliderWithTextBoxInput<T> : CompositeDrawable, IHasCurrentValue<T?>
|
||||
where T : struct, IEquatable<T>, IComparable<T>, IConvertible
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom step value for each key press which actuates a change on this control.
|
||||
/// </summary>
|
||||
public float KeyboardStep
|
||||
{
|
||||
get => slider.KeyboardStep;
|
||||
set => slider.KeyboardStep = value;
|
||||
}
|
||||
|
||||
private readonly BindableWithCurrent<T?> current = new BindableWithCurrent<T?>();
|
||||
|
||||
public Bindable<T?> Current
|
||||
{
|
||||
get => current.Current;
|
||||
set => current.Current = value;
|
||||
}
|
||||
|
||||
private readonly SettingsSlider<T> slider;
|
||||
private readonly LabelledTextBox textbox;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IndeterminateSliderWithTextBoxInput{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="labelText">The label text for the slider and text box.</param>
|
||||
/// <param name="indeterminateValue">
|
||||
/// Bindable to use for the slider until a non-null value is set for <see cref="Current"/>.
|
||||
/// In particular, it can be used to control min/max bounds and precision in the case of <see cref="BindableNumber{T}"/>s.
|
||||
/// </param>
|
||||
public IndeterminateSliderWithTextBoxInput(LocalisableString labelText, Bindable<T> indeterminateValue)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
textbox = new LabelledTextBox
|
||||
{
|
||||
Label = labelText,
|
||||
},
|
||||
slider = new SettingsSlider<T>
|
||||
{
|
||||
TransferValueOnCommit = true,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Current = indeterminateValue
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
textbox.OnCommit += (t, isNew) =>
|
||||
{
|
||||
if (!isNew) return;
|
||||
|
||||
try
|
||||
{
|
||||
slider.Current.Parse(t.Text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TriggerChange below will restore the previous text value on failure.
|
||||
}
|
||||
|
||||
// This is run regardless of parsing success as the parsed number may not actually trigger a change
|
||||
// due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state.
|
||||
Current.TriggerChange();
|
||||
};
|
||||
slider.Current.BindValueChanged(val => Current.Value = val.NewValue);
|
||||
|
||||
Current.BindValueChanged(_ => updateState(), true);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (Current.Value is T nonNullValue)
|
||||
{
|
||||
slider.Current.Value = nonNullValue;
|
||||
|
||||
// use the value from the slider to ensure that any precision/min/max set on it via the initial indeterminate value have been applied correctly.
|
||||
decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo);
|
||||
textbox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}");
|
||||
textbox.PlaceholderText = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
textbox.Text = null;
|
||||
textbox.PlaceholderText = "(multiple)";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -107,7 +107,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ =>
|
||||
{
|
||||
RemoveFilters();
|
||||
// Don't reset frequency as the pause screen may appear post transform, causing a second frequency sweep.
|
||||
RemoveFilters(false);
|
||||
OnComplete?.Invoke();
|
||||
});
|
||||
|
||||
@ -137,15 +138,16 @@ namespace osu.Game.Screens.Play
|
||||
Content.FadeColour(Color4.Gray, duration);
|
||||
}
|
||||
|
||||
public void RemoveFilters()
|
||||
public void RemoveFilters(bool resetTrackFrequency = true)
|
||||
{
|
||||
if (resetTrackFrequency)
|
||||
track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq);
|
||||
|
||||
if (filters.Parent == null)
|
||||
return;
|
||||
|
||||
RemoveInternal(filters);
|
||||
filters.Dispose();
|
||||
|
||||
track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using ManagedBass.Fx;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
@ -35,7 +36,9 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
protected const float BACKGROUND_BLUR = 15;
|
||||
|
||||
private const double content_out_duration = 300;
|
||||
protected const double CONTENT_OUT_DURATION = 300;
|
||||
|
||||
protected virtual double PlayerPushDelay => 1800;
|
||||
|
||||
public override bool HideOverlaysOnEnter => hideOverlays;
|
||||
|
||||
@ -67,6 +70,7 @@ namespace osu.Game.Screens.Play
|
||||
private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
|
||||
|
||||
private AudioFilter lowPassFilter;
|
||||
private AudioFilter highPassFilter;
|
||||
|
||||
protected bool BackgroundBrightnessReduction
|
||||
{
|
||||
@ -168,7 +172,8 @@ namespace osu.Game.Screens.Play
|
||||
},
|
||||
idleTracker = new IdleTracker(750),
|
||||
}),
|
||||
lowPassFilter = new AudioFilter(audio.TrackMixer)
|
||||
lowPassFilter = new AudioFilter(audio.TrackMixer),
|
||||
highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass)
|
||||
};
|
||||
|
||||
if (Beatmap.Value.BeatmapInfo.EpilepsyWarning)
|
||||
@ -210,7 +215,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
// after an initial delay, start the debounced load check.
|
||||
// this will continue to execute even after resuming back on restart.
|
||||
Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0));
|
||||
Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + PlayerPushDelay, 0));
|
||||
|
||||
showMuteWarningIfNeeded();
|
||||
showBatteryWarningIfNeeded();
|
||||
@ -239,18 +244,19 @@ namespace osu.Game.Screens.Play
|
||||
Beatmap.Value.Track.Stop();
|
||||
Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment);
|
||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
||||
highPassFilter.CutoffTo(0);
|
||||
}
|
||||
|
||||
public override bool OnExiting(IScreen next)
|
||||
{
|
||||
cancelLoad();
|
||||
contentOut();
|
||||
ContentOut();
|
||||
|
||||
// If the load sequence was interrupted, the epilepsy warning may already be displayed (or in the process of being displayed).
|
||||
epilepsyWarning?.Hide();
|
||||
|
||||
// Ensure the screen doesn't expire until all the outwards fade operations have completed.
|
||||
this.Delay(content_out_duration).FadeOut();
|
||||
this.Delay(CONTENT_OUT_DURATION).FadeOut();
|
||||
|
||||
ApplyToBackground(b => b.IgnoreUserSettings.Value = true);
|
||||
|
||||
@ -266,9 +272,9 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
const double duration = 300;
|
||||
|
||||
if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.In);
|
||||
if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.OutQuint);
|
||||
|
||||
logo.ScaleTo(new Vector2(0.15f), duration, Easing.In);
|
||||
logo.ScaleTo(new Vector2(0.15f), duration, Easing.OutQuint);
|
||||
logo.FadeIn(350);
|
||||
|
||||
Scheduler.AddDelayed(() =>
|
||||
@ -352,18 +358,20 @@ namespace osu.Game.Screens.Play
|
||||
content.FadeInFromZero(400);
|
||||
content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer);
|
||||
lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint);
|
||||
highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in)
|
||||
|
||||
ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint));
|
||||
}
|
||||
|
||||
private void contentOut()
|
||||
protected virtual void ContentOut()
|
||||
{
|
||||
// Ensure the logo is no longer tracking before we scale the content
|
||||
content.StopTracking();
|
||||
|
||||
content.ScaleTo(0.7f, content_out_duration * 2, Easing.OutQuint);
|
||||
content.FadeOut(content_out_duration, Easing.OutQuint);
|
||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, content_out_duration);
|
||||
content.ScaleTo(0.7f, CONTENT_OUT_DURATION * 2, Easing.OutQuint);
|
||||
content.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint);
|
||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, CONTENT_OUT_DURATION);
|
||||
highPassFilter.CutoffTo(0, CONTENT_OUT_DURATION);
|
||||
}
|
||||
|
||||
private void pushWhenLoaded()
|
||||
@ -388,9 +396,9 @@ namespace osu.Game.Screens.Play
|
||||
// ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared).
|
||||
var consumedPlayer = consumePlayer();
|
||||
|
||||
contentOut();
|
||||
ContentOut();
|
||||
|
||||
TransformSequence<PlayerLoader> pushSequence = this.Delay(content_out_duration);
|
||||
TransformSequence<PlayerLoader> pushSequence = this.Delay(CONTENT_OUT_DURATION);
|
||||
|
||||
// only show if the warning was created (i.e. the beatmap needs it)
|
||||
// and this is not a restart of the map (the warning expires after first load).
|
||||
@ -412,7 +420,7 @@ namespace osu.Game.Screens.Play
|
||||
else
|
||||
{
|
||||
// This goes hand-in-hand with the restoration of low pass filter in contentOut().
|
||||
this.TransformBindableTo(volumeAdjustment, 0, content_out_duration, Easing.OutCubic);
|
||||
this.TransformBindableTo(volumeAdjustment, 0, CONTENT_OUT_DURATION, Easing.OutCubic);
|
||||
}
|
||||
|
||||
pushSequence.Schedule(() =>
|
||||
|
@ -270,7 +270,7 @@ namespace osu.Game.Screens.Play
|
||||
colourNormal = colours.Yellow;
|
||||
colourHover = colours.YellowDark;
|
||||
|
||||
sampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection");
|
||||
sampleConfirm = audio.Samples.Get(@"UI/submit-select");
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
|
@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
sampleHover = audio.Samples.Get("SongSelect/song-ping");
|
||||
sampleHover = audio.Samples.Get("UI/default-hover");
|
||||
}
|
||||
|
||||
public bool InsetForBorder
|
||||
|
@ -105,6 +105,8 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>();
|
||||
|
||||
private double audioFeedbackLastPlaybackTime;
|
||||
|
||||
[Resolved]
|
||||
private MusicController music { get; set; }
|
||||
|
||||
@ -435,6 +437,7 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
|
||||
// We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds.
|
||||
private BeatmapInfo beatmapInfoPrevious;
|
||||
private BeatmapInfo beatmapInfoNoDebounce;
|
||||
private RulesetInfo rulesetNoDebounce;
|
||||
|
||||
@ -477,6 +480,21 @@ namespace osu.Game.Screens.Select
|
||||
else
|
||||
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
|
||||
|
||||
if (beatmap != beatmapInfoPrevious)
|
||||
{
|
||||
if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50)
|
||||
{
|
||||
if (beatmap.BeatmapSetInfoID == beatmapInfoPrevious.BeatmapSetInfoID)
|
||||
sampleChangeDifficulty.Play();
|
||||
else
|
||||
sampleChangeBeatmap.Play();
|
||||
|
||||
audioFeedbackLastPlaybackTime = Time.Current;
|
||||
}
|
||||
|
||||
beatmapInfoPrevious = beatmap;
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
// clear pending task immediately to track any potential nested debounce operation.
|
||||
@ -508,18 +526,7 @@ namespace osu.Game.Screens.Select
|
||||
if (!EqualityComparer<BeatmapInfo>.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo))
|
||||
{
|
||||
Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\"");
|
||||
|
||||
int? lastSetID = Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID;
|
||||
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
|
||||
|
||||
if (beatmap != null)
|
||||
{
|
||||
if (beatmap.BeatmapSetInfoID == lastSetID)
|
||||
sampleChangeDifficulty.Play();
|
||||
else
|
||||
sampleChangeBeatmap.Play();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.IsCurrentScreen())
|
||||
|
Reference in New Issue
Block a user