mirror of
https://github.com/ppy/osu.git
synced 2024-09-21 22:07:25 +08:00
Merge branch 'master' into features/minimum-and-maximum-rank-display
This commit is contained in:
commit
cb3055bff6
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -67,7 +67,7 @@ jobs:
|
|||||||
- { prettyname: macOS, fullname: macos-latest }
|
- { prettyname: macOS, fullname: macos-latest }
|
||||||
- { prettyname: Linux, fullname: ubuntu-latest }
|
- { prettyname: Linux, fullname: ubuntu-latest }
|
||||||
threadingMode: ['SingleThread', 'MultiThreaded']
|
threadingMode: ['SingleThread', 'MultiThreaded']
|
||||||
timeout-minutes: 60
|
timeout-minutes: 120
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.702.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.720.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
@ -28,6 +28,7 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Rulesets.Scoring.Legacy;
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -222,6 +223,12 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
||||||
|
|
||||||
|
public override IEnumerable<SetupSection> CreateEditorSetupSections() =>
|
||||||
|
[
|
||||||
|
new DifficultySection(),
|
||||||
|
new ColoursSection(),
|
||||||
|
];
|
||||||
|
|
||||||
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
|
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
|
||||||
|
|
||||||
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -18,6 +19,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
public BindableBool ShowSpeedChanges { get; } = new BindableBool();
|
public BindableBool ShowSpeedChanges { get; } = new BindableBool();
|
||||||
|
|
||||||
|
public double? TimelineTimeRange { get; set; }
|
||||||
|
|
||||||
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
|
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
|
||||||
|
|
||||||
public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods)
|
public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods)
|
||||||
@ -38,5 +41,11 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = Vector2.One
|
Size = Vector2.One
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
TargetTimeRange = TimelineTimeRange == null || ShowSpeedChanges.Value ? ComputeScrollTime(Config.Get<int>(ManiaRulesetSetting.ScrollSpeed)) : TimelineTimeRange.Value;
|
||||||
|
base.Update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
@ -14,6 +13,7 @@ using osu.Game.Rulesets.Mania.UI;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -21,7 +21,10 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
public partial class ManiaHitObjectComposer : ScrollingHitObjectComposer<ManiaHitObject>
|
public partial class ManiaHitObjectComposer : ScrollingHitObjectComposer<ManiaHitObject>
|
||||||
{
|
{
|
||||||
private DrawableManiaEditorRuleset drawableRuleset;
|
private DrawableManiaEditorRuleset drawableRuleset = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorScreenWithTimeline? screenWithTimeline { get; set; }
|
||||||
|
|
||||||
public ManiaHitObjectComposer(Ruleset ruleset)
|
public ManiaHitObjectComposer(Ruleset ruleset)
|
||||||
: base(ruleset)
|
: base(ruleset)
|
||||||
@ -72,7 +75,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column))
|
if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column);
|
ManiaHitObject? current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column);
|
||||||
|
|
||||||
if (current == null)
|
if (current == null)
|
||||||
continue;
|
continue;
|
||||||
@ -83,5 +86,13 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList();
|
remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (screenWithTimeline?.TimelineArea.Timeline != null)
|
||||||
|
drawableRuleset.TimelineTimeRange = EditorClock.TrackLength / screenWithTimeline.TimelineArea.Timeline.CurrentZoom / 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
|||||||
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
||||||
|
|
||||||
private LabelledSliderBar<float> keyCountSlider { get; set; } = null!;
|
private LabelledSliderBar<float> keyCountSlider { get; set; } = null!;
|
||||||
|
private LabelledSwitchButton specialStyle { get; set; } = null!;
|
||||||
private LabelledSliderBar<float> healthDrainSlider { get; set; } = null!;
|
private LabelledSliderBar<float> healthDrainSlider { get; set; } = null!;
|
||||||
private LabelledSliderBar<float> overallDifficultySlider { get; set; } = null!;
|
private LabelledSliderBar<float> overallDifficultySlider { get; set; } = null!;
|
||||||
private LabelledSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
private LabelledSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
||||||
@ -49,6 +50,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
|||||||
Precision = 1,
|
Precision = 1,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
specialStyle = new LabelledSwitchButton
|
||||||
|
{
|
||||||
|
Label = "Use special (N+1) style",
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
|
||||||
|
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
||||||
|
},
|
||||||
healthDrainSlider = new LabelledSliderBar<float>
|
healthDrainSlider = new LabelledSliderBar<float>
|
||||||
{
|
{
|
||||||
Label = BeatmapsetsStrings.ShowStatsDrain,
|
Label = BeatmapsetsStrings.ShowStatsDrain,
|
||||||
@ -145,6 +153,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
|||||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||||
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value;
|
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value;
|
||||||
|
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
||||||
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||||
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||||
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||||
|
@ -1,49 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
|
||||||
using osu.Game.Screens.Edit.Setup;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Setup
|
|
||||||
{
|
|
||||||
public partial class ManiaSetupSection : RulesetSetupSection
|
|
||||||
{
|
|
||||||
private LabelledSwitchButton specialStyle;
|
|
||||||
|
|
||||||
public ManiaSetupSection()
|
|
||||||
: base(new ManiaRuleset().RulesetInfo)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
specialStyle = new LabelledSwitchButton
|
|
||||||
{
|
|
||||||
Label = "Use special (N+1) style",
|
|
||||||
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
|
|
||||||
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
specialStyle.Current.BindValueChanged(_ => updateBeatmap());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBeatmap()
|
|
||||||
{
|
|
||||||
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
|
||||||
Beatmap.SaveState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -419,9 +419,10 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
return new ManiaFilterCriteria();
|
return new ManiaFilterCriteria();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection();
|
public override IEnumerable<SetupSection> CreateEditorSetupSections() =>
|
||||||
|
[
|
||||||
public override SetupSection CreateEditorDifficultySection() => new ManiaDifficultySection();
|
new ManiaDifficultySection(),
|
||||||
|
];
|
||||||
|
|
||||||
public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList<Mod>? mods = null)
|
public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList<Mod>? mods = null)
|
||||||
=> ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods);
|
=> ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods);
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) };
|
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert), typeof(ManiaModNoRelease) };
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -27,6 +28,8 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
public override ModType Type => ModType.DifficultyReduction;
|
public override ModType Type => ModType.DifficultyReduction;
|
||||||
|
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||||
|
@ -268,11 +268,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
ApplyMaxResult();
|
ApplyMaxResult();
|
||||||
else
|
else
|
||||||
MissForcefully();
|
MissForcefully();
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that the hold note is fully judged by giving the body a judgement.
|
// Make sure that the hold note is fully judged by giving the body a judgement.
|
||||||
if (Tail.AllJudged && !Body.AllJudged)
|
if (!Body.AllJudged)
|
||||||
Body.TriggerResult(Tail.IsHit);
|
Body.TriggerResult(Tail.IsHit);
|
||||||
|
|
||||||
|
// Important that this is always called when a result is applied.
|
||||||
|
endHold();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void MissForcefully()
|
public override void MissForcefully()
|
||||||
|
@ -8,9 +8,10 @@ using osu.Framework.Audio.Track;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
@ -56,13 +57,18 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
|
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
|
||||||
private readonly BindableInt configScrollSpeed = new BindableInt();
|
private readonly BindableInt configScrollSpeed = new BindableInt();
|
||||||
private double smoothTimeRange;
|
|
||||||
|
private double currentTimeRange;
|
||||||
|
protected double TargetTimeRange;
|
||||||
|
|
||||||
// Stores the current speed adjustment active in gameplay.
|
// Stores the current speed adjustment active in gameplay.
|
||||||
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
|
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
|
||||||
|
|
||||||
private ISkinSource currentSkin = null!;
|
private ISkinSource currentSkin = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private GameHost gameHost { get; set; } = null!;
|
||||||
|
|
||||||
public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
|
public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
|
||||||
: base(ruleset, beatmap, mods)
|
: base(ruleset, beatmap, mods)
|
||||||
{
|
{
|
||||||
@ -101,9 +107,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
|
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
|
||||||
|
|
||||||
Config.BindWith(ManiaRulesetSetting.ScrollSpeed, configScrollSpeed);
|
Config.BindWith(ManiaRulesetSetting.ScrollSpeed, configScrollSpeed);
|
||||||
configScrollSpeed.BindValueChanged(speed => this.TransformTo(nameof(smoothTimeRange), ComputeScrollTime(speed.NewValue), 200, Easing.OutQuint));
|
configScrollSpeed.BindValueChanged(speed => TargetTimeRange = ComputeScrollTime(speed.NewValue));
|
||||||
|
|
||||||
TimeRange.Value = smoothTimeRange = ComputeScrollTime(configScrollSpeed.Value);
|
TimeRange.Value = TargetTimeRange = currentTimeRange = ComputeScrollTime(configScrollSpeed.Value);
|
||||||
|
|
||||||
KeyBindingInputManager.Add(new ManiaTouchInputArea());
|
KeyBindingInputManager.Add(new ManiaTouchInputArea());
|
||||||
}
|
}
|
||||||
@ -144,7 +150,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
// This scaling factor preserves the scroll speed as the scroll length varies from changes to the hit position.
|
// This scaling factor preserves the scroll speed as the scroll length varies from changes to the hit position.
|
||||||
float scale = lengthToHitPosition / length_to_default_hit_position;
|
float scale = lengthToHitPosition / length_to_default_hit_position;
|
||||||
|
|
||||||
TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale;
|
// we're intentionally using the game host's update clock here to decouple the time range tween from the gameplay clock (which can be arbitrarily paused, or even rewinding)
|
||||||
|
currentTimeRange = Interpolation.DampContinuously(currentTimeRange, TargetTimeRange, 50, gameHost.UpdateThread.Clock.ElapsedFrameTime);
|
||||||
|
TimeRange.Value = currentTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
150
osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs
Normal file
150
osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Setup
|
||||||
|
{
|
||||||
|
public partial class OsuDifficultySection : SetupSection
|
||||||
|
{
|
||||||
|
private LabelledSliderBar<float> circleSizeSlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<float> healthDrainSlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<float> approachRateSlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<float> overallDifficultySlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<double> tickRateSlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<float> stackLeniency { get; set; } = null!;
|
||||||
|
|
||||||
|
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
circleSizeSlider = new LabelledSliderBar<float>
|
||||||
|
{
|
||||||
|
Label = BeatmapsetsStrings.ShowStatsCs,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.CircleSizeDescription,
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.CircleSize)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
healthDrainSlider = new LabelledSliderBar<float>
|
||||||
|
{
|
||||||
|
Label = BeatmapsetsStrings.ShowStatsDrain,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.DrainRateDescription,
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
approachRateSlider = new LabelledSliderBar<float>
|
||||||
|
{
|
||||||
|
Label = BeatmapsetsStrings.ShowStatsAr,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.ApproachRateDescription,
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.ApproachRate)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
overallDifficultySlider = new LabelledSliderBar<float>
|
||||||
|
{
|
||||||
|
Label = BeatmapsetsStrings.ShowStatsAccuracy,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.OverallDifficultyDescription,
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
baseVelocitySlider = new LabelledSliderBar<double>
|
||||||
|
{
|
||||||
|
Label = EditorSetupStrings.BaseVelocity,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.BaseVelocityDescription,
|
||||||
|
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||||
|
{
|
||||||
|
Default = 1.4,
|
||||||
|
MinValue = 0.4,
|
||||||
|
MaxValue = 3.6,
|
||||||
|
Precision = 0.01f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tickRateSlider = new LabelledSliderBar<double>
|
||||||
|
{
|
||||||
|
Label = EditorSetupStrings.TickRate,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.TickRateDescription,
|
||||||
|
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||||
|
{
|
||||||
|
Default = 1,
|
||||||
|
MinValue = 1,
|
||||||
|
MaxValue = 4,
|
||||||
|
Precision = 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stackLeniency = new LabelledSliderBar<float>
|
||||||
|
{
|
||||||
|
Label = "Stack Leniency",
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.",
|
||||||
|
Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency)
|
||||||
|
{
|
||||||
|
Default = 0.7f,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 1,
|
||||||
|
Precision = 0.1f
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var item in Children.OfType<LabelledSliderBar<float>>())
|
||||||
|
item.Current.ValueChanged += _ => updateValues();
|
||||||
|
|
||||||
|
foreach (var item in Children.OfType<LabelledSliderBar<double>>())
|
||||||
|
item.Current.ValueChanged += _ => updateValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateValues()
|
||||||
|
{
|
||||||
|
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||||
|
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||||
|
Beatmap.Difficulty.CircleSize = circleSizeSlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.ApproachRate = approachRateSlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value;
|
||||||
|
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
||||||
|
|
||||||
|
Beatmap.UpdateAllHitObjects();
|
||||||
|
Beatmap.SaveState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,56 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
|
||||||
using osu.Game.Screens.Edit.Setup;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Setup
|
|
||||||
{
|
|
||||||
public partial class OsuSetupSection : RulesetSetupSection
|
|
||||||
{
|
|
||||||
private LabelledSliderBar<float> stackLeniency;
|
|
||||||
|
|
||||||
public OsuSetupSection()
|
|
||||||
: base(new OsuRuleset().RulesetInfo)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
stackLeniency = new LabelledSliderBar<float>
|
|
||||||
{
|
|
||||||
Label = "Stack Leniency",
|
|
||||||
Description = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.",
|
|
||||||
Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency)
|
|
||||||
{
|
|
||||||
Default = 0.7f,
|
|
||||||
MinValue = 0,
|
|
||||||
MaxValue = 1,
|
|
||||||
Precision = 0.1f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
stackLeniency.Current.BindValueChanged(_ => updateBeatmap());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBeatmap()
|
|
||||||
{
|
|
||||||
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
|
||||||
Beatmap.UpdateAllHitObjects();
|
|
||||||
Beatmap.SaveState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -336,7 +336,11 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override RulesetSetupSection CreateEditorSetupSection() => new OsuSetupSection();
|
public override IEnumerable<SetupSection> CreateEditorSetupSections() =>
|
||||||
|
[
|
||||||
|
new OsuDifficultySection(),
|
||||||
|
new ColoursSection(),
|
||||||
|
];
|
||||||
|
|
||||||
/// <seealso cref="OsuHitObject.ApplyDefaultsToSelf"/>
|
/// <seealso cref="OsuHitObject.ApplyDefaultsToSelf"/>
|
||||||
/// <seealso cref="OsuHitWindows"/>
|
/// <seealso cref="OsuHitWindows"/>
|
||||||
|
@ -116,7 +116,9 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
|
scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
|
||||||
|
|
||||||
ResumeRequested?.Invoke();
|
// When resuming with a button, we do not want the osu! input manager to see this button press and include it in the score.
|
||||||
|
// To ensure that this works correctly, schedule the resume operation one frame forward, since the resume operation enables the input manager to see input events.
|
||||||
|
Schedule(() => ResumeRequested?.Invoke());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
105
osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs
Normal file
105
osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit.Setup
|
||||||
|
{
|
||||||
|
public partial class TaikoDifficultySection : SetupSection
|
||||||
|
{
|
||||||
|
private LabelledSliderBar<float> healthDrainSlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<float> overallDifficultySlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
||||||
|
private LabelledSliderBar<double> tickRateSlider { get; set; } = null!;
|
||||||
|
|
||||||
|
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
healthDrainSlider = new LabelledSliderBar<float>
|
||||||
|
{
|
||||||
|
Label = BeatmapsetsStrings.ShowStatsDrain,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.DrainRateDescription,
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
overallDifficultySlider = new LabelledSliderBar<float>
|
||||||
|
{
|
||||||
|
Label = BeatmapsetsStrings.ShowStatsAccuracy,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.OverallDifficultyDescription,
|
||||||
|
Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty)
|
||||||
|
{
|
||||||
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
baseVelocitySlider = new LabelledSliderBar<double>
|
||||||
|
{
|
||||||
|
Label = EditorSetupStrings.BaseVelocity,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.BaseVelocityDescription,
|
||||||
|
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||||
|
{
|
||||||
|
Default = 1.4,
|
||||||
|
MinValue = 0.4,
|
||||||
|
MaxValue = 3.6,
|
||||||
|
Precision = 0.01f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tickRateSlider = new LabelledSliderBar<double>
|
||||||
|
{
|
||||||
|
Label = EditorSetupStrings.TickRate,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.TickRateDescription,
|
||||||
|
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||||
|
{
|
||||||
|
Default = 1,
|
||||||
|
MinValue = 1,
|
||||||
|
MaxValue = 4,
|
||||||
|
Precision = 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var item in Children.OfType<LabelledSliderBar<float>>())
|
||||||
|
item.Current.ValueChanged += _ => updateValues();
|
||||||
|
|
||||||
|
foreach (var item in Children.OfType<LabelledSliderBar<double>>())
|
||||||
|
item.Current.ValueChanged += _ => updateValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateValues()
|
||||||
|
{
|
||||||
|
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||||
|
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||||
|
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value;
|
||||||
|
|
||||||
|
Beatmap.UpdateAllHitObjects();
|
||||||
|
Beatmap.SaveState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,8 @@ using osu.Game.Rulesets.Configuration;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Scoring.Legacy;
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Rulesets.Taiko.Configuration;
|
using osu.Game.Rulesets.Taiko.Configuration;
|
||||||
|
using osu.Game.Rulesets.Taiko.Edit.Setup;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko
|
namespace osu.Game.Rulesets.Taiko
|
||||||
{
|
{
|
||||||
@ -188,6 +190,11 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
|
|
||||||
public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this);
|
public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this);
|
||||||
|
|
||||||
|
public override IEnumerable<SetupSection> CreateEditorSetupSections() =>
|
||||||
|
[
|
||||||
|
new TaikoDifficultySection(),
|
||||||
|
];
|
||||||
|
|
||||||
public override IBeatmapVerifier CreateBeatmapVerifier() => new TaikoBeatmapVerifier();
|
public override IBeatmapVerifier CreateBeatmapVerifier() => new TaikoBeatmapVerifier();
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap);
|
||||||
|
@ -157,8 +157,9 @@ namespace osu.Game.Tests.Database
|
|||||||
AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False);
|
AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(30000002)]
|
||||||
public void TestScoreUpgradeFailed()
|
[TestCase(30000013)]
|
||||||
|
public void TestScoreUpgradeFailed(int scoreVersion)
|
||||||
{
|
{
|
||||||
ScoreInfo scoreInfo = null!;
|
ScoreInfo scoreInfo = null!;
|
||||||
|
|
||||||
@ -172,16 +173,18 @@ namespace osu.Game.Tests.Database
|
|||||||
Ruleset = r.All<RulesetInfo>().First(),
|
Ruleset = r.All<RulesetInfo>().First(),
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
TotalScoreVersion = 30000002,
|
TotalScoreVersion = scoreVersion,
|
||||||
IsLegacyScore = true,
|
IsLegacyScore = true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor()));
|
TestBackgroundDataStoreProcessor processor = null!;
|
||||||
|
AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor()));
|
||||||
|
AddUntilStep("Wait for completion", () => processor.Completed);
|
||||||
|
|
||||||
AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True);
|
AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True);
|
||||||
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002));
|
AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find<ScoreInfo>(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(scoreVersion));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -168,12 +168,12 @@ namespace osu.Game.Tests.Database
|
|||||||
Assert.That(importAfterUpdate, Is.Not.Null);
|
Assert.That(importAfterUpdate, Is.Not.Null);
|
||||||
Debug.Assert(importAfterUpdate != null);
|
Debug.Assert(importAfterUpdate != null);
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
// should only contain the modified beatmap (others purged).
|
// should only contain the modified beatmap (others purged).
|
||||||
Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(1));
|
Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(1));
|
||||||
Assert.That(importAfterUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps));
|
Assert.That(importAfterUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps));
|
||||||
|
|
||||||
realm.Run(r => r.Refresh());
|
|
||||||
|
|
||||||
checkCount<BeatmapInfo>(realm, count_beatmaps + 1);
|
checkCount<BeatmapInfo>(realm, count_beatmaps + 1);
|
||||||
checkCount<BeatmapMetadata>(realm, count_beatmaps + 1);
|
checkCount<BeatmapMetadata>(realm, count_beatmaps + 1);
|
||||||
|
|
||||||
@ -479,6 +479,7 @@ namespace osu.Game.Tests.Database
|
|||||||
using var rulesets = new RealmRulesetStore(realm, storage);
|
using var rulesets = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
using var __ = getBeatmapArchive(out string pathOriginal);
|
using var __ = getBeatmapArchive(out string pathOriginal);
|
||||||
|
|
||||||
using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory =>
|
using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory =>
|
||||||
{
|
{
|
||||||
// arbitrary beatmap removal
|
// arbitrary beatmap removal
|
||||||
@ -496,7 +497,7 @@ namespace osu.Game.Tests.Database
|
|||||||
Debug.Assert(importAfterUpdate != null);
|
Debug.Assert(importAfterUpdate != null);
|
||||||
|
|
||||||
Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID));
|
Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID));
|
||||||
Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(importAfterUpdate.Value.DateAdded));
|
Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(importAfterUpdate.Value.DateAdded).Within(TimeSpan.FromSeconds(1)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||||
|
{
|
||||||
|
public partial class TestSceneDailyChallengeLeaderboard : OsuTestScene
|
||||||
|
{
|
||||||
|
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicBehaviour()
|
||||||
|
{
|
||||||
|
DailyChallengeLeaderboard leaderboard = null!;
|
||||||
|
|
||||||
|
AddStep("set up response without user best", () =>
|
||||||
|
{
|
||||||
|
dummyAPI.HandleRequest = req =>
|
||||||
|
{
|
||||||
|
if (req is IndexPlaylistScoresRequest indexRequest)
|
||||||
|
{
|
||||||
|
indexRequest.TriggerSuccess(createResponse(50, false));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo))
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(0.8f),
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set up response with user best", () =>
|
||||||
|
{
|
||||||
|
dummyAPI.HandleRequest = req =>
|
||||||
|
{
|
||||||
|
if (req is IndexPlaylistScoresRequest indexRequest)
|
||||||
|
{
|
||||||
|
indexRequest.TriggerSuccess(createResponse(50, true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
AddStep("force refetch", () => leaderboard.RefetchScores());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLoadingBehaviour()
|
||||||
|
{
|
||||||
|
IndexPlaylistScoresRequest pendingRequest = null!;
|
||||||
|
DailyChallengeLeaderboard leaderboard = null!;
|
||||||
|
|
||||||
|
AddStep("set up requests handler", () =>
|
||||||
|
{
|
||||||
|
dummyAPI.HandleRequest = req =>
|
||||||
|
{
|
||||||
|
if (req is IndexPlaylistScoresRequest indexRequest)
|
||||||
|
{
|
||||||
|
pendingRequest = indexRequest;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo))
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(0.8f),
|
||||||
|
});
|
||||||
|
AddStep("complete load", () => pendingRequest.TriggerSuccess(createResponse(3, true)));
|
||||||
|
AddStep("force refetch", () => leaderboard.RefetchScores());
|
||||||
|
AddStep("complete load", () => pendingRequest.TriggerSuccess(createResponse(4, true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IndexedMultiplayerScores createResponse(int scoreCount, bool returnUserBest)
|
||||||
|
{
|
||||||
|
var result = new IndexedMultiplayerScores();
|
||||||
|
|
||||||
|
for (int i = 0; i < scoreCount; ++i)
|
||||||
|
{
|
||||||
|
result.Scores.Add(new MultiplayerScore
|
||||||
|
{
|
||||||
|
ID = i,
|
||||||
|
Accuracy = 1 - (float)i / (2 * scoreCount),
|
||||||
|
Position = i + 1,
|
||||||
|
EndedAt = DateTimeOffset.Now,
|
||||||
|
Passed = true,
|
||||||
|
Rank = (ScoreRank)RNG.Next((int)ScoreRank.D, (int)ScoreRank.XH),
|
||||||
|
MaxCombo = 1000 - i,
|
||||||
|
TotalScore = (long)(1_000_000 * (1 - (float)i / (2 * scoreCount))),
|
||||||
|
User = new APIUser { Username = $"user {i}" },
|
||||||
|
Statistics = new Dictionary<HitResult, int>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnUserBest)
|
||||||
|
{
|
||||||
|
result.UserScore = new MultiplayerScore
|
||||||
|
{
|
||||||
|
ID = 99999,
|
||||||
|
Accuracy = 0.91,
|
||||||
|
Position = 4,
|
||||||
|
EndedAt = DateTimeOffset.Now,
|
||||||
|
Passed = true,
|
||||||
|
Rank = ScoreRank.A,
|
||||||
|
MaxCombo = 100,
|
||||||
|
TotalScore = 800000,
|
||||||
|
User = dummyAPI.LocalUser.Value,
|
||||||
|
Statistics = new Dictionary<HitResult, int>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddStep("set preview time to -1", () => EditorBeatmap.PreviewTime.Value = -1);
|
AddStep("set preview time to -1", () => EditorBeatmap.PreviewTime.Value = -1);
|
||||||
AddAssert("preview time line should not show", () => !Editor.ChildrenOfType<PreviewTimePart>().Single().Children.Any());
|
AddAssert("preview time line should not show", () => !Editor.ChildrenOfType<PreviewTimePart>().Single().Children.Any());
|
||||||
AddStep("set preview time to 1000", () => EditorBeatmap.PreviewTime.Value = 1000);
|
AddStep("set preview time to 1000", () => EditorBeatmap.PreviewTime.Value = 1000);
|
||||||
AddAssert("preview time line should show", () => Editor.ChildrenOfType<PreviewTimePart>().Single().Children.Single().Alpha == 1);
|
AddAssert("preview time line should show", () => Editor.ChildrenOfType<PreviewTimePart>().Single().Children.Single().Alpha, () => Is.GreaterThan(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
Scheduler.AddDelayed(applyMiss, 500 + 30);
|
Scheduler.AddDelayed(applyMiss, 500 + 30);
|
||||||
});
|
});
|
||||||
|
AddUntilStep("wait for sequence", () => !Scheduler.HasPendingTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -120,6 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
AddUntilStep("wait for sequence", () => !Scheduler.HasPendingTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value);
|
AddUntilStep("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value);
|
||||||
|
|
||||||
// because we are in frame stable context, it's quite likely that not all samples are "played" at this point.
|
// because we are in frame stable context, it's quite likely that not all samples are "played" at this point.
|
||||||
// the important thing is that at least one started, and that sample has since stopped.
|
// the important thing is that at least one started, and that sample has since stopped.
|
||||||
|
267
osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs
Normal file
267
osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mania;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public partial class TestScenePauseInputHandling : PlayerTestScene
|
||||||
|
{
|
||||||
|
private Ruleset currentRuleset = new OsuRuleset();
|
||||||
|
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => currentRuleset;
|
||||||
|
|
||||||
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private AudioManager audioManager { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) =>
|
||||||
|
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
foreach (var key in InputManager.CurrentState.Keyboard.Keys)
|
||||||
|
InputManager.ReleaseKey(key);
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(Content);
|
||||||
|
LocalConfig.SetValue(OsuSetting.KeyOverlay, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOsuInputNotReceivedWhilePaused()
|
||||||
|
{
|
||||||
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
|
loadPlayer(() => new OsuRuleset());
|
||||||
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
||||||
|
checkKey(() => counter, 0, false);
|
||||||
|
|
||||||
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||||
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
|
AddStep("resume", () => Player.Resume());
|
||||||
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
|
|
||||||
|
// Z key was released before pause, resuming should not trigger it
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||||
|
checkKey(() => counter, 2, true);
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
checkKey(() => counter, 2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManiaInputNotReceivedWhilePaused()
|
||||||
|
{
|
||||||
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
|
loadPlayer(() => new ManiaRuleset());
|
||||||
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
||||||
|
checkKey(() => counter, 0, false);
|
||||||
|
|
||||||
|
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||||
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
|
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
|
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
|
AddStep("resume", () => Player.Resume());
|
||||||
|
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
|
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||||
|
checkKey(() => counter, 2, true);
|
||||||
|
|
||||||
|
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||||
|
checkKey(() => counter, 2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOsuPreviouslyHeldInputReleaseOnResume()
|
||||||
|
{
|
||||||
|
KeyCounter counterZ = null!;
|
||||||
|
KeyCounter counterX = null!;
|
||||||
|
|
||||||
|
loadPlayer(() => new OsuRuleset());
|
||||||
|
AddStep("get key counter Z", () => counterZ = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
||||||
|
AddStep("get key counter X", () => counterX = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.RightButton));
|
||||||
|
|
||||||
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
|
||||||
|
AddStep("resume", () => Player.Resume());
|
||||||
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
|
AddStep("press and release Z", () => InputManager.Key(Key.Z));
|
||||||
|
checkKey(() => counterZ, 1, false);
|
||||||
|
|
||||||
|
AddStep("press X", () => InputManager.PressKey(Key.X));
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||||
|
checkKey(() => counterX, 1, true);
|
||||||
|
|
||||||
|
AddStep("resume", () => Player.Resume());
|
||||||
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
|
checkKey(() => counterZ, 1, false);
|
||||||
|
checkKey(() => counterX, 1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManiaPreviouslyHeldInputReleaseOnResume()
|
||||||
|
{
|
||||||
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
|
loadPlayer(() => new ManiaRuleset());
|
||||||
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
||||||
|
|
||||||
|
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
|
||||||
|
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||||
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
|
AddStep("resume", () => Player.Resume());
|
||||||
|
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOsuHeldInputRemainHeldAfterResume()
|
||||||
|
{
|
||||||
|
KeyCounter counterZ = null!;
|
||||||
|
KeyCounter counterX = null!;
|
||||||
|
|
||||||
|
loadPlayer(() => new OsuRuleset());
|
||||||
|
AddStep("get key counter Z", () => counterZ = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
||||||
|
AddStep("get key counter X", () => counterX = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.RightButton));
|
||||||
|
|
||||||
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
|
||||||
|
AddStep("resume", () => Player.Resume());
|
||||||
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
|
checkKey(() => counterZ, 1, true);
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
checkKey(() => counterZ, 1, false);
|
||||||
|
|
||||||
|
AddStep("press X", () => InputManager.PressKey(Key.X));
|
||||||
|
checkKey(() => counterX, 1, true);
|
||||||
|
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
|
||||||
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||||
|
AddStep("press X", () => InputManager.PressKey(Key.X));
|
||||||
|
|
||||||
|
AddStep("resume", () => Player.Resume());
|
||||||
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
|
checkKey(() => counterZ, 1, false);
|
||||||
|
checkKey(() => counterX, 1, true);
|
||||||
|
|
||||||
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||||
|
checkKey(() => counterZ, 1, false);
|
||||||
|
checkKey(() => counterX, 1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManiaHeldInputRemainHeldAfterResume()
|
||||||
|
{
|
||||||
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
|
loadPlayer(() => new ManiaRuleset());
|
||||||
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
||||||
|
|
||||||
|
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||||
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
|
||||||
|
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||||
|
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||||
|
|
||||||
|
AddStep("resume", () => Player.Resume());
|
||||||
|
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||||
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
|
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadPlayer(Func<Ruleset> createRuleset)
|
||||||
|
{
|
||||||
|
AddStep("set ruleset", () => currentRuleset = createRuleset());
|
||||||
|
AddStep("load player", LoadPlayer);
|
||||||
|
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||||
|
AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType<SkinComponentsContainer>().All(s => s.ComponentsLoaded));
|
||||||
|
|
||||||
|
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(20000));
|
||||||
|
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(20000).Within(500));
|
||||||
|
AddAssert("not in break", () => !Player.IsBreakTime.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkKey(Func<KeyCounter> counter, int count, bool active)
|
||||||
|
{
|
||||||
|
AddAssert($"key count = {count}", () => counter().CountPresses.Value, () => Is.EqualTo(count));
|
||||||
|
AddAssert($"key active = {active}", () => counter().IsActive.Value, () => Is.EqualTo(active));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PausePlayer();
|
||||||
|
|
||||||
|
private partial class PausePlayer : TestPlayer
|
||||||
|
{
|
||||||
|
protected override double PauseCooldownDuration => 0;
|
||||||
|
|
||||||
|
public PausePlayer()
|
||||||
|
: base(allowPause: true, showResults: false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -34,7 +32,7 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMusicNavigationActions()
|
public void TestMusicNavigationActions()
|
||||||
{
|
{
|
||||||
Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null;
|
Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null!;
|
||||||
|
|
||||||
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
|
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
|
||||||
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5);
|
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5);
|
||||||
@ -62,14 +60,22 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000);
|
AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000);
|
||||||
|
|
||||||
AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
|
AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
|
||||||
AddAssert("track changed to previous", () =>
|
AddUntilStep("track changed to previous", () =>
|
||||||
trackChangeQueue.Count == 1 &&
|
trackChangeQueue.Count == 1 &&
|
||||||
trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev);
|
trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev);
|
||||||
|
|
||||||
AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext));
|
AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext));
|
||||||
AddAssert("track changed to next", () =>
|
AddUntilStep("track changed to next", () =>
|
||||||
trackChangeQueue.Count == 1 &&
|
trackChangeQueue.Count == 1 &&
|
||||||
trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next);
|
trackChangeQueue.Peek().changeDirection == TrackChangeDirection.Next);
|
||||||
|
|
||||||
|
AddUntilStep("wait until track switches", () => trackChangeQueue.Count == 2);
|
||||||
|
|
||||||
|
AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext));
|
||||||
|
AddUntilStep("track changed to next", () =>
|
||||||
|
trackChangeQueue.Count == 3 &&
|
||||||
|
trackChangeQueue.Peek().changeDirection == TrackChangeDirection.Next);
|
||||||
|
AddAssert("track actually changed", () => !trackChangeQueue.First().working.BeatmapInfo.Equals(trackChangeQueue.Last().working.BeatmapInfo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -319,16 +320,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for load", () => playlist.ChildrenOfType<DrawableLinkCompiler>().Any() && playlist.ChildrenOfType<BeatmapCardThumbnail>().First().DrawWidth > 0);
|
AddUntilStep("wait for load", () => playlist.ChildrenOfType<DrawableLinkCompiler>().Any() && playlist.ChildrenOfType<BeatmapCardThumbnail>().First().DrawWidth > 0);
|
||||||
AddStep("move mouse to first item title", () =>
|
|
||||||
{
|
AddStep("move mouse to first item title", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<LinkFlowContainer>().First().ChildrenOfType<SpriteText>().First()));
|
||||||
var drawQuad = playlist.ChildrenOfType<LinkFlowContainer>().First().ScreenSpaceDrawQuad;
|
|
||||||
var location = (drawQuad.TopLeft + drawQuad.BottomLeft) / 2 + new Vector2(drawQuad.Width * 0.2f, 0);
|
|
||||||
InputManager.MoveMouseTo(location);
|
|
||||||
});
|
|
||||||
AddAssert("first item title not hovered", () => playlist.ChildrenOfType<DrawableLinkCompiler>().First().IsHovered, () => Is.False);
|
AddAssert("first item title not hovered", () => playlist.ChildrenOfType<DrawableLinkCompiler>().First().IsHovered, () => Is.False);
|
||||||
AddStep("click left mouse", () => InputManager.Click(MouseButton.Left));
|
|
||||||
|
AddStep("click title", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(playlist.ChildrenOfType<LinkFlowContainer>().First().ChildrenOfType<SpriteText>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
AddUntilStep("first item selected", () => playlist.ChildrenOfType<DrawableRoomPlaylistItem>().First().IsSelectedItem, () => Is.True);
|
AddUntilStep("first item selected", () => playlist.ChildrenOfType<DrawableRoomPlaylistItem>().First().IsSelectedItem, () => Is.True);
|
||||||
// implies being clickable.
|
|
||||||
AddUntilStep("first item title hovered", () => playlist.ChildrenOfType<DrawableLinkCompiler>().First().IsHovered, () => Is.True);
|
AddUntilStep("first item title hovered", () => playlist.ChildrenOfType<DrawableLinkCompiler>().First().IsHovered, () => Is.True);
|
||||||
|
|
||||||
AddStep("move mouse to second item results button", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<GrayButton>().ElementAt(5)));
|
AddStep("move mouse to second item results button", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<GrayButton>().ElementAt(5)));
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -16,6 +17,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
using osu.Game.Screens.OnlinePlay;
|
using osu.Game.Screens.OnlinePlay;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -26,6 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
private FreeModSelectOverlay freeModSelectOverlay;
|
private FreeModSelectOverlay freeModSelectOverlay;
|
||||||
private FooterButtonFreeMods footerButtonFreeMods;
|
private FooterButtonFreeMods footerButtonFreeMods;
|
||||||
|
private ScreenFooter footer;
|
||||||
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -127,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
createFreeModSelect();
|
createFreeModSelect();
|
||||||
|
|
||||||
AddAssert("overlay select all button enabled", () => freeModSelectOverlay.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
|
AddAssert("overlay select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
|
||||||
AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "off"));
|
AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "off"));
|
||||||
|
|
||||||
AddStep("click footer select all button", () =>
|
AddStep("click footer select all button", () =>
|
||||||
@ -150,7 +153,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private void createFreeModSelect()
|
private void createFreeModSelect()
|
||||||
{
|
{
|
||||||
AddStep("create free mod select screen", () => Children = new Drawable[]
|
AddStep("create free mod select screen", () => Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
freeModSelectOverlay = new FreeModSelectOverlay
|
freeModSelectOverlay = new FreeModSelectOverlay
|
||||||
{
|
{
|
||||||
@ -160,9 +166,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
|
Y = -ScreenFooter.HEIGHT,
|
||||||
Current = { BindTarget = freeModSelectOverlay.SelectedMods },
|
Current = { BindTarget = freeModSelectOverlay.SelectedMods },
|
||||||
},
|
},
|
||||||
|
footer = new ScreenFooter(),
|
||||||
|
},
|
||||||
|
CachedDependencies = new (Type, object)[] { (typeof(ScreenFooter), footer) },
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("all column content loaded",
|
AddUntilStep("all column content loaded",
|
||||||
() => freeModSelectOverlay.ChildrenOfType<ModColumn>().Any()
|
() => freeModSelectOverlay.ChildrenOfType<ModColumn>().Any()
|
||||||
&& freeModSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
&& freeModSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
|
||||||
|
@ -312,14 +312,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("wait for join", () => RoomJoined);
|
AddUntilStep("wait for join", () => RoomJoined);
|
||||||
|
|
||||||
ClickButtonWhenEnabled<UserModSelectButton>();
|
ClickButtonWhenEnabled<UserModSelectButton>();
|
||||||
AddAssert("mod select shows unranked", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().Ranked.Value == false);
|
AddAssert("mod select shows unranked", () => this.ChildrenOfType<RankingInformationDisplay>().Single().Ranked.Value == false);
|
||||||
AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
|
AddAssert("score multiplier = 1.20", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
|
||||||
|
|
||||||
AddStep("select flashlight", () => screen.UserModsSelectOverlay.ChildrenOfType<ModPanel>().Single(m => m.Mod is ModFlashlight).TriggerClick());
|
AddStep("select flashlight", () => screen.UserModsSelectOverlay.ChildrenOfType<ModPanel>().Single(m => m.Mod is ModFlashlight).TriggerClick());
|
||||||
AddAssert("score multiplier = 1.35", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01));
|
AddAssert("score multiplier = 1.35", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01));
|
||||||
|
|
||||||
AddStep("change flashlight setting", () => ((OsuModFlashlight)screen.UserModsSelectOverlay.SelectedMods.Value.Single()).FollowDelay.Value = 1200);
|
AddStep("change flashlight setting", () => ((OsuModFlashlight)screen.UserModsSelectOverlay.SelectedMods.Value.Single()).FollowDelay.Value = 1200);
|
||||||
AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
|
AddAssert("score multiplier = 1.20", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
|
private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
@ -13,6 +18,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Mania;
|
using osu.Game.Rulesets.Mania;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
@ -29,24 +35,102 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
{
|
{
|
||||||
public partial class TestSceneBeatmapEditorNavigation : OsuGameTestScene
|
public partial class TestSceneBeatmapEditorNavigation : OsuGameTestScene
|
||||||
{
|
{
|
||||||
|
private BeatmapSetInfo beatmapSet = null!;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExternalEditingNoChange()
|
||||||
|
{
|
||||||
|
string difficultyName = null!;
|
||||||
|
|
||||||
|
prepareBeatmap();
|
||||||
|
openEditor();
|
||||||
|
|
||||||
|
AddStep("store difficulty name", () => difficultyName = getEditor().Beatmap.Value.BeatmapInfo.DifficultyName);
|
||||||
|
|
||||||
|
AddStep("open file menu", () => getEditor().ChildrenOfType<Menu.DrawableMenuItem>().Single(m => m.Item.Text.Value.ToString() == "File").TriggerClick());
|
||||||
|
AddStep("click external edit", () => getEditor().ChildrenOfType<Menu.DrawableMenuItem>().Single(m => m.Item.Text.Value.ToString() == "Edit externally").TriggerClick());
|
||||||
|
|
||||||
|
AddUntilStep("wait for external edit screen", () => Game.ScreenStack.CurrentScreen is ExternalEditScreen externalEditScreen && externalEditScreen.IsLoaded);
|
||||||
|
|
||||||
|
AddUntilStep("wait for button ready", () => ((ExternalEditScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType<DangerousRoundedButton>().FirstOrDefault()?.Enabled.Value == true);
|
||||||
|
|
||||||
|
AddStep("finish external edit", () => ((ExternalEditScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType<DangerousRoundedButton>().First().TriggerClick());
|
||||||
|
|
||||||
|
AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||||
|
|
||||||
|
AddAssert("beatmapset didn't change", () => getEditor().Beatmap.Value.BeatmapSetInfo, () => Is.EqualTo(beatmapSet));
|
||||||
|
AddAssert("difficulty didn't change", () => getEditor().Beatmap.Value.BeatmapInfo.DifficultyName, () => Is.EqualTo(difficultyName));
|
||||||
|
AddAssert("old beatmapset not deleted", () => Game.BeatmapManager.QueryBeatmapSet(s => s.ID == beatmapSet.ID), () => Is.Not.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExternalEditingWithChange()
|
||||||
|
{
|
||||||
|
string difficultyName = null!;
|
||||||
|
|
||||||
|
prepareBeatmap();
|
||||||
|
openEditor();
|
||||||
|
|
||||||
|
AddStep("store difficulty name", () => difficultyName = getEditor().Beatmap.Value.BeatmapInfo.DifficultyName);
|
||||||
|
|
||||||
|
AddStep("open file menu", () => getEditor().ChildrenOfType<Menu.DrawableMenuItem>().Single(m => m.Item.Text.Value.ToString() == "File").TriggerClick());
|
||||||
|
AddStep("click external edit", () => getEditor().ChildrenOfType<Menu.DrawableMenuItem>().Single(m => m.Item.Text.Value.ToString() == "Edit externally").TriggerClick());
|
||||||
|
|
||||||
|
AddUntilStep("wait for external edit screen", () => Game.ScreenStack.CurrentScreen is ExternalEditScreen externalEditScreen && externalEditScreen.IsLoaded);
|
||||||
|
|
||||||
|
AddUntilStep("wait for button ready", () => ((ExternalEditScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType<DangerousRoundedButton>().FirstOrDefault()?.Enabled.Value == true);
|
||||||
|
|
||||||
|
AddStep("add file externally", () =>
|
||||||
|
{
|
||||||
|
var op = ((ExternalEditScreen)Game.ScreenStack.CurrentScreen).EditOperation!;
|
||||||
|
File.WriteAllText(Path.Combine(op.MountedPath, "test.txt"), "test");
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("finish external edit", () => ((ExternalEditScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType<DangerousRoundedButton>().First().TriggerClick());
|
||||||
|
|
||||||
|
AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||||
|
|
||||||
|
AddAssert("beatmapset changed", () => getEditor().Beatmap.Value.BeatmapSetInfo, () => Is.Not.EqualTo(beatmapSet));
|
||||||
|
AddAssert("beatmapset is locally modified", () => getEditor().Beatmap.Value.BeatmapSetInfo.Status, () => Is.EqualTo(BeatmapOnlineStatus.LocallyModified));
|
||||||
|
AddAssert("all difficulties are locally modified", () => getEditor().Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Status == BeatmapOnlineStatus.LocallyModified));
|
||||||
|
AddAssert("difficulty didn't change", () => getEditor().Beatmap.Value.BeatmapInfo.DifficultyName, () => Is.EqualTo(difficultyName));
|
||||||
|
AddAssert("old beatmapset deleted", () => Game.BeatmapManager.QueryBeatmapSet(s => s.ID == beatmapSet.ID), () => Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSaveThenDeleteActuallyDeletesAtSongSelect()
|
||||||
|
{
|
||||||
|
prepareBeatmap();
|
||||||
|
openEditor();
|
||||||
|
makeMetadataChange();
|
||||||
|
|
||||||
|
AddAssert("save", () => getEditor().Save());
|
||||||
|
|
||||||
|
AddStep("delete beatmap", () => Game.BeatmapManager.Delete(beatmapSet));
|
||||||
|
|
||||||
|
AddStep("exit", () => getEditor().Exit());
|
||||||
|
|
||||||
|
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||||
|
&& songSelect.Beatmap.Value is DummyWorkingBeatmap);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestChangeMetadataExitWhileTextboxFocusedPromptsSave()
|
public void TestChangeMetadataExitWhileTextboxFocusedPromptsSave()
|
||||||
{
|
{
|
||||||
BeatmapSetInfo beatmapSet = null!;
|
|
||||||
|
|
||||||
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
|
|
||||||
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
|
|
||||||
|
|
||||||
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
|
||||||
AddUntilStep("wait for song select",
|
|
||||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
|
||||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
|
||||||
&& songSelect.IsLoaded);
|
|
||||||
AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
||||||
|
|
||||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
prepareBeatmap();
|
||||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
openEditor();
|
||||||
|
|
||||||
|
makeMetadataChange(commit: false);
|
||||||
|
|
||||||
|
AddStep("exit", () => getEditor().Exit());
|
||||||
|
|
||||||
|
AddUntilStep("save dialog displayed", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault()?.CurrentDialog is PromptForSaveDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeMetadataChange(bool commit = true)
|
||||||
|
{
|
||||||
AddStep("change to song setup", () => InputManager.Key(Key.F4));
|
AddStep("change to song setup", () => InputManager.Key(Key.F4));
|
||||||
|
|
||||||
TextBox textbox = null!;
|
TextBox textbox = null!;
|
||||||
@ -77,24 +161,14 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
InputManager.Keys(PlatformAction.Paste);
|
InputManager.Keys(PlatformAction.Paste);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("exit", () => Game.ChildrenOfType<Editor>().Single().Exit());
|
if (commit) AddStep("commit", () => InputManager.Key(Key.Enter));
|
||||||
|
|
||||||
AddUntilStep("save dialog displayed", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault()?.CurrentDialog is PromptForSaveDialog);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
|
public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
|
||||||
{
|
{
|
||||||
BeatmapSetInfo beatmapSet = null!;
|
prepareBeatmap();
|
||||||
|
|
||||||
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
|
|
||||||
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
|
|
||||||
|
|
||||||
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
|
||||||
AddUntilStep("wait for song select",
|
|
||||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
|
||||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
|
||||||
&& songSelect.IsLoaded);
|
|
||||||
AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
||||||
|
|
||||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||||
@ -134,12 +208,16 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
AddStep("Set current beatmap to default", () => Game.Beatmap.SetDefault());
|
AddStep("Set current beatmap to default", () => Game.Beatmap.SetDefault());
|
||||||
|
|
||||||
AddStep("Push editor loader", () => Game.ScreenStack.Push(new EditorLoader()));
|
DelayedLoadEditorLoader loader = null!;
|
||||||
|
AddStep("Push editor loader", () => Game.ScreenStack.Push(loader = new DelayedLoadEditorLoader()));
|
||||||
AddUntilStep("Wait for loader current", () => Game.ScreenStack.CurrentScreen is EditorLoader);
|
AddUntilStep("Wait for loader current", () => Game.ScreenStack.CurrentScreen is EditorLoader);
|
||||||
|
AddUntilStep("wait for editor load start", () => loader.Editor != null);
|
||||||
AddStep("Close editor while loading", () => Game.ScreenStack.CurrentScreen.Exit());
|
AddStep("Close editor while loading", () => Game.ScreenStack.CurrentScreen.Exit());
|
||||||
|
AddStep("allow editor load", () => loader.AllowLoad.Set());
|
||||||
|
AddUntilStep("wait for editor ready", () => loader.Editor!.LoadState >= LoadState.Ready);
|
||||||
|
|
||||||
AddUntilStep("Wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
AddUntilStep("Wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||||
AddAssert("Check no new beatmaps were made", () => allBeatmapSets().SequenceEqual(beatmapSets));
|
AddAssert("Check no new beatmaps were made", allBeatmapSets, () => Is.EquivalentTo(beatmapSets));
|
||||||
|
|
||||||
BeatmapSetInfo[] allBeatmapSets() => Game.Realm.Run(realm => realm.All<BeatmapSetInfo>().Where(x => !x.DeletePending).ToArray());
|
BeatmapSetInfo[] allBeatmapSets() => Game.Realm.Run(realm => realm.All<BeatmapSetInfo>().Where(x => !x.DeletePending).ToArray());
|
||||||
}
|
}
|
||||||
@ -147,19 +225,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExitEditorWithoutSelection()
|
public void TestExitEditorWithoutSelection()
|
||||||
{
|
{
|
||||||
BeatmapSetInfo beatmapSet = null!;
|
prepareBeatmap();
|
||||||
|
openEditor();
|
||||||
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
|
|
||||||
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
|
|
||||||
|
|
||||||
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
|
||||||
AddUntilStep("wait for song select",
|
|
||||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
|
||||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
|
||||||
&& songSelect.IsLoaded);
|
|
||||||
|
|
||||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
|
||||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
|
||||||
|
|
||||||
AddStep("escape once", () => InputManager.Key(Key.Escape));
|
AddStep("escape once", () => InputManager.Key(Key.Escape));
|
||||||
|
|
||||||
@ -169,19 +236,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExitEditorWithSelection()
|
public void TestExitEditorWithSelection()
|
||||||
{
|
{
|
||||||
BeatmapSetInfo beatmapSet = null!;
|
prepareBeatmap();
|
||||||
|
openEditor();
|
||||||
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
|
|
||||||
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
|
|
||||||
|
|
||||||
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
|
||||||
AddUntilStep("wait for song select",
|
|
||||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
|
||||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
|
||||||
&& songSelect.IsLoaded);
|
|
||||||
|
|
||||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
|
||||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
|
||||||
|
|
||||||
AddStep("make selection", () =>
|
AddStep("make selection", () =>
|
||||||
{
|
{
|
||||||
@ -203,19 +259,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestLastTimestampRememberedOnExit()
|
public void TestLastTimestampRememberedOnExit()
|
||||||
{
|
{
|
||||||
BeatmapSetInfo beatmapSet = null!;
|
prepareBeatmap();
|
||||||
|
openEditor();
|
||||||
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
|
|
||||||
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
|
|
||||||
|
|
||||||
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
|
||||||
AddUntilStep("wait for song select",
|
|
||||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
|
||||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
|
||||||
&& songSelect.IsLoaded);
|
|
||||||
|
|
||||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
|
||||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
|
||||||
|
|
||||||
AddStep("seek to arbitrary time", () => getEditor().ChildrenOfType<EditorClock>().First().Seek(1234));
|
AddStep("seek to arbitrary time", () => getEditor().ChildrenOfType<EditorClock>().First().Seek(1234));
|
||||||
AddUntilStep("time is correct", () => getEditor().ChildrenOfType<EditorClock>().First().CurrentTime, () => Is.EqualTo(1234));
|
AddUntilStep("time is correct", () => getEditor().ChildrenOfType<EditorClock>().First().CurrentTime, () => Is.EqualTo(1234));
|
||||||
@ -223,32 +268,21 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddStep("exit editor", () => InputManager.Key(Key.Escape));
|
AddStep("exit editor", () => InputManager.Key(Key.Escape));
|
||||||
AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor);
|
AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor);
|
||||||
|
|
||||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit());
|
openEditor();
|
||||||
|
|
||||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
|
||||||
AddUntilStep("time is correct", () => getEditor().ChildrenOfType<EditorClock>().First().CurrentTime, () => Is.EqualTo(1234));
|
AddUntilStep("time is correct", () => getEditor().ChildrenOfType<EditorClock>().First().CurrentTime, () => Is.EqualTo(1234));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAttemptGlobalMusicOperationFromEditor()
|
public void TestAttemptGlobalMusicOperationFromEditor()
|
||||||
{
|
{
|
||||||
BeatmapSetInfo beatmapSet = null!;
|
prepareBeatmap();
|
||||||
|
|
||||||
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
|
|
||||||
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
|
|
||||||
|
|
||||||
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
|
||||||
AddUntilStep("wait for song select",
|
|
||||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
|
||||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
|
||||||
&& songSelect.IsLoaded);
|
|
||||||
|
|
||||||
AddUntilStep("wait for music playing", () => Game.MusicController.IsPlaying);
|
AddUntilStep("wait for music playing", () => Game.MusicController.IsPlaying);
|
||||||
AddStep("user request stop", () => Game.MusicController.Stop(requestedByUser: true));
|
AddStep("user request stop", () => Game.MusicController.Stop(requestedByUser: true));
|
||||||
AddUntilStep("wait for music stopped", () => !Game.MusicController.IsPlaying);
|
AddUntilStep("wait for music stopped", () => !Game.MusicController.IsPlaying);
|
||||||
|
|
||||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
openEditor();
|
||||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
|
||||||
|
|
||||||
AddUntilStep("music still stopped", () => !Game.MusicController.IsPlaying);
|
AddUntilStep("music still stopped", () => !Game.MusicController.IsPlaying);
|
||||||
AddStep("user request play", () => Game.MusicController.Play(requestedByUser: true));
|
AddStep("user request play", () => Game.MusicController.Play(requestedByUser: true));
|
||||||
@ -266,20 +300,10 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[TestCase(SortMode.Difficulty)]
|
[TestCase(SortMode.Difficulty)]
|
||||||
public void TestSelectionRetainedOnExit(SortMode sortMode)
|
public void TestSelectionRetainedOnExit(SortMode sortMode)
|
||||||
{
|
{
|
||||||
BeatmapSetInfo beatmapSet = null!;
|
|
||||||
|
|
||||||
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
|
|
||||||
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
|
|
||||||
|
|
||||||
AddStep($"set sort mode to {sortMode}", () => Game.LocalConfig.SetValue(OsuSetting.SongSelectSortingMode, sortMode));
|
AddStep($"set sort mode to {sortMode}", () => Game.LocalConfig.SetValue(OsuSetting.SongSelectSortingMode, sortMode));
|
||||||
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
|
||||||
AddUntilStep("wait for song select",
|
|
||||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
|
||||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
|
||||||
&& songSelect.IsLoaded);
|
|
||||||
|
|
||||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
prepareBeatmap();
|
||||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
openEditor();
|
||||||
|
|
||||||
AddStep("exit editor", () => InputManager.Key(Key.Escape));
|
AddStep("exit editor", () => InputManager.Key(Key.Escape));
|
||||||
AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor);
|
AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor);
|
||||||
@ -296,6 +320,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
AddStep("open editor", () => Game.ChildrenOfType<ButtonSystem>().Single().OnEditBeatmap?.Invoke());
|
AddStep("open editor", () => Game.ChildrenOfType<ButtonSystem>().Single().OnEditBeatmap?.Invoke());
|
||||||
AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded);
|
AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded);
|
||||||
|
|
||||||
AddStep("click on file", () =>
|
AddStep("click on file", () =>
|
||||||
{
|
{
|
||||||
var item = getEditor().ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value.ToString() == "File");
|
var item = getEditor().ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value.ToString() == "File");
|
||||||
@ -318,8 +343,54 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddAssert("editor beatmap uses catch ruleset", () => getEditorBeatmap().BeatmapInfo.Ruleset.ShortName == "fruits");
|
AddAssert("editor beatmap uses catch ruleset", () => getEditorBeatmap().BeatmapInfo.Ruleset.ShortName == "fruits");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void prepareBeatmap()
|
||||||
|
{
|
||||||
|
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
|
||||||
|
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
|
||||||
|
|
||||||
|
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
||||||
|
AddUntilStep("wait for song select",
|
||||||
|
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
||||||
|
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||||
|
&& songSelect.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openEditor()
|
||||||
|
{
|
||||||
|
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||||
|
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||||
|
}
|
||||||
|
|
||||||
private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType<EditorBeatmap>().Single();
|
private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType<EditorBeatmap>().Single();
|
||||||
|
|
||||||
private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen;
|
private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen;
|
||||||
|
|
||||||
|
private partial class DelayedLoadEditorLoader : EditorLoader
|
||||||
|
{
|
||||||
|
public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim();
|
||||||
|
public Editor? Editor { get; private set; }
|
||||||
|
|
||||||
|
protected override Editor CreateEditor() => Editor = new DelayedLoadEditor(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class DelayedLoadEditor : Editor
|
||||||
|
{
|
||||||
|
private readonly DelayedLoadEditorLoader loader;
|
||||||
|
|
||||||
|
public DelayedLoadEditor(DelayedLoadEditorLoader loader)
|
||||||
|
: base(loader)
|
||||||
|
{
|
||||||
|
this.loader = loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
|
{
|
||||||
|
// Importantly, this occurs before base.load().
|
||||||
|
if (!loader.AllowLoad.Wait(TimeSpan.FromSeconds(10)))
|
||||||
|
throw new TimeoutException();
|
||||||
|
|
||||||
|
return base.CreateChildDependencies(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ using osu.Game.Screens.Select.Options;
|
|||||||
using osu.Game.Tests.Beatmaps.IO;
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
using SharpCompress;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Navigation
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
{
|
{
|
||||||
@ -837,21 +838,25 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExitWithOperationInProgress()
|
public void TestExitWithOperationInProgress()
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault() != null);
|
int x = 0;
|
||||||
|
|
||||||
ProgressNotification progressNotification = null!;
|
AddUntilStep("wait for dialog overlay", () =>
|
||||||
|
|
||||||
AddStep("start ongoing operation", () =>
|
|
||||||
{
|
{
|
||||||
progressNotification = new ProgressNotification
|
x = 0;
|
||||||
{
|
return Game.ChildrenOfType<DialogOverlay>().SingleOrDefault() != null;
|
||||||
Text = "Something is still running",
|
|
||||||
Progress = 0.5f,
|
|
||||||
State = ProgressNotificationState.Active,
|
|
||||||
};
|
|
||||||
Game.Notifications.Post(progressNotification);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddRepeatStep("start ongoing operation", () =>
|
||||||
|
{
|
||||||
|
Game.Notifications.Post(new ProgressNotification
|
||||||
|
{
|
||||||
|
Text = $"Something is still running #{++x}",
|
||||||
|
Progress = 0.5f,
|
||||||
|
State = ProgressNotificationState.Active,
|
||||||
|
});
|
||||||
|
}, 15);
|
||||||
|
|
||||||
|
AddAssert("all notifications = 15", () => Game.Notifications.AllNotifications.Count(), () => Is.EqualTo(15));
|
||||||
AddStep("Hold escape", () => InputManager.PressKey(Key.Escape));
|
AddStep("Hold escape", () => InputManager.PressKey(Key.Escape));
|
||||||
AddUntilStep("confirmation dialog shown", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog is ConfirmExitDialog);
|
AddUntilStep("confirmation dialog shown", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog is ConfirmExitDialog);
|
||||||
AddStep("Release escape", () => InputManager.ReleaseKey(Key.Escape));
|
AddStep("Release escape", () => InputManager.ReleaseKey(Key.Escape));
|
||||||
@ -861,8 +866,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
AddStep("complete operation", () =>
|
AddStep("complete operation", () =>
|
||||||
{
|
{
|
||||||
progressNotification.Progress = 100;
|
this.ChildrenOfType<ProgressNotification>().ForEach(n =>
|
||||||
progressNotification.State = ProgressNotificationState.Completed;
|
{
|
||||||
|
n.Progress = 100;
|
||||||
|
n.State = ProgressNotificationState.Completed;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("Hold escape", () => InputManager.PressKey(Key.Escape));
|
AddStep("Hold escape", () => InputManager.PressKey(Key.Escape));
|
||||||
@ -878,7 +886,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddStep("set hold delay to 0", () => Game.LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0));
|
AddStep("set hold delay to 0", () => Game.LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0));
|
||||||
AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault() != null);
|
AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault() != null);
|
||||||
|
|
||||||
AddStep("start ongoing operation", () =>
|
AddRepeatStep("start ongoing operation", () =>
|
||||||
{
|
{
|
||||||
Game.Notifications.Post(new ProgressNotification
|
Game.Notifications.Post(new ProgressNotification
|
||||||
{
|
{
|
||||||
@ -886,7 +894,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
Progress = 0.5f,
|
Progress = 0.5f,
|
||||||
State = ProgressNotificationState.Active,
|
State = ProgressNotificationState.Active,
|
||||||
});
|
});
|
||||||
});
|
}, 15);
|
||||||
|
|
||||||
AddRepeatStep("attempt force exit", () => Game.ScreenStack.CurrentScreen.Exit(), 2);
|
AddRepeatStep("attempt force exit", () => Game.ScreenStack.CurrentScreen.Exit(), 2);
|
||||||
AddUntilStep("stopped at exit confirm", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog is ConfirmExitDialog);
|
AddUntilStep("stopped at exit confirm", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog is ConfirmExitDialog);
|
||||||
@ -944,6 +952,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestTouchScreenDetectionAtSongSelect()
|
public void TestTouchScreenDetectionAtSongSelect()
|
||||||
{
|
{
|
||||||
|
AddUntilStep("wait for settings", () => Game.Settings.IsLoaded);
|
||||||
|
|
||||||
AddStep("touch logo", () =>
|
AddStep("touch logo", () =>
|
||||||
{
|
{
|
||||||
var button = Game.ChildrenOfType<OsuLogo>().Single();
|
var button = Game.ChildrenOfType<OsuLogo>().Single();
|
||||||
|
@ -157,6 +157,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
setUpCommentsResponse(getExampleComments());
|
setUpCommentsResponse(getExampleComments());
|
||||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||||
|
AddUntilStep("comments shown", () => commentsContainer.ChildrenOfType<DrawableComment>().Any());
|
||||||
|
|
||||||
setUpPostResponse();
|
setUpPostResponse();
|
||||||
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
|
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
|
||||||
@ -175,6 +176,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
setUpCommentsResponse(getExampleComments());
|
setUpCommentsResponse(getExampleComments());
|
||||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||||
|
AddUntilStep("comments shown", () => commentsContainer.ChildrenOfType<DrawableComment>().Any());
|
||||||
|
|
||||||
setUpPostResponse(true);
|
setUpPostResponse(true);
|
||||||
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
|
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
|
||||||
|
@ -413,7 +413,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class TestResultsScreen : PlaylistsResultsScreen
|
private partial class TestResultsScreen : PlaylistItemUserResultsScreen
|
||||||
{
|
{
|
||||||
public new LoadingSpinner LeftSpinner => base.LeftSpinner;
|
public new LoadingSpinner LeftSpinner => base.LeftSpinner;
|
||||||
public new LoadingSpinner CentreSpinner => base.CentreSpinner;
|
public new LoadingSpinner CentreSpinner => base.CentreSpinner;
|
||||||
|
@ -50,8 +50,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[Test]
|
||||||
public void Setup() => Schedule(() =>
|
public void TestSheared()
|
||||||
|
{
|
||||||
|
AddStep("create content", () =>
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -70,8 +72,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
foreach (var scoreInfo in getTestScores())
|
foreach (var scoreInfo in getTestScores())
|
||||||
{
|
{
|
||||||
fillFlow.Add(new LeaderboardScoreV2(scoreInfo, scoreInfo.Position, scoreInfo.User.Id == 2)
|
fillFlow.Add(new LeaderboardScoreV2(scoreInfo)
|
||||||
{
|
{
|
||||||
|
Rank = scoreInfo.Position,
|
||||||
|
IsPersonalBest = scoreInfo.User.Id == 2,
|
||||||
Shear = Vector2.Zero,
|
Shear = Vector2.Zero,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -79,6 +83,40 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
foreach (var score in fillFlow.Children)
|
foreach (var score in fillFlow.Children)
|
||||||
score.Show();
|
score.Show();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNonSheared()
|
||||||
|
{
|
||||||
|
AddStep("create content", () =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
fillFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Width = relativeWidth,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(0f, 2f),
|
||||||
|
},
|
||||||
|
drawWidthText = new OsuSpriteText(),
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var scoreInfo in getTestScores())
|
||||||
|
{
|
||||||
|
fillFlow.Add(new LeaderboardScoreV2(scoreInfo)
|
||||||
|
{
|
||||||
|
Rank = scoreInfo.Position,
|
||||||
|
IsPersonalBest = scoreInfo.User.Id == 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var score in fillFlow.Children)
|
||||||
|
score.Show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
|
@ -181,12 +181,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
Stack.Padding = new MarginPadding { Bottom = screenScreenFooter.DrawHeight - screenScreenFooter.Y };
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateFooter(IScreen? _, IScreen? newScreen)
|
private void updateFooter(IScreen? _, IScreen? newScreen)
|
||||||
{
|
{
|
||||||
if (newScreen is IOsuScreen osuScreen && osuScreen.ShowFooter)
|
if (newScreen is IOsuScreen osuScreen && osuScreen.ShowFooter)
|
||||||
|
@ -11,6 +11,8 @@ using Moq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
@ -20,6 +22,7 @@ using osu.Game.Overlays;
|
|||||||
using osu.Game.Overlays.FirstRunSetup;
|
using osu.Game.Overlays.FirstRunSetup;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -28,6 +31,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
public partial class TestSceneFirstRunSetupOverlay : OsuManualInputManagerTestScene
|
public partial class TestSceneFirstRunSetupOverlay : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private FirstRunSetupOverlay overlay;
|
private FirstRunSetupOverlay overlay;
|
||||||
|
private ScreenFooter footer;
|
||||||
|
|
||||||
private readonly Mock<TestPerformerFromScreenRunner> performer = new Mock<TestPerformerFromScreenRunner>();
|
private readonly Mock<TestPerformerFromScreenRunner> performer = new Mock<TestPerformerFromScreenRunner>();
|
||||||
|
|
||||||
@ -60,19 +64,16 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
.Callback((Notification n) => lastNotification = n);
|
.Callback((Notification n) => lastNotification = n);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("add overlay", () =>
|
createOverlay();
|
||||||
{
|
|
||||||
Child = overlay = new FirstRunSetupOverlay
|
AddStep("show overlay", () => overlay.Show());
|
||||||
{
|
|
||||||
State = { Value = Visibility.Visible }
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasic()
|
public void TestBasic()
|
||||||
{
|
{
|
||||||
AddAssert("overlay visible", () => overlay.State.Value == Visibility.Visible);
|
AddAssert("overlay visible", () => overlay.State.Value == Visibility.Visible);
|
||||||
|
AddAssert("footer visible", () => footer.State.Value == Visibility.Visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -82,16 +83,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
AddUntilStep("step through", () =>
|
AddUntilStep("step through", () =>
|
||||||
{
|
{
|
||||||
if (overlay.CurrentScreen?.IsLoaded != false) overlay.NextButton.TriggerClick();
|
if (overlay.CurrentScreen?.IsLoaded != false) overlay.NextButton.AsNonNull().TriggerClick();
|
||||||
return overlay.State.Value == Visibility.Hidden;
|
return overlay.State.Value == Visibility.Hidden;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("first run false", () => !LocalConfig.Get<bool>(OsuSetting.ShowFirstRunSetup));
|
AddAssert("first run false", () => !LocalConfig.Get<bool>(OsuSetting.ShowFirstRunSetup));
|
||||||
|
|
||||||
AddStep("add overlay", () =>
|
createOverlay();
|
||||||
{
|
|
||||||
Child = overlay = new FirstRunSetupOverlay();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddWaitStep("wait some", 5);
|
AddWaitStep("wait some", 5);
|
||||||
|
|
||||||
@ -109,7 +107,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
if (keyboard)
|
if (keyboard)
|
||||||
InputManager.Key(Key.Enter);
|
InputManager.Key(Key.Enter);
|
||||||
else
|
else
|
||||||
overlay.NextButton.TriggerClick();
|
overlay.NextButton.AsNonNull().TriggerClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
return overlay.State.Value == Visibility.Hidden;
|
return overlay.State.Value == Visibility.Hidden;
|
||||||
@ -128,11 +126,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
public void TestBackButton(bool keyboard)
|
public void TestBackButton(bool keyboard)
|
||||||
{
|
{
|
||||||
AddAssert("back button disabled", () => !overlay.BackButton.Enabled.Value);
|
|
||||||
|
|
||||||
AddUntilStep("step to last", () =>
|
AddUntilStep("step to last", () =>
|
||||||
{
|
{
|
||||||
var nextButton = overlay.NextButton;
|
var nextButton = overlay.NextButton.AsNonNull();
|
||||||
|
|
||||||
if (overlay.CurrentScreen?.IsLoaded != false)
|
if (overlay.CurrentScreen?.IsLoaded != false)
|
||||||
nextButton.TriggerClick();
|
nextButton.TriggerClick();
|
||||||
@ -142,24 +138,29 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
AddUntilStep("step back to start", () =>
|
AddUntilStep("step back to start", () =>
|
||||||
{
|
{
|
||||||
if (overlay.CurrentScreen?.IsLoaded != false)
|
if (overlay.CurrentScreen?.IsLoaded != false && !(overlay.CurrentScreen is ScreenWelcome))
|
||||||
{
|
{
|
||||||
if (keyboard)
|
if (keyboard)
|
||||||
InputManager.Key(Key.Escape);
|
InputManager.Key(Key.Escape);
|
||||||
else
|
else
|
||||||
overlay.BackButton.TriggerClick();
|
footer.BackButton.TriggerClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
return overlay.CurrentScreen is ScreenWelcome;
|
return overlay.CurrentScreen is ScreenWelcome;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("back button disabled", () => !overlay.BackButton.Enabled.Value);
|
AddAssert("overlay not dismissed", () => overlay.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
if (keyboard)
|
if (keyboard)
|
||||||
{
|
{
|
||||||
AddStep("exit via keyboard", () => InputManager.Key(Key.Escape));
|
AddStep("exit via keyboard", () => InputManager.Key(Key.Escape));
|
||||||
AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden);
|
AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddStep("press back button", () => footer.BackButton.TriggerClick());
|
||||||
|
AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -185,7 +186,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestResumeViaNotification()
|
public void TestResumeViaNotification()
|
||||||
{
|
{
|
||||||
AddStep("step to next", () => overlay.NextButton.TriggerClick());
|
AddStep("step to next", () => overlay.NextButton.AsNonNull().TriggerClick());
|
||||||
|
|
||||||
AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenUIScale);
|
AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenUIScale);
|
||||||
|
|
||||||
@ -200,6 +201,27 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale);
|
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createOverlay()
|
||||||
|
{
|
||||||
|
AddStep("add overlay", () =>
|
||||||
|
{
|
||||||
|
var receptor = new ScreenFooter.BackReceptor();
|
||||||
|
footer = new ScreenFooter(receptor);
|
||||||
|
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
receptor,
|
||||||
|
overlay = new FirstRunSetupOverlay(),
|
||||||
|
footer,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// interface mocks break hot reload, mocking this stub implementation instead works around it.
|
// interface mocks break hot reload, mocking this stub implementation instead works around it.
|
||||||
// see: https://github.com/moq/moq4/issues/1252
|
// see: https://github.com/moq/moq4/issues/1252
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
|
@ -24,6 +24,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Mods;
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
using osu.Game.Tests.Mods;
|
using osu.Game.Tests.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -94,12 +95,28 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
private void createScreen()
|
private void createScreen()
|
||||||
{
|
{
|
||||||
AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay
|
AddStep("create screen", () =>
|
||||||
|
{
|
||||||
|
var receptor = new ScreenFooter.BackReceptor();
|
||||||
|
var footer = new ScreenFooter(receptor);
|
||||||
|
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
receptor,
|
||||||
|
modSelectOverlay = new TestModSelectOverlay
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
State = { Value = Visibility.Visible },
|
State = { Value = Visibility.Visible },
|
||||||
Beatmap = Beatmap.Value,
|
Beatmap = { Value = Beatmap.Value },
|
||||||
SelectedMods = { BindTarget = SelectedMods }
|
SelectedMods = { BindTarget = SelectedMods },
|
||||||
|
},
|
||||||
|
footer,
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
waitForColumnLoad();
|
waitForColumnLoad();
|
||||||
}
|
}
|
||||||
@ -120,7 +137,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("mod multiplier correct", () =>
|
AddAssert("mod multiplier correct", () =>
|
||||||
{
|
{
|
||||||
double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier);
|
double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier);
|
||||||
return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value);
|
return Precision.AlmostEquals(multiplier, this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value);
|
||||||
});
|
});
|
||||||
assertCustomisationToggleState(disabled: false, active: false);
|
assertCustomisationToggleState(disabled: false, active: false);
|
||||||
AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType<ISettingsItem>().Any());
|
AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType<ISettingsItem>().Any());
|
||||||
@ -135,7 +152,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("mod multiplier correct", () =>
|
AddAssert("mod multiplier correct", () =>
|
||||||
{
|
{
|
||||||
double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier);
|
double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier);
|
||||||
return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value);
|
return Precision.AlmostEquals(multiplier, this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value);
|
||||||
});
|
});
|
||||||
assertCustomisationToggleState(disabled: false, active: false);
|
assertCustomisationToggleState(disabled: false, active: false);
|
||||||
AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType<ISettingsItem>().Any());
|
AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType<ISettingsItem>().Any());
|
||||||
@ -757,7 +774,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
AddStep("click back button", () =>
|
AddStep("click back button", () =>
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(modSelectOverlay.BackButton);
|
InputManager.MoveMouseTo(this.ChildrenOfType<ScreenBackButton>().Single());
|
||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden);
|
AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden);
|
||||||
@ -885,7 +902,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
AddAssert("difficulty multiplier display shows correct value",
|
AddAssert("difficulty multiplier display shows correct value",
|
||||||
() => modSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON));
|
() => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON));
|
||||||
|
|
||||||
// this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation,
|
// this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation,
|
||||||
// it is instrumental in the reproduction of the failure scenario that this test is supposed to cover.
|
// it is instrumental in the reproduction of the failure scenario that this test is supposed to cover.
|
||||||
@ -895,7 +912,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single()
|
AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single()
|
||||||
.ChildrenOfType<RevertToDefaultButton<double>>().Single().TriggerClick());
|
.ChildrenOfType<RevertToDefaultButton<double>>().Single().TriggerClick());
|
||||||
AddUntilStep("difficulty multiplier display shows correct value",
|
AddUntilStep("difficulty multiplier display shows correct value",
|
||||||
() => modSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON));
|
() => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -1015,8 +1032,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
private partial class TestModSelectOverlay : UserModSelectOverlay
|
private partial class TestModSelectOverlay : UserModSelectOverlay
|
||||||
{
|
{
|
||||||
protected override bool ShowPresets => true;
|
protected override bool ShowPresets => true;
|
||||||
|
|
||||||
public new ShearedButton BackButton => base.BackButton;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestUnimplementedMod : Mod
|
private class TestUnimplementedMod : Mod
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
@ -61,6 +62,22 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
clearTextboxes(numberBoxes);
|
clearTextboxes(numberBoxes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSelectAllOnFocus()
|
||||||
|
{
|
||||||
|
AddStep("create themed content", () => CreateThemedContent(OverlayColourScheme.Red));
|
||||||
|
|
||||||
|
AddStep("enter numbers", () => numberBoxes.ForEach(numberBox => numberBox.Text = "987654321"));
|
||||||
|
|
||||||
|
AddAssert("nothing selected", () => string.IsNullOrEmpty(numberBoxes.First().SelectedText));
|
||||||
|
AddStep("click on a number box", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(numberBoxes.First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddAssert("text selected", () => numberBoxes.First().SelectedText == "987654321");
|
||||||
|
}
|
||||||
|
|
||||||
private void clearTextboxes(IEnumerable<OsuTextBox> textBoxes) => AddStep("clear textbox", () => textBoxes.ForEach(textBox => textBox.Text = null));
|
private void clearTextboxes(IEnumerable<OsuTextBox> textBoxes) => AddStep("clear textbox", () => textBoxes.ForEach(textBox => textBox.Text = null));
|
||||||
private void expectedValue(IEnumerable<OsuTextBox> textBoxes, string value) => AddAssert("expected textbox value", () => textBoxes.All(textBox => textBox.Text == value));
|
private void expectedValue(IEnumerable<OsuTextBox> textBoxes, string value) => AddAssert("expected textbox value", () => textBoxes.All(textBox => textBox.Text == value));
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,16 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Screens.Footer;
|
using osu.Game.Screens.Footer;
|
||||||
@ -15,31 +21,37 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene
|
public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
|
private DependencyProvidingContainer contentContainer = null!;
|
||||||
private ScreenFooter screenFooter = null!;
|
private ScreenFooter screenFooter = null!;
|
||||||
private TestModSelectOverlay overlay = null!;
|
private TestModSelectOverlay modOverlay = null!;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
|
screenFooter = new ScreenFooter();
|
||||||
|
|
||||||
|
Child = contentContainer = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new (Type, object)[]
|
||||||
|
{
|
||||||
|
(typeof(ScreenFooter), screenFooter)
|
||||||
|
},
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
overlay = new TestModSelectOverlay
|
modOverlay = new TestModSelectOverlay(),
|
||||||
{
|
|
||||||
Padding = new MarginPadding
|
|
||||||
{
|
|
||||||
Bottom = ScreenFooter.HEIGHT
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new PopoverContainer
|
new PopoverContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = screenFooter = new ScreenFooter(),
|
Depth = float.MinValue,
|
||||||
|
Child = screenFooter,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
screenFooter.SetButtons(new ScreenFooterButton[]
|
screenFooter.SetButtons(new ScreenFooterButton[]
|
||||||
{
|
{
|
||||||
new ScreenFooterButtonMods(overlay) { Current = SelectedMods },
|
new ScreenFooterButtonMods(modOverlay) { Current = SelectedMods },
|
||||||
new ScreenFooterButtonRandom(),
|
new ScreenFooterButtonRandom(),
|
||||||
new ScreenFooterButtonOptions(),
|
new ScreenFooterButtonOptions(),
|
||||||
});
|
});
|
||||||
@ -82,13 +94,171 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExternalOverlayContent()
|
||||||
|
{
|
||||||
|
TestShearedOverlayContainer externalOverlay = null!;
|
||||||
|
|
||||||
|
AddStep("add overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
|
||||||
|
AddStep("set buttons", () => screenFooter.SetButtons(new[]
|
||||||
|
{
|
||||||
|
new ScreenFooterButton(externalOverlay)
|
||||||
|
{
|
||||||
|
AccentColour = Dependencies.Get<OsuColour>().Orange1,
|
||||||
|
Icon = FontAwesome.Solid.Toolbox,
|
||||||
|
Text = "One",
|
||||||
|
},
|
||||||
|
new ScreenFooterButton { Text = "Two", Action = () => { } },
|
||||||
|
new ScreenFooterButton { Text = "Three", Action = () => { } },
|
||||||
|
}));
|
||||||
|
AddWaitStep("wait for transition", 3);
|
||||||
|
|
||||||
|
AddStep("show overlay", () => externalOverlay.Show());
|
||||||
|
AddAssert("content displayed in footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().Single().IsPresent);
|
||||||
|
AddUntilStep("other buttons hidden", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
|
||||||
|
|
||||||
|
AddStep("hide overlay", () => externalOverlay.Hide());
|
||||||
|
AddUntilStep("content hidden from footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
|
||||||
|
AddUntilStep("other buttons returned", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTemporarilyShowFooter()
|
||||||
|
{
|
||||||
|
TestShearedOverlayContainer externalOverlay = null!;
|
||||||
|
|
||||||
|
AddStep("hide footer", () => screenFooter.Hide());
|
||||||
|
AddStep("remove buttons", () => screenFooter.SetButtons(Array.Empty<ScreenFooterButton>()));
|
||||||
|
|
||||||
|
AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
|
||||||
|
AddStep("show external overlay", () => externalOverlay.Show());
|
||||||
|
AddAssert("footer shown", () => screenFooter.State.Value == Visibility.Visible);
|
||||||
|
AddAssert("content displayed in footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().Single().IsPresent);
|
||||||
|
|
||||||
|
AddStep("hide external overlay", () => externalOverlay.Hide());
|
||||||
|
AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden);
|
||||||
|
AddUntilStep("content hidden from footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
|
||||||
|
|
||||||
|
AddStep("show footer", () => screenFooter.Show());
|
||||||
|
AddAssert("content still hidden from footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
|
||||||
|
|
||||||
|
AddStep("show external overlay", () => externalOverlay.Show());
|
||||||
|
AddAssert("footer still visible", () => screenFooter.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
|
AddStep("hide external overlay", () => externalOverlay.Hide());
|
||||||
|
AddAssert("footer still visible", () => screenFooter.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
|
AddStep("hide footer", () => screenFooter.Hide());
|
||||||
|
AddStep("show external overlay", () => externalOverlay.Show());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackButton()
|
||||||
|
{
|
||||||
|
TestShearedOverlayContainer externalOverlay = null!;
|
||||||
|
|
||||||
|
AddStep("hide footer", () => screenFooter.Hide());
|
||||||
|
AddStep("remove buttons", () => screenFooter.SetButtons(Array.Empty<ScreenFooterButton>()));
|
||||||
|
|
||||||
|
AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
|
||||||
|
AddStep("show external overlay", () => externalOverlay.Show());
|
||||||
|
AddAssert("footer shown", () => screenFooter.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
|
AddStep("press back", () => this.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
|
||||||
|
AddAssert("overlay hidden", () => externalOverlay.State.Value == Visibility.Hidden);
|
||||||
|
AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden);
|
||||||
|
|
||||||
|
AddStep("show external overlay", () => externalOverlay.Show());
|
||||||
|
AddStep("set block count", () => externalOverlay.BackButtonCount = 1);
|
||||||
|
AddStep("press back", () => this.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
|
||||||
|
AddAssert("overlay still visible", () => externalOverlay.State.Value == Visibility.Visible);
|
||||||
|
AddAssert("footer still shown", () => screenFooter.State.Value == Visibility.Visible);
|
||||||
|
AddStep("press back again", () => this.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
|
||||||
|
AddAssert("overlay hidden", () => externalOverlay.State.Value == Visibility.Hidden);
|
||||||
|
AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLoadOverlayAfterFooterIsDisplayed()
|
||||||
|
{
|
||||||
|
TestShearedOverlayContainer externalOverlay = null!;
|
||||||
|
|
||||||
|
AddStep("show mod overlay", () => modOverlay.Show());
|
||||||
|
AddUntilStep("mod footer content shown", () => this.ChildrenOfType<ModSelectFooterContent>().SingleOrDefault()?.IsPresent, () => Is.True);
|
||||||
|
|
||||||
|
AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
|
||||||
|
AddUntilStep("wait for load", () => externalOverlay.IsLoaded);
|
||||||
|
AddAssert("mod footer content still shown", () => this.ChildrenOfType<ModSelectFooterContent>().SingleOrDefault()?.IsPresent, () => Is.True);
|
||||||
|
AddAssert("external overlay content not shown", () => this.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
|
||||||
|
|
||||||
|
AddStep("hide mod overlay", () => modOverlay.Hide());
|
||||||
|
AddUntilStep("mod footer content hidden", () => this.ChildrenOfType<ModSelectFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
|
||||||
|
AddAssert("external overlay content still not shown", () => this.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
|
||||||
|
}
|
||||||
|
|
||||||
private partial class TestModSelectOverlay : UserModSelectOverlay
|
private partial class TestModSelectOverlay : UserModSelectOverlay
|
||||||
{
|
{
|
||||||
protected override bool ShowPresets => true;
|
protected override bool ShowPresets => true;
|
||||||
|
}
|
||||||
|
|
||||||
public TestModSelectOverlay()
|
private partial class TestShearedOverlayContainer : ShearedOverlayContainer
|
||||||
: base(OverlayColourScheme.Aquamarine)
|
|
||||||
{
|
{
|
||||||
|
public TestShearedOverlayContainer()
|
||||||
|
: base(OverlayColourScheme.Orange)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Header.Title = "Test overlay";
|
||||||
|
Header.Description = "An overlay that is made purely for testing purposes.";
|
||||||
|
}
|
||||||
|
|
||||||
|
public int BackButtonCount;
|
||||||
|
|
||||||
|
public override bool OnBackButton()
|
||||||
|
{
|
||||||
|
if (BackButtonCount > 0)
|
||||||
|
{
|
||||||
|
BackButtonCount--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override VisibilityContainer CreateFooterContent() => new TestFooterContent();
|
||||||
|
|
||||||
|
public partial class TestFooterContent : VisibilityContainer
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new ShearedButton(200) { Text = "Action #1", Action = () => { } },
|
||||||
|
new ShearedButton(140) { Text = "Action #2", Action = () => { } },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
this.MoveToY(0, 400, Easing.OutQuint)
|
||||||
|
.FadeIn(400, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
this.MoveToY(-20f, 200, Easing.OutQuint)
|
||||||
|
.FadeOut(200, Easing.OutQuint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,9 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public override async Task<Live<BeatmapSetInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original)
|
public override async Task<Live<BeatmapSetInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original)
|
||||||
{
|
{
|
||||||
var imported = await Import(notification, new[] { importTask }).ConfigureAwait(true);
|
Guid originalId = original.ID;
|
||||||
|
|
||||||
|
var imported = await Import(notification, new[] { importTask }).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!imported.Any())
|
if (!imported.Any())
|
||||||
return null;
|
return null;
|
||||||
@ -53,7 +55,7 @@ namespace osu.Game.Beatmaps
|
|||||||
var first = imported.First();
|
var first = imported.First();
|
||||||
|
|
||||||
// If there were no changes, ensure we don't accidentally nuke ourselves.
|
// If there were no changes, ensure we don't accidentally nuke ourselves.
|
||||||
if (first.ID == original.ID)
|
if (first.ID == originalId)
|
||||||
{
|
{
|
||||||
first.PerformRead(s =>
|
first.PerformRead(s =>
|
||||||
{
|
{
|
||||||
@ -69,7 +71,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
Logger.Log($"Beatmap \"{updated}\" update completed successfully", LoggingTarget.Database);
|
Logger.Log($"Beatmap \"{updated}\" update completed successfully", LoggingTarget.Database);
|
||||||
|
|
||||||
original = realm!.Find<BeatmapSetInfo>(original.ID)!;
|
// Re-fetch as we are likely on a different thread.
|
||||||
|
original = realm!.Find<BeatmapSetInfo>(originalId)!;
|
||||||
|
|
||||||
// Generally the import process will do this for us if the OnlineIDs match,
|
// Generally the import process will do this for us if the OnlineIDs match,
|
||||||
// but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated).
|
// but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated).
|
||||||
|
@ -415,6 +415,9 @@ namespace osu.Game.Beatmaps
|
|||||||
public Task<Live<BeatmapSetInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) =>
|
public Task<Live<BeatmapSetInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) =>
|
||||||
beatmapImporter.ImportAsUpdate(notification, importTask, original);
|
beatmapImporter.ImportAsUpdate(notification, importTask, original);
|
||||||
|
|
||||||
|
public Task<ExternalEditOperation<BeatmapSetInfo>> BeginExternalEditing(BeatmapSetInfo model) =>
|
||||||
|
beatmapImporter.BeginExternalEditing(model);
|
||||||
|
|
||||||
public Task Export(BeatmapSetInfo beatmap) => beatmapExporter.ExportAsync(beatmap.ToLive(Realm));
|
public Task Export(BeatmapSetInfo beatmap) => beatmapExporter.ExportAsync(beatmap.ToLive(Realm));
|
||||||
|
|
||||||
public Task ExportLegacy(BeatmapSetInfo beatmap) => legacyBeatmapExporter.ExportAsync(beatmap.ToLive(Realm));
|
public Task ExportLegacy(BeatmapSetInfo beatmap) => legacyBeatmapExporter.ExportAsync(beatmap.ToLive(Realm));
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
set => ScrollSpeedBindable.Value = value;
|
set => ScrollSpeedBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple;
|
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this control point enables Kiai mode.
|
/// Whether this control point enables Kiai mode.
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const double default_beat_length = 60000.0 / 60.0;
|
private const double default_beat_length = 60000.0 / 60.0;
|
||||||
|
|
||||||
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1;
|
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Red2;
|
||||||
|
|
||||||
public static readonly TimingControlPoint DEFAULT = new TimingControlPoint
|
public static readonly TimingControlPoint DEFAULT = new TimingControlPoint
|
||||||
{
|
{
|
||||||
|
@ -22,8 +22,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
|
|
||||||
public override bool IsPresent => true;
|
public override bool IsPresent => true;
|
||||||
|
|
||||||
private readonly CircularContainer foreground;
|
|
||||||
|
|
||||||
private readonly Box backgroundFill;
|
private readonly Box backgroundFill;
|
||||||
private readonly Box foregroundFill;
|
private readonly Box foregroundFill;
|
||||||
|
|
||||||
@ -35,22 +33,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
|
|
||||||
public BeatmapCardDownloadProgressBar()
|
public BeatmapCardDownloadProgressBar()
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChild = new CircularContainer
|
||||||
{
|
|
||||||
new CircularContainer
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Child = backgroundFill = new Box
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
backgroundFill = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
foreground = new CircularContainer
|
foregroundFill = new Box
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Child = foregroundFill = new Box
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
}
|
}
|
||||||
@ -89,7 +82,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
|
|
||||||
private void progressChanged()
|
private void progressChanged()
|
||||||
{
|
{
|
||||||
foreground.ResizeWidthTo((float)progress.Value, progress.Value > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint);
|
foregroundFill.ResizeWidthTo((float)progress.Value, progress.Value > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
bool shouldDim = Dimmed.Value || playButton.Playing.Value;
|
bool shouldDim = Dimmed.Value || playButton.Playing.Value;
|
||||||
|
|
||||||
playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||||
background.FadeColour(colourProvider.Background6.Opacity(shouldDim ? 0.8f : 0f), BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
background.FadeColour(colourProvider.Background6.Opacity(shouldDim ? 0.6f : 0f), BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,7 +389,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
HashSet<Guid> scoreIds = realmAccess.Run(r => new HashSet<Guid>(
|
HashSet<Guid> scoreIds = realmAccess.Run(r => new HashSet<Guid>(
|
||||||
r.All<ScoreInfo>()
|
r.All<ScoreInfo>()
|
||||||
.Where(s => s.TotalScoreVersion < 30000013) // last total score version with a significant change to ranks
|
.Where(s => s.TotalScoreVersion < 30000013 && !s.BackgroundReprocessingFailed) // last total score version with a significant change to ranks
|
||||||
.AsEnumerable()
|
.AsEnumerable()
|
||||||
// must be done after materialisation, as realm doesn't support
|
// must be done after materialisation, as realm doesn't support
|
||||||
// filtering on nested property predicates or projection via `.Select()`
|
// filtering on nested property predicates or projection via `.Select()`
|
||||||
|
65
osu.Game/Database/ExternalEditOperation.cs
Normal file
65
osu.Game/Database/ExternalEditOperation.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains information related to an active external edit operation.
|
||||||
|
/// </summary>
|
||||||
|
public class ExternalEditOperation<TModel> where TModel : class, IHasGuidPrimaryKey
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The temporary path at which the model has been exported to for editing.
|
||||||
|
/// </summary>
|
||||||
|
public readonly string MountedPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the model is still mounted at <see cref="MountedPath"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMounted { get; private set; }
|
||||||
|
|
||||||
|
private readonly IModelImporter<TModel> importer;
|
||||||
|
private readonly TModel original;
|
||||||
|
|
||||||
|
public ExternalEditOperation(IModelImporter<TModel> importer, TModel original, string path)
|
||||||
|
{
|
||||||
|
this.importer = importer;
|
||||||
|
this.original = original;
|
||||||
|
|
||||||
|
MountedPath = path;
|
||||||
|
|
||||||
|
IsMounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finish the external edit operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This will trigger an asynchronous reimport of the model.
|
||||||
|
/// Subsequent calls will be a no-op.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>A task which will eventuate in the newly imported model with changes applied.</returns>
|
||||||
|
public async Task<Live<TModel>?> Finish()
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(MountedPath) || !IsMounted)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
IsMounted = false;
|
||||||
|
|
||||||
|
Live<TModel>? imported = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(MountedPath), original)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.Delete(MountedPath, true);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
return imported;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,15 @@ namespace osu.Game.Database
|
|||||||
/// <returns>The imported model.</returns>
|
/// <returns>The imported model.</returns>
|
||||||
Task<Live<TModel>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original);
|
Task<Live<TModel>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mount all files for a model to a temporary directory to allow for external editing.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// When editing is completed, call Finish() on the returned operation class to begin the import-and-update process.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="model">The model to mount.</param>
|
||||||
|
public Task<ExternalEditOperation<TModel>> BeginExternalEditing(TModel model);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A user displayable name for the model type associated with this manager.
|
/// A user displayable name for the model type associated with this manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -179,6 +179,30 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
public virtual Task<Live<TModel>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original) => throw new NotImplementedException();
|
public virtual Task<Live<TModel>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public async Task<ExternalEditOperation<TModel>> BeginExternalEditing(TModel model)
|
||||||
|
{
|
||||||
|
string mountedPath = Path.Join(Path.GetTempPath(), model.Hash);
|
||||||
|
|
||||||
|
if (Directory.Exists(mountedPath))
|
||||||
|
Directory.Delete(mountedPath, true);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(mountedPath);
|
||||||
|
|
||||||
|
foreach (var realmFile in model.Files)
|
||||||
|
{
|
||||||
|
string sourcePath = Files.Storage.GetFullPath(realmFile.File.GetStoragePath());
|
||||||
|
string destinationPath = Path.Join(mountedPath, realmFile.Filename);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);
|
||||||
|
|
||||||
|
using (var inStream = Files.Storage.GetStream(sourcePath))
|
||||||
|
using (var outStream = File.Create(destinationPath))
|
||||||
|
await inStream.CopyToAsync(outStream).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ExternalEditOperation<TModel>(this, model, mountedPath);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Import one <typeparamref name="TModel"/> from the filesystem and delete the file on success.
|
/// Import one <typeparamref name="TModel"/> from the filesystem and delete the file on success.
|
||||||
/// Note that this bypasses the UI flow and should only be used for special cases or testing.
|
/// Note that this bypasses the UI flow and should only be used for special cases or testing.
|
||||||
|
@ -16,15 +16,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
[Description("button-sidebar")]
|
[Description("button-sidebar")]
|
||||||
ButtonSidebar,
|
ButtonSidebar,
|
||||||
|
|
||||||
[Description("toolbar")]
|
|
||||||
Toolbar,
|
|
||||||
|
|
||||||
[Description("tabselect")]
|
[Description("tabselect")]
|
||||||
TabSelect,
|
TabSelect,
|
||||||
|
|
||||||
[Description("scrolltotop")]
|
|
||||||
ScrollToTop,
|
|
||||||
|
|
||||||
[Description("dialog-cancel")]
|
[Description("dialog-cancel")]
|
||||||
DialogCancel,
|
DialogCancel,
|
||||||
|
|
||||||
|
@ -7,6 +7,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
protected override bool AllowIme => false;
|
protected override bool AllowIme => false;
|
||||||
|
|
||||||
|
public OsuNumberBox()
|
||||||
|
{
|
||||||
|
SelectAllOnFocus = true;
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character);
|
protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,8 +122,6 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Padding = new MarginPadding { Left = 5, Right = 5 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
@ -63,6 +63,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
private Dictionary<FeedbackSampleType, Sample?[]> sampleMap = new Dictionary<FeedbackSampleType, Sample?[]>();
|
private Dictionary<FeedbackSampleType, Sample?[]> sampleMap = new Dictionary<FeedbackSampleType, Sample?[]>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether all text should be selected when the <see cref="OsuTextBox"/> gains focus.
|
||||||
|
/// </summary>
|
||||||
|
public bool SelectAllOnFocus { get; set; }
|
||||||
|
|
||||||
public OsuTextBox()
|
public OsuTextBox()
|
||||||
{
|
{
|
||||||
Height = 40;
|
Height = 40;
|
||||||
@ -255,6 +260,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
BorderThickness = 3;
|
BorderThickness = 3;
|
||||||
|
|
||||||
base.OnFocus(e);
|
base.OnFocus(e);
|
||||||
|
|
||||||
|
if (SelectAllOnFocus)
|
||||||
|
SelectAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnFocusLost(FocusLostEvent e)
|
protected override void OnFocusLost(FocusLostEvent e)
|
||||||
|
@ -28,6 +28,12 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
set => Component.ReadOnly = value;
|
set => Component.ReadOnly = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SelectAllOnFocus
|
||||||
|
{
|
||||||
|
get => Component.SelectAllOnFocus;
|
||||||
|
set => Component.SelectAllOnFocus = value;
|
||||||
|
}
|
||||||
|
|
||||||
public LocalisableString PlaceholderText
|
public LocalisableString PlaceholderText
|
||||||
{
|
{
|
||||||
set => Component.PlaceholderText = value;
|
set => Component.PlaceholderText = value;
|
||||||
|
@ -164,6 +164,8 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
public string AccessToken => authentication.RequestAccessToken();
|
public string AccessToken => authentication.RequestAccessToken();
|
||||||
|
|
||||||
|
public Guid SessionIdentifier { get; } = Guid.NewGuid();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Number of consecutive requests which failed due to network issues.
|
/// Number of consecutive requests which failed due to network issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -39,6 +39,8 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
public string AccessToken => "token";
|
public string AccessToken => "token";
|
||||||
|
|
||||||
|
public Guid SessionIdentifier { get; } = Guid.NewGuid();
|
||||||
|
|
||||||
/// <seealso cref="APIAccess.IsLoggedIn"/>
|
/// <seealso cref="APIAccess.IsLoggedIn"/>
|
||||||
public bool IsLoggedIn => State.Value > APIState.Offline;
|
public bool IsLoggedIn => State.Value > APIState.Offline;
|
||||||
|
|
||||||
|
@ -44,6 +44,12 @@ namespace osu.Game.Online.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
string AccessToken { get; }
|
string AccessToken { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used as an identifier of a single local lazer session.
|
||||||
|
/// Sent across the wire for the purposes of concurrency control to spectator server.
|
||||||
|
/// </summary>
|
||||||
|
Guid SessionIdentifier { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns whether the local user is logged in.
|
/// Returns whether the local user is logged in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -19,6 +19,9 @@ namespace osu.Game.Online
|
|||||||
{
|
{
|
||||||
public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down.";
|
public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down.";
|
||||||
|
|
||||||
|
public const string VERSION_HASH_HEADER = @"X-Osu-Version-Hash";
|
||||||
|
public const string CLIENT_SESSION_ID_HEADER = @"X-Client-Session-ID";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked whenever a new hub connection is built, to configure it before it's started.
|
/// Invoked whenever a new hub connection is built, to configure it before it's started.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -68,8 +71,11 @@ namespace osu.Game.Online
|
|||||||
options.Proxy.Credentials = CredentialCache.DefaultCredentials;
|
options.Proxy.Credentials = CredentialCache.DefaultCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.Headers.Add("Authorization", $"Bearer {API.AccessToken}");
|
options.Headers.Add(@"Authorization", @$"Bearer {API.AccessToken}");
|
||||||
options.Headers.Add("OsuVersionHash", versionHash);
|
// non-standard header name kept for backwards compatibility, can be removed after server side has migrated to `VERSION_HASH_HEADER`
|
||||||
|
options.Headers.Add(@"OsuVersionHash", versionHash);
|
||||||
|
options.Headers.Add(VERSION_HASH_HEADER, versionHash);
|
||||||
|
options.Headers.Add(CLIENT_SESSION_ID_HEADER, API.SessionIdentifier.ToString());
|
||||||
});
|
});
|
||||||
|
|
||||||
if (RuntimeFeature.IsDynamicCodeCompiled && preferMessagePack)
|
if (RuntimeFeature.IsDynamicCodeCompiled && preferMessagePack)
|
||||||
|
23
osu.Game/Online/Rooms/ShowPlaylistScoreRequest.cs
Normal file
23
osu.Game/Online/Rooms/ShowPlaylistScoreRequest.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// 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.Game.Online.API;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Rooms
|
||||||
|
{
|
||||||
|
public class ShowPlaylistScoreRequest : APIRequest<MultiplayerScore>
|
||||||
|
{
|
||||||
|
private readonly long roomId;
|
||||||
|
private readonly long playlistItemId;
|
||||||
|
private readonly long scoreId;
|
||||||
|
|
||||||
|
public ShowPlaylistScoreRequest(long roomId, long playlistItemId, long scoreId)
|
||||||
|
{
|
||||||
|
this.roomId = roomId;
|
||||||
|
this.playlistItemId = playlistItemId;
|
||||||
|
this.scoreId = scoreId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores/{scoreId}";
|
||||||
|
}
|
||||||
|
}
|
@ -51,6 +51,7 @@ using osu.Game.Online.Chat;
|
|||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapListing;
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Overlays.Music;
|
using osu.Game.Overlays.Music;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Overlays.SkinEditor;
|
using osu.Game.Overlays.SkinEditor;
|
||||||
@ -83,7 +84,7 @@ namespace osu.Game
|
|||||||
public partial class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler
|
public partial class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// Different port allows runnning release and debug builds alongside each other.
|
// Different port allows running release and debug builds alongside each other.
|
||||||
public const int IPC_PORT = 44824;
|
public const int IPC_PORT = 44824;
|
||||||
#else
|
#else
|
||||||
public const int IPC_PORT = 44823;
|
public const int IPC_PORT = 44823;
|
||||||
@ -132,6 +133,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
private Container topMostOverlayContent;
|
private Container topMostOverlayContent;
|
||||||
|
|
||||||
|
private Container footerBasedOverlayContent;
|
||||||
|
|
||||||
protected ScalingContainer ScreenContainer { get; private set; }
|
protected ScalingContainer ScreenContainer { get; private set; }
|
||||||
|
|
||||||
protected Container ScreenOffsetContainer { get; private set; }
|
protected Container ScreenOffsetContainer { get; private set; }
|
||||||
@ -156,8 +159,6 @@ namespace osu.Game
|
|||||||
|
|
||||||
private float toolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0);
|
private float toolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0);
|
||||||
|
|
||||||
private float screenFooterOffset => (ScreenFooter?.DrawHeight ?? 0) - (ScreenFooter?.Position.Y ?? 0);
|
|
||||||
|
|
||||||
private IdleTracker idleTracker;
|
private IdleTracker idleTracker;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -242,6 +243,10 @@ namespace osu.Game
|
|||||||
throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} once.");
|
throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} once.");
|
||||||
|
|
||||||
externalOverlays.Add(overlayContainer);
|
externalOverlays.Add(overlayContainer);
|
||||||
|
|
||||||
|
if (overlayContainer is ShearedOverlayContainer)
|
||||||
|
footerBasedOverlayContent.Add(overlayContainer);
|
||||||
|
else
|
||||||
overlayContent.Add(overlayContainer);
|
overlayContent.Add(overlayContainer);
|
||||||
|
|
||||||
if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer)
|
if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer)
|
||||||
@ -934,7 +939,6 @@ namespace osu.Game
|
|||||||
return string.Join(" / ", combinations);
|
return string.Join(" / ", combinations);
|
||||||
};
|
};
|
||||||
|
|
||||||
Container logoContainer;
|
|
||||||
ScreenFooter.BackReceptor backReceptor;
|
ScreenFooter.BackReceptor backReceptor;
|
||||||
|
|
||||||
dependencies.CacheAs(idleTracker = new GameIdleTracker(6000));
|
dependencies.CacheAs(idleTracker = new GameIdleTracker(6000));
|
||||||
@ -948,6 +952,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
Add(sessionIdleTracker);
|
Add(sessionIdleTracker);
|
||||||
|
|
||||||
|
Container logoContainer;
|
||||||
|
|
||||||
AddRange(new Drawable[]
|
AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
new VolumeControlReceptor
|
new VolumeControlReceptor
|
||||||
@ -976,11 +982,19 @@ namespace osu.Game
|
|||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
Action = () => ScreenFooter.OnBack?.Invoke(),
|
Action = () => ScreenFooter.OnBack?.Invoke(),
|
||||||
},
|
},
|
||||||
|
logoContainer = new Container { RelativeSizeAxes = Axes.Both },
|
||||||
|
footerBasedOverlayContent = new Container
|
||||||
|
{
|
||||||
|
Depth = -1,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
new PopoverContainer
|
new PopoverContainer
|
||||||
{
|
{
|
||||||
|
Depth = -1,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = ScreenFooter = new ScreenFooter(backReceptor)
|
Child = ScreenFooter = new ScreenFooter(backReceptor)
|
||||||
{
|
{
|
||||||
|
RequestLogoInFront = inFront => ScreenContainer.ChangeChildDepth(logoContainer, inFront ? float.MinValue : 0),
|
||||||
OnBack = () =>
|
OnBack = () =>
|
||||||
{
|
{
|
||||||
if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen))
|
if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen))
|
||||||
@ -991,7 +1005,6 @@ namespace osu.Game
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
logoContainer = new Container { RelativeSizeAxes = Axes.Both },
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1025,7 +1038,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
if (!IsDeployedBuild)
|
if (!IsDeployedBuild)
|
||||||
{
|
{
|
||||||
dependencies.Cache(versionManager = new VersionManager { Depth = int.MinValue });
|
dependencies.Cache(versionManager = new VersionManager());
|
||||||
loadComponentSingleFile(versionManager, ScreenContainer.Add);
|
loadComponentSingleFile(versionManager, ScreenContainer.Add);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1072,7 +1085,7 @@ namespace osu.Game
|
|||||||
loadComponentSingleFile(CreateUpdateManager(), Add, true);
|
loadComponentSingleFile(CreateUpdateManager(), Add, true);
|
||||||
|
|
||||||
// overlay elements
|
// overlay elements
|
||||||
loadComponentSingleFile(FirstRunOverlay = new FirstRunSetupOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(FirstRunOverlay = new FirstRunSetupOverlay(), footerBasedOverlayContent.Add, true);
|
||||||
loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true);
|
loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
|
||||||
@ -1137,7 +1150,7 @@ namespace osu.Game
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensure only one of these overlays are open at once.
|
// ensure only one of these overlays are open at once.
|
||||||
var singleDisplayOverlays = new OverlayContainer[] { FirstRunOverlay, chatOverlay, news, dashboard, beatmapListing, changelogOverlay, rankingsOverlay, wikiOverlay };
|
var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, news, dashboard, beatmapListing, changelogOverlay, rankingsOverlay, wikiOverlay };
|
||||||
|
|
||||||
foreach (var overlay in singleDisplayOverlays)
|
foreach (var overlay in singleDisplayOverlays)
|
||||||
{
|
{
|
||||||
@ -1485,7 +1498,6 @@ namespace osu.Game
|
|||||||
|
|
||||||
ScreenOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset };
|
ScreenOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset };
|
||||||
overlayOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset };
|
overlayOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset };
|
||||||
ScreenStack.Padding = new MarginPadding { Bottom = screenFooterOffset };
|
|
||||||
|
|
||||||
float horizontalOffset = 0f;
|
float horizontalOffset = 0f;
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
Progress.BindValueChanged(progressChanged, true);
|
Progress.BindValueChanged(progressChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Confirm()
|
protected override void Confirm()
|
||||||
@ -114,13 +114,13 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
if (progress.NewValue < progress.OldValue)
|
if (progress.NewValue < progress.OldValue)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Clock.CurrentTime - lastTickPlaybackTime < 30)
|
if (Clock.CurrentTime - lastTickPlaybackTime < 40)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var channel = tickSample.GetChannel();
|
var channel = tickSample.GetChannel();
|
||||||
|
|
||||||
channel.Frequency.Value = 1 + progress.NewValue * 0.5f;
|
channel.Frequency.Value = 1 + progress.NewValue;
|
||||||
channel.Volume.Value = 0.5f + progress.NewValue / 2f;
|
channel.Volume.Value = 0.1f + progress.NewValue / 2f;
|
||||||
|
|
||||||
channel.Play();
|
channel.Play();
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ using osu.Game.Overlays.Settings;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
@ -153,6 +154,7 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
|
|
||||||
OsuScreenStack stack;
|
OsuScreenStack stack;
|
||||||
OsuLogo logo;
|
OsuLogo logo;
|
||||||
|
ScreenFooter footer;
|
||||||
|
|
||||||
Padding = new MarginPadding(5);
|
Padding = new MarginPadding(5);
|
||||||
|
|
||||||
@ -166,7 +168,8 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.Both,
|
RelativePositionAxes = Axes.Both,
|
||||||
Position = new Vector2(0.5f),
|
Position = new Vector2(0.5f),
|
||||||
})
|
}),
|
||||||
|
(typeof(ScreenFooter), footer = new ScreenFooter()),
|
||||||
},
|
},
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -178,7 +181,8 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
stack = new OsuScreenStack(),
|
stack = new OsuScreenStack(),
|
||||||
logo
|
footer,
|
||||||
|
logo,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ using osu.Game.Overlays.FirstRunSetup;
|
|||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
|
|
||||||
namespace osu.Game.Overlays
|
namespace osu.Game.Overlays
|
||||||
@ -44,8 +45,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private ScreenStack? stack;
|
private ScreenStack? stack;
|
||||||
|
|
||||||
public ShearedButton NextButton = null!;
|
public ShearedButton? NextButton => DisplayedFooterContent?.NextButton;
|
||||||
public ShearedButton BackButton = null!;
|
|
||||||
|
|
||||||
private readonly Bindable<bool> showFirstRunSetup = new Bindable<bool>();
|
private readonly Bindable<bool> showFirstRunSetup = new Bindable<bool>();
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ namespace osu.Game.Overlays
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Bottom = 20, },
|
Padding = new MarginPadding { Bottom = 20 },
|
||||||
Child = new GridContainer
|
Child = new GridContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -134,51 +134,6 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
FooterContent.Add(new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Margin = new MarginPadding { Vertical = PADDING },
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.Absolute, 10),
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.Absolute, 10),
|
|
||||||
},
|
|
||||||
RowDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
Empty(),
|
|
||||||
BackButton = new ShearedButton(300)
|
|
||||||
{
|
|
||||||
Text = CommonStrings.Back,
|
|
||||||
Action = showPreviousStep,
|
|
||||||
Enabled = { Value = false },
|
|
||||||
DarkerColour = colours.Pink2,
|
|
||||||
LighterColour = colours.Pink1,
|
|
||||||
},
|
|
||||||
NextButton = new ShearedButton(0)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Width = 1,
|
|
||||||
Text = FirstRunSetupOverlayStrings.GetStarted,
|
|
||||||
DarkerColour = ColourProvider.Colour2,
|
|
||||||
LighterColour = ColourProvider.Colour1,
|
|
||||||
Action = showNextStep
|
|
||||||
},
|
|
||||||
Empty(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -190,6 +145,36 @@ namespace osu.Game.Overlays
|
|||||||
if (showFirstRunSetup.Value) Show();
|
if (showFirstRunSetup.Value) Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ScreenFooter footer { get; set; } = null!;
|
||||||
|
|
||||||
|
public new FirstRunSetupFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as FirstRunSetupFooterContent;
|
||||||
|
|
||||||
|
public override VisibilityContainer CreateFooterContent()
|
||||||
|
{
|
||||||
|
var footerContent = new FirstRunSetupFooterContent
|
||||||
|
{
|
||||||
|
ShowNextStep = showNextStep,
|
||||||
|
};
|
||||||
|
|
||||||
|
footerContent.OnLoadComplete += _ => updateButtons();
|
||||||
|
return footerContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnBackButton()
|
||||||
|
{
|
||||||
|
if (currentStepIndex == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Debug.Assert(stack != null);
|
||||||
|
|
||||||
|
stack.CurrentScreen.Exit();
|
||||||
|
currentStepIndex--;
|
||||||
|
|
||||||
|
updateButtons();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
{
|
{
|
||||||
if (!e.Repeat)
|
if (!e.Repeat)
|
||||||
@ -197,19 +182,12 @@ namespace osu.Game.Overlays
|
|||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case GlobalAction.Select:
|
case GlobalAction.Select:
|
||||||
NextButton.TriggerClick();
|
DisplayedFooterContent?.NextButton.TriggerClick();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GlobalAction.Back:
|
case GlobalAction.Back:
|
||||||
if (BackButton.Enabled.Value)
|
footer.BackButton.TriggerClick();
|
||||||
{
|
return false;
|
||||||
BackButton.TriggerClick();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If back button is disabled, we are at the first step.
|
|
||||||
// The base call will handle dismissal of the overlay.
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,19 +257,6 @@ namespace osu.Game.Overlays
|
|||||||
showNextStep();
|
showNextStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showPreviousStep()
|
|
||||||
{
|
|
||||||
if (currentStepIndex == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Debug.Assert(stack != null);
|
|
||||||
|
|
||||||
stack.CurrentScreen.Exit();
|
|
||||||
currentStepIndex--;
|
|
||||||
|
|
||||||
updateButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showNextStep()
|
private void showNextStep()
|
||||||
{
|
{
|
||||||
Debug.Assert(currentStepIndex != null);
|
Debug.Assert(currentStepIndex != null);
|
||||||
@ -322,29 +287,61 @@ namespace osu.Game.Overlays
|
|||||||
updateButtons();
|
updateButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateButtons()
|
private void updateButtons() => DisplayedFooterContent?.UpdateButtons(currentStepIndex, steps);
|
||||||
{
|
|
||||||
BackButton.Enabled.Value = currentStepIndex > 0;
|
|
||||||
NextButton.Enabled.Value = currentStepIndex != null;
|
|
||||||
|
|
||||||
if (currentStepIndex == null)
|
public partial class FirstRunSetupFooterContent : VisibilityContainer
|
||||||
|
{
|
||||||
|
public ShearedButton NextButton { get; private set; } = null!;
|
||||||
|
|
||||||
|
public Action? ShowNextStep;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = NextButton = new ShearedButton(0)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Margin = new MarginPadding { Right = 12f },
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 1,
|
||||||
|
Text = FirstRunSetupOverlayStrings.GetStarted,
|
||||||
|
DarkerColour = colourProvider.Colour2,
|
||||||
|
LighterColour = colourProvider.Colour1,
|
||||||
|
Action = () => ShowNextStep?.Invoke(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateButtons(int? currentStep, IReadOnlyList<Type> steps)
|
||||||
|
{
|
||||||
|
NextButton.Enabled.Value = currentStep != null;
|
||||||
|
|
||||||
|
if (currentStep == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool isFirstStep = currentStepIndex == 0;
|
bool isFirstStep = currentStep == 0;
|
||||||
bool isLastStep = currentStepIndex == steps.Count - 1;
|
bool isLastStep = currentStep == steps.Count - 1;
|
||||||
|
|
||||||
if (isFirstStep)
|
if (isFirstStep)
|
||||||
{
|
|
||||||
BackButton.Text = CommonStrings.Back;
|
|
||||||
NextButton.Text = FirstRunSetupOverlayStrings.GetStarted;
|
NextButton.Text = FirstRunSetupOverlayStrings.GetStarted;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
BackButton.Text = LocalisableString.Interpolate($@"{CommonStrings.Back} ({steps[currentStepIndex.Value - 1].GetLocalisableDescription()})");
|
|
||||||
|
|
||||||
NextButton.Text = isLastStep
|
NextButton.Text = isLastStep
|
||||||
? CommonStrings.Finish
|
? CommonStrings.Finish
|
||||||
: LocalisableString.Interpolate($@"{CommonStrings.Next} ({steps[currentStepIndex.Value + 1].GetLocalisableDescription()})");
|
: LocalisableString.Interpolate($@"{CommonStrings.Next} ({steps[currentStep.Value + 1].GetLocalisableDescription()})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
this.FadeIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
this.Delay(400).FadeOut();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,8 +108,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
updateValues();
|
updateValues();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
BeatmapInfo.BindValueChanged(_ => updateValues());
|
|
||||||
|
|
||||||
Collapsed.BindValueChanged(_ =>
|
Collapsed.BindValueChanged(_ =>
|
||||||
{
|
{
|
||||||
// Only start autosize animations on first collapse toggle. This avoids an ugly initial presentation.
|
// Only start autosize animations on first collapse toggle. This avoids an ugly initial presentation.
|
||||||
@ -120,12 +118,32 @@ namespace osu.Game.Overlays.Mods
|
|||||||
GameRuleset = game.Ruleset.GetBoundCopy();
|
GameRuleset = game.Ruleset.GetBoundCopy();
|
||||||
GameRuleset.BindValueChanged(_ => updateValues());
|
GameRuleset.BindValueChanged(_ => updateValues());
|
||||||
|
|
||||||
BeatmapInfo.BindValueChanged(_ => updateValues());
|
BeatmapInfo.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
updateStarDifficultyBindable();
|
||||||
updateValues();
|
updateValues();
|
||||||
|
}, true);
|
||||||
|
|
||||||
updateCollapsedState();
|
updateCollapsedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateStarDifficultyBindable()
|
||||||
|
{
|
||||||
|
cancellationSource?.Cancel();
|
||||||
|
|
||||||
|
if (BeatmapInfo.Value == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
starDifficulty = difficultyCache.GetBindableDifficulty(BeatmapInfo.Value, (cancellationSource = new CancellationTokenSource()).Token);
|
||||||
|
starDifficulty.BindValueChanged(s =>
|
||||||
|
{
|
||||||
|
starRatingDisplay.Current.Value = s.NewValue ?? default;
|
||||||
|
|
||||||
|
if (!starRatingDisplay.IsPresent)
|
||||||
|
starRatingDisplay.FinishTransforms(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
startAnimating();
|
startAnimating();
|
||||||
@ -154,17 +172,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
if (BeatmapInfo.Value == null)
|
if (BeatmapInfo.Value == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
cancellationSource?.Cancel();
|
|
||||||
|
|
||||||
starDifficulty = difficultyCache.GetBindableDifficulty(BeatmapInfo.Value, (cancellationSource = new CancellationTokenSource()).Token);
|
|
||||||
starDifficulty.BindValueChanged(s =>
|
|
||||||
{
|
|
||||||
starRatingDisplay.Current.Value = s.NewValue ?? default;
|
|
||||||
|
|
||||||
if (!starRatingDisplay.IsPresent)
|
|
||||||
starRatingDisplay.FinishTransforms(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
double rate = ModUtils.CalculateRateWithMods(Mods.Value);
|
double rate = ModUtils.CalculateRateWithMods(Mods.Value);
|
||||||
|
|
||||||
bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate);
|
bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate);
|
||||||
|
177
osu.Game/Overlays/Mods/ModSelectFooterContent.cs
Normal file
177
osu.Game/Overlays/Mods/ModSelectFooterContent.cs
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
public partial class ModSelectFooterContent : VisibilityContainer
|
||||||
|
{
|
||||||
|
private readonly ModSelectOverlay overlay;
|
||||||
|
|
||||||
|
private RankingInformationDisplay? rankingInformationDisplay;
|
||||||
|
private BeatmapAttributesDisplay? beatmapAttributesDisplay;
|
||||||
|
private FillFlowContainer<ShearedButton> buttonFlow = null!;
|
||||||
|
private FillFlowContainer contentFlow = null!;
|
||||||
|
|
||||||
|
public DeselectAllModsButton? DeselectAllModsButton { get; set; }
|
||||||
|
|
||||||
|
public readonly IBindable<WorkingBeatmap?> Beatmap = new Bindable<WorkingBeatmap?>();
|
||||||
|
public readonly IBindable<IReadOnlyList<Mod>> ActiveMods = new Bindable<IReadOnlyList<Mod>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the effects (on score multiplier, on or beatmap difficulty) of the current selected set of mods should be shown.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool ShowModEffects => true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the ranking information and beatmap attributes displays are stacked vertically due to small space.
|
||||||
|
/// </summary>
|
||||||
|
public bool DisplaysStackedVertically { get; private set; }
|
||||||
|
|
||||||
|
public ModSelectFooterContent(ModSelectOverlay overlay)
|
||||||
|
{
|
||||||
|
this.overlay = overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = buttonFlow = new FillFlowContainer<ShearedButton>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Padding = new MarginPadding { Horizontal = 20 },
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
ChildrenEnumerable = CreateButtons(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ShowModEffects)
|
||||||
|
{
|
||||||
|
AddInternal(contentFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(30, 10),
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Margin = new MarginPadding { Horizontal = 20 },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
rankingInformationDisplay = new RankingInformationDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight
|
||||||
|
},
|
||||||
|
beatmapAttributesDisplay = new BeatmapAttributesDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
BeatmapInfo = { Value = Beatmap.Value?.BeatmapInfo },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModSettingChangeTracker? modSettingChangeTracker;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Beatmap.BindValueChanged(b =>
|
||||||
|
{
|
||||||
|
if (beatmapAttributesDisplay != null)
|
||||||
|
beatmapAttributesDisplay.BeatmapInfo.Value = b.NewValue?.BeatmapInfo;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
ActiveMods.BindValueChanged(m =>
|
||||||
|
{
|
||||||
|
updateInformation();
|
||||||
|
|
||||||
|
modSettingChangeTracker?.Dispose();
|
||||||
|
|
||||||
|
// Importantly, use ActiveMods.Value here (and not the ValueChanged NewValue) as the latter can
|
||||||
|
// potentially be stale, due to complexities in the way change trackers work.
|
||||||
|
//
|
||||||
|
// See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988
|
||||||
|
modSettingChangeTracker = new ModSettingChangeTracker(ActiveMods.Value);
|
||||||
|
modSettingChangeTracker.SettingChanged += _ => updateInformation();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInformation()
|
||||||
|
{
|
||||||
|
if (rankingInformationDisplay != null)
|
||||||
|
{
|
||||||
|
double multiplier = 1.0;
|
||||||
|
|
||||||
|
foreach (var mod in ActiveMods.Value)
|
||||||
|
multiplier *= mod.ScoreMultiplier;
|
||||||
|
|
||||||
|
rankingInformationDisplay.ModMultiplier.Value = multiplier;
|
||||||
|
rankingInformationDisplay.Ranked.Value = ActiveMods.Value.All(m => m.Ranked);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (beatmapAttributesDisplay != null)
|
||||||
|
beatmapAttributesDisplay.Mods.Value = ActiveMods.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (beatmapAttributesDisplay != null)
|
||||||
|
{
|
||||||
|
float rightEdgeOfLastButton = buttonFlow[^1].ScreenSpaceDrawQuad.TopRight.X;
|
||||||
|
|
||||||
|
// this is cheating a bit; the 640 value is hardcoded based on how wide the expanded panel _generally_ is.
|
||||||
|
// due to the transition applied, the raw screenspace quad of the panel cannot be used, as it will trigger an ugly feedback cycle of expanding and collapsing.
|
||||||
|
float projectedLeftEdgeOfExpandedBeatmapAttributesDisplay = buttonFlow.ToScreenSpace(buttonFlow.DrawSize - new Vector2(640, 0)).X;
|
||||||
|
|
||||||
|
DisplaysStackedVertically = rightEdgeOfLastButton > projectedLeftEdgeOfExpandedBeatmapAttributesDisplay;
|
||||||
|
|
||||||
|
// only update preview panel's collapsed state after we are fully visible, to ensure all the buttons are where we expect them to be.
|
||||||
|
if (Alpha == 1)
|
||||||
|
beatmapAttributesDisplay.Collapsed.Value = DisplaysStackedVertically;
|
||||||
|
|
||||||
|
contentFlow.LayoutDuration = 200;
|
||||||
|
contentFlow.LayoutEasing = Easing.OutQuint;
|
||||||
|
contentFlow.Direction = DisplaysStackedVertically ? FillDirection.Vertical : FillDirection.Horizontal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual IEnumerable<ShearedButton> CreateButtons() => new[]
|
||||||
|
{
|
||||||
|
DeselectAllModsButton = new DeselectAllModsButton(overlay)
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
this.MoveToY(0, 400, Easing.OutQuint)
|
||||||
|
.FadeIn(400, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
this.MoveToY(-20f, 200, Easing.OutQuint)
|
||||||
|
.FadeOut(200, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -87,11 +88,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
public ShearedSearchTextBox SearchTextBox { get; private set; } = null!;
|
public ShearedSearchTextBox SearchTextBox { get; private set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the effects (on score multiplier, on or beatmap difficulty) of the current selected set of mods should be shown.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual bool ShowModEffects => true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether per-mod customisation controls are visible.
|
/// Whether per-mod customisation controls are visible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -108,11 +104,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
protected virtual IReadOnlyList<Mod> ComputeActiveMods() => SelectedMods.Value;
|
protected virtual IReadOnlyList<Mod> ComputeActiveMods() => SelectedMods.Value;
|
||||||
|
|
||||||
protected virtual IEnumerable<ShearedButton> CreateFooterButtons()
|
|
||||||
{
|
|
||||||
yield return deselectAllModsButton = new DeselectAllModsButton(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> globalAvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> globalAvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
|
||||||
|
|
||||||
public IEnumerable<ModState> AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value);
|
public IEnumerable<ModState> AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value);
|
||||||
@ -121,34 +112,18 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
private ColumnScrollContainer columnScroll = null!;
|
private ColumnScrollContainer columnScroll = null!;
|
||||||
private ColumnFlowContainer columnFlow = null!;
|
private ColumnFlowContainer columnFlow = null!;
|
||||||
private FillFlowContainer<ShearedButton> footerButtonFlow = null!;
|
|
||||||
private FillFlowContainer footerContentFlow = null!;
|
|
||||||
private DeselectAllModsButton deselectAllModsButton = null!;
|
|
||||||
|
|
||||||
private Container aboveColumnsContent = null!;
|
private Container aboveColumnsContent = null!;
|
||||||
private RankingInformationDisplay? rankingInformationDisplay;
|
|
||||||
private BeatmapAttributesDisplay? beatmapAttributesDisplay;
|
|
||||||
private ModCustomisationPanel customisationPanel = null!;
|
private ModCustomisationPanel customisationPanel = null!;
|
||||||
|
|
||||||
protected ShearedButton BackButton { get; private set; } = null!;
|
protected virtual SelectAllModsButton? SelectAllModsButton => null;
|
||||||
protected SelectAllModsButton? SelectAllModsButton { get; set; }
|
|
||||||
|
|
||||||
private Sample? columnAppearSample;
|
private Sample? columnAppearSample;
|
||||||
|
|
||||||
private WorkingBeatmap? beatmap;
|
public readonly Bindable<WorkingBeatmap?> Beatmap = new Bindable<WorkingBeatmap?>();
|
||||||
|
|
||||||
public WorkingBeatmap? Beatmap
|
[Resolved]
|
||||||
{
|
private ScreenFooter? footer { get; set; }
|
||||||
get => beatmap;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (beatmap == value) return;
|
|
||||||
|
|
||||||
beatmap = value;
|
|
||||||
if (IsLoaded && beatmapAttributesDisplay != null)
|
|
||||||
beatmapAttributesDisplay.BeatmapInfo.Value = beatmap?.BeatmapInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
|
protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
|
||||||
: base(colourScheme)
|
: base(colourScheme)
|
||||||
@ -227,59 +202,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
FooterContent.Add(footerButtonFlow = new FillFlowContainer<ShearedButton>
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
Padding = new MarginPadding
|
|
||||||
{
|
|
||||||
Vertical = PADDING,
|
|
||||||
Horizontal = 70
|
|
||||||
},
|
|
||||||
Spacing = new Vector2(10),
|
|
||||||
ChildrenEnumerable = CreateFooterButtons().Prepend(BackButton = new ShearedButton(BUTTON_WIDTH)
|
|
||||||
{
|
|
||||||
Text = CommonStrings.Back,
|
|
||||||
Action = Hide,
|
|
||||||
DarkerColour = colours.Pink2,
|
|
||||||
LighterColour = colours.Pink1
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ShowModEffects)
|
|
||||||
{
|
|
||||||
FooterContent.Add(footerContentFlow = new FillFlowContainer
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Spacing = new Vector2(30, 10),
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
Margin = new MarginPadding
|
|
||||||
{
|
|
||||||
Vertical = PADDING,
|
|
||||||
Horizontal = 20
|
|
||||||
},
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
rankingInformationDisplay = new RankingInformationDisplay
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight
|
|
||||||
},
|
|
||||||
beatmapAttributesDisplay = new BeatmapAttributesDisplay
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
BeatmapInfo = { Value = Beatmap?.BeatmapInfo },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
globalAvailableMods.BindTo(game.AvailableMods);
|
globalAvailableMods.BindTo(game.AvailableMods);
|
||||||
|
|
||||||
textSearchStartsActive = configManager.GetBindable<bool>(OsuSetting.ModSelectTextSearchStartsActive);
|
textSearchStartsActive = configManager.GetBindable<bool>(OsuSetting.ModSelectTextSearchStartsActive);
|
||||||
@ -293,8 +215,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
SearchTextBox.Current.Value = string.Empty;
|
SearchTextBox.Current.Value = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModSettingChangeTracker? modSettingChangeTracker;
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
// this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly.
|
// this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly.
|
||||||
@ -317,23 +237,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
ActiveMods.Value = ComputeActiveMods();
|
ActiveMods.Value = ComputeActiveMods();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
ActiveMods.BindValueChanged(_ =>
|
|
||||||
{
|
|
||||||
updateOverlayInformation();
|
|
||||||
|
|
||||||
modSettingChangeTracker?.Dispose();
|
|
||||||
|
|
||||||
if (AllowCustomisation)
|
|
||||||
{
|
|
||||||
// Importantly, use ActiveMods.Value here (and not the ValueChanged NewValue) as the latter can
|
|
||||||
// potentially be stale, due to complexities in the way change trackers work.
|
|
||||||
//
|
|
||||||
// See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988
|
|
||||||
modSettingChangeTracker = new ModSettingChangeTracker(ActiveMods.Value);
|
|
||||||
modSettingChangeTracker.SettingChanged += _ => updateOverlayInformation();
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
||||||
|
|
||||||
SearchTextBox.Current.BindValueChanged(query =>
|
SearchTextBox.Current.BindValueChanged(query =>
|
||||||
@ -351,6 +254,14 @@ namespace osu.Game.Overlays.Mods
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public new ModSelectFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as ModSelectFooterContent;
|
||||||
|
|
||||||
|
public override VisibilityContainer CreateFooterContent() => new ModSelectFooterContent(this)
|
||||||
|
{
|
||||||
|
Beatmap = { BindTarget = Beatmap },
|
||||||
|
ActiveMods = { BindTarget = ActiveMods },
|
||||||
|
};
|
||||||
|
|
||||||
private static readonly LocalisableString input_search_placeholder = Resources.Localisation.Web.CommonStrings.InputSearch;
|
private static readonly LocalisableString input_search_placeholder = Resources.Localisation.Web.CommonStrings.InputSearch;
|
||||||
private static readonly LocalisableString tab_to_search_placeholder = ModSelectOverlayStrings.TabToSearch;
|
private static readonly LocalisableString tab_to_search_placeholder = ModSelectOverlayStrings.TabToSearch;
|
||||||
|
|
||||||
@ -359,26 +270,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? input_search_placeholder : tab_to_search_placeholder;
|
SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? input_search_placeholder : tab_to_search_placeholder;
|
||||||
|
aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = DisplayedFooterContent?.DisplaysStackedVertically == true ? 75f : 15f };
|
||||||
if (beatmapAttributesDisplay != null)
|
|
||||||
{
|
|
||||||
float rightEdgeOfLastButton = footerButtonFlow[^1].ScreenSpaceDrawQuad.TopRight.X;
|
|
||||||
|
|
||||||
// this is cheating a bit; the 640 value is hardcoded based on how wide the expanded panel _generally_ is.
|
|
||||||
// due to the transition applied, the raw screenspace quad of the panel cannot be used, as it will trigger an ugly feedback cycle of expanding and collapsing.
|
|
||||||
float projectedLeftEdgeOfExpandedBeatmapAttributesDisplay = footerButtonFlow.ToScreenSpace(footerButtonFlow.DrawSize - new Vector2(640, 0)).X;
|
|
||||||
|
|
||||||
bool screenIsntWideEnough = rightEdgeOfLastButton > projectedLeftEdgeOfExpandedBeatmapAttributesDisplay;
|
|
||||||
|
|
||||||
// only update preview panel's collapsed state after we are fully visible, to ensure all the buttons are where we expect them to be.
|
|
||||||
if (Alpha == 1)
|
|
||||||
beatmapAttributesDisplay.Collapsed.Value = screenIsntWideEnough;
|
|
||||||
|
|
||||||
footerContentFlow.LayoutDuration = 200;
|
|
||||||
footerContentFlow.LayoutEasing = Easing.OutQuint;
|
|
||||||
footerContentFlow.Direction = screenIsntWideEnough ? FillDirection.Vertical : FillDirection.Horizontal;
|
|
||||||
aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = screenIsntWideEnough ? 70f : 15f };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -456,27 +348,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
|
modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates any information displayed on the overlay regarding the effects of the active mods.
|
|
||||||
/// This reads from <see cref="ActiveMods"/> instead of <see cref="SelectedMods"/>.
|
|
||||||
/// </summary>
|
|
||||||
private void updateOverlayInformation()
|
|
||||||
{
|
|
||||||
if (rankingInformationDisplay != null)
|
|
||||||
{
|
|
||||||
double multiplier = 1.0;
|
|
||||||
|
|
||||||
foreach (var mod in ActiveMods.Value)
|
|
||||||
multiplier *= mod.ScoreMultiplier;
|
|
||||||
|
|
||||||
rankingInformationDisplay.ModMultiplier.Value = multiplier;
|
|
||||||
rankingInformationDisplay.Ranked.Value = ActiveMods.Value.All(m => m.Ranked);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (beatmapAttributesDisplay != null)
|
|
||||||
beatmapAttributesDisplay.Mods.Value = ActiveMods.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCustomisation()
|
private void updateCustomisation()
|
||||||
{
|
{
|
||||||
if (!AllowCustomisation)
|
if (!AllowCustomisation)
|
||||||
@ -702,7 +573,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value)
|
if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value)
|
||||||
{
|
{
|
||||||
deselectAllModsButton.TriggerClick();
|
DisplayedFooterContent?.DeselectAllModsButton?.TriggerClick();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,7 +604,13 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
return base.OnPressed(e);
|
return base.OnPressed(e);
|
||||||
|
|
||||||
void hideOverlay() => BackButton.TriggerClick();
|
void hideOverlay()
|
||||||
|
{
|
||||||
|
if (footer != null)
|
||||||
|
footer.BackButton.TriggerClick();
|
||||||
|
else
|
||||||
|
Hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="IKeyBindingHandler{PlatformAction}"/>
|
/// <inheritdoc cref="IKeyBindingHandler{PlatformAction}"/>
|
||||||
@ -741,7 +618,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
/// This is handled locally here due to conflicts in input handling between the search text box and the select all mods button.
|
/// This is handled locally here due to conflicts in input handling between the search text box and the select all mods button.
|
||||||
/// Attempting to handle this action locally in both places leads to a possible scenario
|
/// Attempting to handle this action locally in both places leads to a possible scenario
|
||||||
/// wherein activating the "select all" platform binding will both select all text in the search box and select all mods.
|
/// wherein activating the "select all" platform binding will both select all text in the search box and select all mods.
|
||||||
/// </remarks>>
|
/// </remarks>
|
||||||
public bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
|
public bool OnPressed(KeyBindingPressEvent<PlatformAction> e)
|
||||||
{
|
{
|
||||||
if (e.Repeat || e.Action != PlatformAction.SelectAll || SelectAllModsButton == null)
|
if (e.Repeat || e.Action != PlatformAction.SelectAll || SelectAllModsButton == null)
|
||||||
|
@ -1,60 +1,67 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System;
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Screens.Footer;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A sheared overlay which provides a header and footer and basic animations.
|
/// A sheared overlay which provides a header and basic animations.
|
||||||
/// Exposes <see cref="TopLevelContent"/>, <see cref="MainAreaContent"/> and <see cref="Footer"/> as valid targets for content.
|
/// Exposes <see cref="TopLevelContent"/> and <see cref="MainAreaContent"/> as valid targets for content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class ShearedOverlayContainer : OsuFocusedOverlayContainer
|
public abstract partial class ShearedOverlayContainer : OsuFocusedOverlayContainer
|
||||||
{
|
{
|
||||||
protected const float PADDING = 14;
|
public const float PADDING = 14;
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
protected readonly OverlayColourProvider ColourProvider;
|
public readonly OverlayColourProvider ColourProvider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The overlay's header.
|
/// The overlay's header.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected ShearedOverlayHeader Header { get; private set; }
|
protected ShearedOverlayHeader Header { get; private set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The overlay's footer.
|
/// The overlay's footer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Container Footer { get; private set; }
|
protected Container Footer { get; private set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ScreenFooter? footer { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A container containing all content, including the header and footer.
|
/// A container containing all content, including the header and footer.
|
||||||
/// May be used for overlay-wide animations.
|
/// May be used for overlay-wide animations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Container TopLevelContent { get; private set; }
|
protected Container TopLevelContent { get; private set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A container for content that is to be displayed between the header and footer.
|
/// A container for content that is to be displayed between the header and footer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Container MainAreaContent { get; private set; }
|
protected Container MainAreaContent { get; private set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A container for content that is to be displayed inside the footer.
|
/// A container for content that is to be displayed inside the footer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Container FooterContent { get; private set; }
|
protected Container FooterContent { get; private set; } = null!;
|
||||||
|
|
||||||
protected override bool StartHidden => true;
|
protected override bool StartHidden => true;
|
||||||
|
|
||||||
protected override bool BlockNonPositionalInput => true;
|
protected override bool BlockNonPositionalInput => true;
|
||||||
|
|
||||||
|
// ShearedOverlayContainers are placed at a layer within the screen container as they rely on ScreenFooter which must be placed there.
|
||||||
|
// Therefore, dimming must be managed locally, since DimMainContent dims the entire screen layer.
|
||||||
|
protected sealed override bool DimMainContent => false;
|
||||||
|
|
||||||
protected ShearedOverlayContainer(OverlayColourScheme colourScheme)
|
protected ShearedOverlayContainer(OverlayColourScheme colourScheme)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -65,13 +72,16 @@ namespace osu.Game.Overlays.Mods
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
const float footer_height = 50;
|
|
||||||
|
|
||||||
Child = TopLevelContent = new Container
|
Child = TopLevelContent = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourProvider.Background6.Opacity(0.75f),
|
||||||
|
},
|
||||||
Header = new ShearedOverlayHeader
|
Header = new ShearedOverlayHeader
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
@ -85,34 +95,26 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Padding = new MarginPadding
|
Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Top = ShearedOverlayHeader.HEIGHT,
|
Top = ShearedOverlayHeader.HEIGHT,
|
||||||
Bottom = footer_height + PADDING,
|
Bottom = ScreenFooter.HEIGHT + PADDING,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Footer = new InputBlockingContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Depth = float.MinValue,
|
|
||||||
Height = footer_height,
|
|
||||||
Margin = new MarginPadding { Top = PADDING },
|
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = ColourProvider.Background5
|
|
||||||
},
|
|
||||||
FooterContent = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VisibilityContainer? DisplayedFooterContent { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates content to be displayed on the game-wide footer.
|
||||||
|
/// </summary>
|
||||||
|
public virtual VisibilityContainer? CreateFooterContent() => null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when the back button in the footer is pressed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether the back button should not close the overlay.</returns>
|
||||||
|
public virtual bool OnBackButton() => false;
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
if (State.Value == Visibility.Visible)
|
if (State.Value == Visibility.Visible)
|
||||||
@ -124,6 +126,9 @@ namespace osu.Game.Overlays.Mods
|
|||||||
return base.OnClick(e);
|
return base.OnClick(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IDisposable? activeOverlayRegistration;
|
||||||
|
private bool hideFooterOnPopOut;
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
const double fade_in_duration = 400;
|
const double fade_in_duration = 400;
|
||||||
@ -131,7 +136,18 @@ namespace osu.Game.Overlays.Mods
|
|||||||
this.FadeIn(fade_in_duration, Easing.OutQuint);
|
this.FadeIn(fade_in_duration, Easing.OutQuint);
|
||||||
|
|
||||||
Header.MoveToY(0, fade_in_duration, Easing.OutQuint);
|
Header.MoveToY(0, fade_in_duration, Easing.OutQuint);
|
||||||
Footer.MoveToY(0, fade_in_duration, Easing.OutQuint);
|
|
||||||
|
if (footer != null)
|
||||||
|
{
|
||||||
|
activeOverlayRegistration = footer.RegisterActiveOverlayContainer(this, out var footerContent);
|
||||||
|
DisplayedFooterContent = footerContent;
|
||||||
|
|
||||||
|
if (footer.State.Value == Visibility.Hidden)
|
||||||
|
{
|
||||||
|
footer.Show();
|
||||||
|
hideFooterOnPopOut = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PopOut()
|
protected override void PopOut()
|
||||||
@ -142,7 +158,19 @@ namespace osu.Game.Overlays.Mods
|
|||||||
this.FadeOut(fade_out_duration, Easing.OutQuint);
|
this.FadeOut(fade_out_duration, Easing.OutQuint);
|
||||||
|
|
||||||
Header.MoveToY(-Header.DrawHeight, fade_out_duration, Easing.OutQuint);
|
Header.MoveToY(-Header.DrawHeight, fade_out_duration, Easing.OutQuint);
|
||||||
Footer.MoveToY(Footer.DrawHeight, fade_out_duration, Easing.OutQuint);
|
|
||||||
|
if (footer != null)
|
||||||
|
{
|
||||||
|
activeOverlayRegistration?.Dispose();
|
||||||
|
activeOverlayRegistration = null;
|
||||||
|
DisplayedFooterContent = null;
|
||||||
|
|
||||||
|
if (hideFooterOnPopOut)
|
||||||
|
{
|
||||||
|
footer.Hide();
|
||||||
|
hideFooterOnPopOut = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
beatmapSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => !s.DeletePending), beatmapsChanged);
|
beatmapSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected), beatmapsChanged);
|
||||||
|
|
||||||
list.Items.BindTo(beatmapSets);
|
list.Items.BindTo(beatmapSets);
|
||||||
beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo.ToLive(realm), true);
|
beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo.ToLive(realm), true);
|
||||||
|
@ -133,7 +133,7 @@ namespace osu.Game.Overlays
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Logger.Log($"{nameof(MusicController)} skipping next track to {nameof(EnsurePlayingSomething)}");
|
Logger.Log($"{nameof(MusicController)} skipping next track to {nameof(EnsurePlayingSomething)}");
|
||||||
NextTrack();
|
NextTrack(allowProtectedTracks: true);
|
||||||
}
|
}
|
||||||
else if (!IsPlaying)
|
else if (!IsPlaying)
|
||||||
{
|
{
|
||||||
@ -207,9 +207,10 @@ namespace osu.Game.Overlays
|
|||||||
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="onSuccess">Invoked when the operation has been performed successfully.</param>
|
/// <param name="onSuccess">Invoked when the operation has been performed successfully.</param>
|
||||||
public void PreviousTrack(Action<PreviousTrackResult>? onSuccess = null) => Schedule(() =>
|
/// <param name="allowProtectedTracks">Whether to include <see cref="BeatmapSetInfo.Protected"/> beatmap sets when navigating.</param>
|
||||||
|
public void PreviousTrack(Action<PreviousTrackResult>? onSuccess = null, bool allowProtectedTracks = false) => Schedule(() =>
|
||||||
{
|
{
|
||||||
PreviousTrackResult res = prev();
|
PreviousTrackResult res = prev(allowProtectedTracks);
|
||||||
if (res != PreviousTrackResult.None)
|
if (res != PreviousTrackResult.None)
|
||||||
onSuccess?.Invoke(res);
|
onSuccess?.Invoke(res);
|
||||||
});
|
});
|
||||||
@ -217,8 +218,9 @@ namespace osu.Game.Overlays
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="allowProtectedTracks">Whether to include <see cref="BeatmapSetInfo.Protected"/> beatmap sets when navigating.</param>
|
||||||
/// <returns>The <see cref="PreviousTrackResult"/> that indicate the decided action.</returns>
|
/// <returns>The <see cref="PreviousTrackResult"/> that indicate the decided action.</returns>
|
||||||
private PreviousTrackResult prev()
|
private PreviousTrackResult prev(bool allowProtectedTracks)
|
||||||
{
|
{
|
||||||
if (beatmap.Disabled || !AllowTrackControl.Value)
|
if (beatmap.Disabled || !AllowTrackControl.Value)
|
||||||
return PreviousTrackResult.None;
|
return PreviousTrackResult.None;
|
||||||
@ -233,8 +235,8 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
queuedDirection = TrackChangeDirection.Prev;
|
queuedDirection = TrackChangeDirection.Prev;
|
||||||
|
|
||||||
var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault()
|
var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks)
|
||||||
?? getBeatmapSets().LastOrDefault();
|
?? getBeatmapSets().AsEnumerable().LastOrDefault(s => !s.Protected || allowProtectedTracks);
|
||||||
|
|
||||||
if (playableSet != null)
|
if (playableSet != null)
|
||||||
{
|
{
|
||||||
@ -250,10 +252,11 @@ namespace osu.Game.Overlays
|
|||||||
/// Play the next random or playlist track.
|
/// Play the next random or playlist track.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="onSuccess">Invoked when the operation has been performed successfully.</param>
|
/// <param name="onSuccess">Invoked when the operation has been performed successfully.</param>
|
||||||
|
/// <param name="allowProtectedTracks">Whether to include <see cref="BeatmapSetInfo.Protected"/> beatmap sets when navigating.</param>
|
||||||
/// <returns>A <see cref="ScheduledDelegate"/> of the operation.</returns>
|
/// <returns>A <see cref="ScheduledDelegate"/> of the operation.</returns>
|
||||||
public void NextTrack(Action? onSuccess = null) => Schedule(() =>
|
public void NextTrack(Action? onSuccess = null, bool allowProtectedTracks = false) => Schedule(() =>
|
||||||
{
|
{
|
||||||
bool res = next();
|
bool res = next(allowProtectedTracks);
|
||||||
if (res)
|
if (res)
|
||||||
onSuccess?.Invoke();
|
onSuccess?.Invoke();
|
||||||
});
|
});
|
||||||
@ -306,15 +309,15 @@ namespace osu.Game.Overlays
|
|||||||
Scheduler.AddDelayed(() => duckOperation.Dispose(), delayUntilRestore);
|
Scheduler.AddDelayed(() => duckOperation.Dispose(), delayUntilRestore);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool next()
|
private bool next(bool allowProtectedTracks)
|
||||||
{
|
{
|
||||||
if (beatmap.Disabled || !AllowTrackControl.Value)
|
if (beatmap.Disabled || !AllowTrackControl.Value)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
queuedDirection = TrackChangeDirection.Next;
|
queuedDirection = TrackChangeDirection.Next;
|
||||||
|
|
||||||
var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo)).ElementAtOrDefault(1)
|
var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) || (i.Protected && !allowProtectedTracks)).ElementAtOrDefault(1)
|
||||||
?? getBeatmapSets().FirstOrDefault();
|
?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks);
|
||||||
|
|
||||||
var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault();
|
var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault();
|
||||||
|
|
||||||
@ -432,7 +435,7 @@ namespace osu.Game.Overlays
|
|||||||
private void onTrackCompleted()
|
private void onTrackCompleted()
|
||||||
{
|
{
|
||||||
if (!CurrentTrack.Looping && !beatmap.Disabled && AllowTrackControl.Value)
|
if (!CurrentTrack.Looping && !beatmap.Disabled && AllowTrackControl.Value)
|
||||||
NextTrack();
|
NextTrack(allowProtectedTracks: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool applyModTrackAdjustments;
|
private bool applyModTrackAdjustments;
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Overlays
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// All notifications currently being displayed by the toast tray.
|
/// All notifications currently being displayed by the toast tray.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<Notification> Notifications => toastFlow;
|
public IEnumerable<Notification> Notifications => toastFlow.Concat(InternalChildren.OfType<Notification>());
|
||||||
|
|
||||||
public bool IsDisplayingToasts => toastFlow.Count > 0;
|
public bool IsDisplayingToasts => toastFlow.Count > 0;
|
||||||
|
|
||||||
@ -43,12 +43,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
public Action<Notification>? ForwardNotificationToPermanentStore { get; set; }
|
public Action<Notification>? ForwardNotificationToPermanentStore { get; set; }
|
||||||
|
|
||||||
public int UnreadCount => allDisplayedNotifications.Count(n => !n.WasClosed && !n.Read);
|
public int UnreadCount => Notifications.Count(n => !n.WasClosed && !n.Read);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notifications contained in the toast flow, or in a detached state while they animate during forwarding to the main overlay.
|
|
||||||
/// </summary>
|
|
||||||
private IEnumerable<Notification> allDisplayedNotifications => toastFlow.Concat(InternalChildren.OfType<Notification>());
|
|
||||||
|
|
||||||
private int runningDepth;
|
private int runningDepth;
|
||||||
|
|
||||||
@ -91,11 +86,7 @@ namespace osu.Game.Overlays
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MarkAllRead()
|
public void MarkAllRead() => Notifications.ForEach(n => n.Read = true);
|
||||||
{
|
|
||||||
toastFlow.Children.ForEach(n => n.Read = true);
|
|
||||||
InternalChildren.OfType<Notification>().ForEach(n => n.Read = true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FlushAllToasts()
|
public void FlushAllToasts()
|
||||||
{
|
{
|
||||||
|
@ -9,11 +9,11 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
public class OverlayColourProvider
|
public class OverlayColourProvider
|
||||||
{
|
{
|
||||||
private readonly OverlayColourScheme colourScheme;
|
public OverlayColourScheme ColourScheme { get; private set; }
|
||||||
|
|
||||||
public OverlayColourProvider(OverlayColourScheme colourScheme)
|
public OverlayColourProvider(OverlayColourScheme colourScheme)
|
||||||
{
|
{
|
||||||
this.colourScheme = colourScheme;
|
ColourScheme = colourScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that the following five colours are also defined in `OsuColour` as `{colourScheme}{0,1,2,3,4}`.
|
// Note that the following five colours are also defined in `OsuColour` as `{colourScheme}{0,1,2,3,4}`.
|
||||||
@ -47,7 +47,17 @@ namespace osu.Game.Overlays
|
|||||||
public Color4 Background5 => getColour(0.1f, 0.15f);
|
public Color4 Background5 => getColour(0.1f, 0.15f);
|
||||||
public Color4 Background6 => getColour(0.1f, 0.1f);
|
public Color4 Background6 => getColour(0.1f, 0.1f);
|
||||||
|
|
||||||
private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(colourScheme), saturation, lightness, 1));
|
/// <summary>
|
||||||
|
/// Changes the value of <see cref="ColourScheme"/> to a different colour scheme.
|
||||||
|
/// Note that this does not trigger any kind of signal to any drawable that received colours from here, all drawables need to be updated manually.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="colourScheme">The proposed colour scheme.</param>
|
||||||
|
public void ChangeColourScheme(OverlayColourScheme colourScheme)
|
||||||
|
{
|
||||||
|
ColourScheme = colourScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(ColourScheme), saturation, lightness, 1));
|
||||||
|
|
||||||
// See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628
|
// See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628
|
||||||
private static float getBaseHue(OverlayColourScheme colourScheme)
|
private static float getBaseHue(OverlayColourScheme colourScheme)
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -112,8 +114,12 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
public Bindable<float?> LastScrollTarget = new Bindable<float?>();
|
public Bindable<float?> LastScrollTarget = new Bindable<float?>();
|
||||||
|
|
||||||
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||||
|
|
||||||
|
private Sample scrollToTopSample;
|
||||||
|
private Sample scrollToPreviousSample;
|
||||||
|
|
||||||
public ScrollBackButton()
|
public ScrollBackButton()
|
||||||
: base(HoverSampleSet.ScrollToTop)
|
|
||||||
{
|
{
|
||||||
Size = new Vector2(50);
|
Size = new Vector2(50);
|
||||||
Alpha = 0;
|
Alpha = 0;
|
||||||
@ -150,11 +156,14 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider)
|
private void load(OverlayColourProvider colourProvider, AudioManager audio)
|
||||||
{
|
{
|
||||||
IdleColour = colourProvider.Background6;
|
IdleColour = colourProvider.Background6;
|
||||||
HoverColour = colourProvider.Background5;
|
HoverColour = colourProvider.Background5;
|
||||||
flashColour = colourProvider.Light1;
|
flashColour = colourProvider.Light1;
|
||||||
|
|
||||||
|
scrollToTopSample = audio.Samples.Get(@"UI/scroll-to-top");
|
||||||
|
scrollToPreviousSample = audio.Samples.Get(@"UI/scroll-to-previous");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -171,6 +180,12 @@ namespace osu.Game.Overlays
|
|||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
background.FlashColour(flashColour, 800, Easing.OutQuint);
|
background.FlashColour(flashColour, 800, Easing.OutQuint);
|
||||||
|
|
||||||
|
if (LastScrollTarget.Value == null)
|
||||||
|
scrollToTopSample?.Play();
|
||||||
|
else
|
||||||
|
scrollToPreviousSample?.Play();
|
||||||
|
|
||||||
return base.OnClick(e);
|
return base.OnClick(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -19,7 +17,6 @@ using osu.Game.Input.Bindings;
|
|||||||
using osu.Game.Overlays.Volume;
|
using osu.Game.Overlays.Volume;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays
|
namespace osu.Game.Overlays
|
||||||
{
|
{
|
||||||
@ -27,17 +24,17 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
private const float offset = 10;
|
private const float offset = 10;
|
||||||
|
|
||||||
private VolumeMeter volumeMeterMaster;
|
private VolumeMeter volumeMeterMaster = null!;
|
||||||
private VolumeMeter volumeMeterEffect;
|
private VolumeMeter volumeMeterEffect = null!;
|
||||||
private VolumeMeter volumeMeterMusic;
|
private VolumeMeter volumeMeterMusic = null!;
|
||||||
private MuteButton muteButton;
|
private MuteButton muteButton = null!;
|
||||||
|
|
||||||
|
private SelectionCycleFillFlowContainer<VolumeMeter> volumeMeters = null!;
|
||||||
|
|
||||||
private readonly BindableDouble muteAdjustment = new BindableDouble();
|
private readonly BindableDouble muteAdjustment = new BindableDouble();
|
||||||
|
|
||||||
public Bindable<bool> IsMuted { get; } = new Bindable<bool>();
|
public Bindable<bool> IsMuted { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
private SelectionCycleFillFlowContainer<VolumeMeter> volumeMeters;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio, OsuColour colours)
|
private void load(AudioManager audio, OsuColour colours)
|
||||||
{
|
{
|
||||||
@ -140,8 +137,6 @@ namespace osu.Game.Overlays
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledDelegate popOutDelegate;
|
|
||||||
|
|
||||||
public void FocusMasterVolume()
|
public void FocusMasterVolume()
|
||||||
{
|
{
|
||||||
volumeMeters.Select(volumeMeterMaster);
|
volumeMeters.Select(volumeMeterMaster);
|
||||||
@ -179,30 +174,6 @@ namespace osu.Game.Overlays
|
|||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
|
||||||
{
|
|
||||||
switch (e.Key)
|
|
||||||
{
|
|
||||||
case Key.Left:
|
|
||||||
Adjust(GlobalAction.PreviousVolumeMeter);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case Key.Right:
|
|
||||||
Adjust(GlobalAction.NextVolumeMeter);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case Key.Down:
|
|
||||||
Adjust(GlobalAction.DecreaseVolume);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case Key.Up:
|
|
||||||
Adjust(GlobalAction.IncreaseVolume);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.OnKeyDown(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
schedulePopOut();
|
schedulePopOut();
|
||||||
@ -215,6 +186,8 @@ namespace osu.Game.Overlays
|
|||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ScheduledDelegate? popOutDelegate;
|
||||||
|
|
||||||
private void schedulePopOut()
|
private void schedulePopOut()
|
||||||
{
|
{
|
||||||
popOutDelegate?.Cancel();
|
popOutDelegate?.Cancel();
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
|
||||||
FillFlow.Spacing = new Vector2(5);
|
FillFlow.Spacing = new Vector2(5);
|
||||||
Padding = new MarginPadding { Vertical = 5 };
|
FillFlow.Padding = new MarginPadding { Vertical = 5 };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && anyToolboxHovered(screenSpacePos);
|
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && anyToolboxHovered(screenSpacePos);
|
||||||
|
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
PlayfieldContentContainer = new ContentContainer
|
PlayfieldContentContainer = new Container
|
||||||
{
|
{
|
||||||
Name = "Playfield content",
|
Name = "Playfield content",
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
@ -269,6 +269,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
composerFocusMode.BindValueChanged(_ =>
|
composerFocusMode.BindValueChanged(_ =>
|
||||||
{
|
{
|
||||||
|
// Transforms should be kept in sync with other usages of composer focus mode.
|
||||||
if (!composerFocusMode.Value)
|
if (!composerFocusMode.Value)
|
||||||
{
|
{
|
||||||
leftToolboxBackground.FadeIn(750, Easing.OutQuint);
|
leftToolboxBackground.FadeIn(750, Easing.OutQuint);
|
||||||
@ -303,6 +304,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
PlayfieldContentContainer.Width = Math.Max(1024, DrawWidth) - (TOOLBOX_CONTRACTED_SIZE_LEFT + TOOLBOX_CONTRACTED_SIZE_RIGHT);
|
PlayfieldContentContainer.Width = Math.Max(1024, DrawWidth) - (TOOLBOX_CONTRACTED_SIZE_LEFT + TOOLBOX_CONTRACTED_SIZE_RIGHT);
|
||||||
PlayfieldContentContainer.X = TOOLBOX_CONTRACTED_SIZE_LEFT;
|
PlayfieldContentContainer.X = TOOLBOX_CONTRACTED_SIZE_LEFT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Playfield Playfield => drawableRulesetWrapper.Playfield;
|
public override Playfield Playfield => drawableRulesetWrapper.Playfield;
|
||||||
@ -529,31 +532,6 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private partial class ContentContainer : Container
|
|
||||||
{
|
|
||||||
public override bool HandlePositionalInput => true;
|
|
||||||
|
|
||||||
private readonly Bindable<bool> composerFocusMode = new Bindable<bool>();
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
|
||||||
private void load([CanBeNull] Editor editor)
|
|
||||||
{
|
|
||||||
if (editor != null)
|
|
||||||
composerFocusMode.BindTo(editor.ComposerFocusMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
|
||||||
{
|
|
||||||
composerFocusMode.Value = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
|
||||||
{
|
|
||||||
composerFocusMode.Value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -394,13 +394,12 @@ namespace osu.Game.Rulesets
|
|||||||
public virtual IRulesetFilterCriteria? CreateRulesetFilterCriteria() => null;
|
public virtual IRulesetFilterCriteria? CreateRulesetFilterCriteria() => null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Can be overridden to add a ruleset-specific section to the editor beatmap setup screen.
|
/// Can be overridden to add ruleset-specific sections to the editor beatmap setup screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual RulesetSetupSection? CreateEditorSetupSection() => null;
|
public virtual IEnumerable<SetupSection> CreateEditorSetupSections() =>
|
||||||
|
[
|
||||||
/// <summary>
|
new DifficultySection(),
|
||||||
/// Can be overridden to alter the difficulty section to the editor beatmap setup screen.
|
new ColoursSection(),
|
||||||
/// </summary>
|
];
|
||||||
public virtual SetupSection? CreateEditorDifficultySection() => null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,10 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Scoring.Legacy;
|
using osu.Game.Scoring.Legacy;
|
||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
@ -214,6 +214,7 @@ namespace osu.Game.Scoring
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Task<Live<ScoreInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original);
|
public Task<Live<ScoreInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original);
|
||||||
|
public Task<ExternalEditOperation<ScoreInfo>> BeginExternalEditing(ScoreInfo model) => scoreImporter.BeginExternalEditing(model);
|
||||||
|
|
||||||
public Live<ScoreInfo>? Import(ScoreInfo item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) =>
|
public Live<ScoreInfo>? Import(ScoreInfo item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) =>
|
||||||
scoreImporter.ImportModel(item, archive, parameters, cancellationToken);
|
scoreImporter.ImportModel(item, archive, parameters, cancellationToken);
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
Height = 60;
|
Height = 50;
|
||||||
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
EdgeEffect = new EdgeEffectParameters
|
EdgeEffect = new EdgeEffectParameters
|
||||||
@ -48,7 +48,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
ColumnDimensions = new[]
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.Absolute, 170),
|
new Dimension(GridSizeMode.Absolute, 150),
|
||||||
new Dimension(),
|
new Dimension(),
|
||||||
new Dimension(GridSizeMode.Absolute, 220),
|
new Dimension(GridSizeMode.Absolute, 220),
|
||||||
new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT),
|
new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT),
|
||||||
@ -82,6 +82,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
saveInProgress.BindValueChanged(_ => TestGameplayButton.Enabled.Value = !saveInProgress.Value, true);
|
saveInProgress.BindValueChanged(_ => TestGameplayButton.Enabled.Value = !saveInProgress.Value, true);
|
||||||
composerFocusMode.BindValueChanged(_ =>
|
composerFocusMode.BindValueChanged(_ =>
|
||||||
{
|
{
|
||||||
|
// Transforms should be kept in sync with other usages of composer focus mode.
|
||||||
foreach (var c in this.ChildrenOfType<BottomBarContainer>())
|
foreach (var c in this.ChildrenOfType<BottomBarContainer>())
|
||||||
{
|
{
|
||||||
if (!composerFocusMode.Value)
|
if (!composerFocusMode.Value)
|
||||||
|
@ -46,8 +46,8 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Scale = new Vector2(1.4f),
|
Scale = new Vector2(1.2f),
|
||||||
IconScale = new Vector2(1.4f),
|
IconScale = new Vector2(1.2f),
|
||||||
Icon = FontAwesome.Regular.PlayCircle,
|
Icon = FontAwesome.Regular.PlayCircle,
|
||||||
Action = togglePause,
|
Action = togglePause,
|
||||||
},
|
},
|
||||||
|
@ -37,8 +37,8 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
{
|
{
|
||||||
Colour = colours.Orange1,
|
Colour = colours.Orange1,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Font = OsuFont.Torus.With(size: 18, weight: FontWeight.SemiBold),
|
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
||||||
Position = new Vector2(2, 5),
|
Position = new Vector2(2, 4),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -87,7 +87,8 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding
|
Padding = new MarginPadding
|
||||||
{
|
{
|
||||||
Top = 5,
|
Top = 4,
|
||||||
|
Bottom = 1,
|
||||||
Horizontal = -2
|
Horizontal = -2
|
||||||
},
|
},
|
||||||
Child = new Container
|
Child = new Container
|
||||||
@ -107,12 +108,13 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Spacing = new Vector2(-2, 0),
|
Spacing = new Vector2(-2, 0),
|
||||||
Font = OsuFont.Torus.With(size: 36, fixedWidth: true, weight: FontWeight.Light),
|
Font = OsuFont.Torus.With(size: 32, fixedWidth: true, weight: FontWeight.Light),
|
||||||
},
|
},
|
||||||
inputTextBox = new OsuTextBox
|
inputTextBox = new TimestampTextBox
|
||||||
{
|
{
|
||||||
Width = 150,
|
Position = new Vector2(-2, 4),
|
||||||
Height = 36,
|
Width = 128,
|
||||||
|
Height = 26,
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
CommitOnFocusLost = true,
|
CommitOnFocusLost = true,
|
||||||
},
|
},
|
||||||
@ -160,6 +162,14 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
showingHoverLayer = shouldShowHoverLayer;
|
showingHoverLayer = shouldShowHoverLayer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class TimestampTextBox : OsuTextBox
|
||||||
|
{
|
||||||
|
public TimestampTextBox()
|
||||||
|
{
|
||||||
|
TextContainer.Height = 0.8f;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
|
using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
|
||||||
|
|
||||||
@ -19,7 +22,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
Add(new BookmarkVisualisation(bookmark));
|
Add(new BookmarkVisualisation(bookmark));
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class BookmarkVisualisation : PointVisualisation
|
private partial class BookmarkVisualisation : PointVisualisation, IHasTooltip
|
||||||
{
|
{
|
||||||
public BookmarkVisualisation(double startTime)
|
public BookmarkVisualisation(double startTime)
|
||||||
: base(startTime)
|
: base(startTime)
|
||||||
@ -28,6 +31,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours) => Colour = colours.Blue;
|
private void load(OsuColour colours) => Colour = colours.Blue;
|
||||||
|
|
||||||
|
public LocalisableString TooltipText => $"{StartTime.ToEditorFormattedString()} bookmark";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,12 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
||||||
@ -17,32 +21,59 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
{
|
{
|
||||||
private readonly BindableList<BreakPeriod> breaks = new BindableList<BreakPeriod>();
|
private readonly BindableList<BreakPeriod> breaks = new BindableList<BreakPeriod>();
|
||||||
|
|
||||||
|
private DrawablePool<BreakVisualisation> pool = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AddInternal(pool = new DrawablePool<BreakVisualisation>(10));
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadBeatmap(EditorBeatmap beatmap)
|
protected override void LoadBeatmap(EditorBeatmap beatmap)
|
||||||
{
|
{
|
||||||
base.LoadBeatmap(beatmap);
|
base.LoadBeatmap(beatmap);
|
||||||
|
|
||||||
breaks.UnbindAll();
|
breaks.UnbindAll();
|
||||||
breaks.BindTo(beatmap.Breaks);
|
breaks.BindTo(beatmap.Breaks);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
breaks.BindCollectionChanged((_, _) =>
|
breaks.BindCollectionChanged((_, _) =>
|
||||||
{
|
{
|
||||||
Clear();
|
Clear(disposeChildren: false);
|
||||||
foreach (var breakPeriod in beatmap.Breaks)
|
foreach (var breakPeriod in breaks)
|
||||||
Add(new BreakVisualisation(breakPeriod));
|
Add(pool.Get(v => v.BreakPeriod = breakPeriod));
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class BreakVisualisation : Circle
|
private partial class BreakVisualisation : PoolableDrawable, IHasTooltip
|
||||||
{
|
{
|
||||||
public BreakVisualisation(BreakPeriod breakPeriod)
|
private BreakPeriod breakPeriod = null!;
|
||||||
|
|
||||||
|
public BreakPeriod BreakPeriod
|
||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.X;
|
set
|
||||||
RelativeSizeAxes = Axes.Both;
|
{
|
||||||
X = (float)breakPeriod.StartTime;
|
breakPeriod = value;
|
||||||
Width = (float)breakPeriod.Duration;
|
X = (float)value.StartTime;
|
||||||
|
Width = (float)value.Duration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours) => Colour = colours.Gray7;
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
RelativePositionAxes = Axes.X;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = new Circle { RelativeSizeAxes = Axes.Both };
|
||||||
|
Colour = colours.Gray6;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalisableString TooltipText => $"{breakPeriod.StartTime.ToEditorFormattedString()} - {breakPeriod.EndTime.ToEditorFormattedString()} break time";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,23 +2,27 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
|
using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
||||||
{
|
{
|
||||||
public partial class ControlPointVisualisation : PointVisualisation, IControlPointVisualisation
|
public partial class ControlPointVisualisation : PointVisualisation, IControlPointVisualisation, IHasTooltip
|
||||||
{
|
{
|
||||||
protected readonly ControlPoint Point;
|
protected readonly ControlPoint Point;
|
||||||
|
|
||||||
public ControlPointVisualisation(ControlPoint point)
|
public ControlPointVisualisation(ControlPoint point)
|
||||||
|
: base(point.Time)
|
||||||
{
|
{
|
||||||
Point = point;
|
Point = point;
|
||||||
|
Alpha = 0.5f;
|
||||||
Height = 0.25f;
|
Blending = BlendingParameters.Additive;
|
||||||
Origin = Anchor.TopCentre;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -28,5 +32,22 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool IsVisuallyRedundant(ControlPoint other) => other.GetType() == Point.GetType();
|
public bool IsVisuallyRedundant(ControlPoint other) => other.GetType() == Point.GetType();
|
||||||
|
|
||||||
|
public LocalisableString TooltipText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (Point)
|
||||||
|
{
|
||||||
|
case EffectControlPoint effect:
|
||||||
|
return $"{StartTime.ToEditorFormattedString()} effect [{effect.ScrollSpeed:N2}x scroll{(effect.KiaiMode ? " kiai" : "")}]";
|
||||||
|
|
||||||
|
case TimingControlPoint timing:
|
||||||
|
return $"{StartTime.ToEditorFormattedString()} timing [{timing.BPM:N2} bpm {timing.TimeSignature.GetDescription()}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,14 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
||||||
{
|
{
|
||||||
@ -91,18 +93,32 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
|
|
||||||
Width = (float)(nextControlPoint.Time - effect.Time);
|
Width = (float)(nextControlPoint.Time - effect.Time);
|
||||||
|
|
||||||
AddInternal(new PointVisualisation
|
AddInternal(new KiaiVisualisation(effect.Time, nextControlPoint.Time)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.TopLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Width = 1,
|
Origin = Anchor.CentreLeft,
|
||||||
Height = 0.25f,
|
Height = 0.4f,
|
||||||
Depth = float.MaxValue,
|
Depth = float.MaxValue,
|
||||||
Colour = effect.GetRepresentingColour(colours).Darken(0.5f),
|
Colour = colours.Purple1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class KiaiVisualisation : Circle, IHasTooltip
|
||||||
|
{
|
||||||
|
private readonly double startTime;
|
||||||
|
private readonly double endTime;
|
||||||
|
|
||||||
|
public KiaiVisualisation(double startTime, double endTime)
|
||||||
|
{
|
||||||
|
this.startTime = startTime;
|
||||||
|
this.endTime = endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalisableString TooltipText => $"{startTime.ToEditorFormattedString()} - {endTime.ToEditorFormattedString()} kiai time";
|
||||||
|
}
|
||||||
|
|
||||||
// kiai sections display duration, so are required to be visualised.
|
// kiai sections display duration, so are required to be visualised.
|
||||||
public bool IsVisuallyRedundant(ControlPoint other) => other is EffectControlPoint otherEffect && effect.KiaiMode == otherEffect.KiaiMode;
|
public bool IsVisuallyRedundant(ControlPoint other) => other is EffectControlPoint otherEffect && effect.KiaiMode == otherEffect.KiaiMode;
|
||||||
}
|
}
|
||||||
|
@ -39,19 +39,16 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
switch (point)
|
switch (point)
|
||||||
{
|
{
|
||||||
case TimingControlPoint:
|
case TimingControlPoint:
|
||||||
AddInternal(new ControlPointVisualisation(point) { Y = 0, });
|
AddInternal(new ControlPointVisualisation(point)
|
||||||
break;
|
{
|
||||||
|
// importantly, override the x position being set since we do that above.
|
||||||
case DifficultyControlPoint:
|
X = 0,
|
||||||
AddInternal(new ControlPointVisualisation(point) { Y = 0.25f, });
|
Y = -0.4f,
|
||||||
break;
|
});
|
||||||
|
|
||||||
case SampleControlPoint:
|
|
||||||
AddInternal(new ControlPointVisualisation(point) { Y = 0.5f, });
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EffectControlPoint effect:
|
case EffectControlPoint effect:
|
||||||
AddInternal(new EffectPointVisualisation(effect) { Y = 0.75f });
|
AddInternal(new EffectPointVisualisation(effect));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Overlays;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
||||||
@ -73,8 +73,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
{
|
{
|
||||||
public MarkerVisualisation()
|
public MarkerVisualisation()
|
||||||
{
|
{
|
||||||
const float box_height = 4;
|
|
||||||
|
|
||||||
Anchor = Anchor.CentreLeft;
|
Anchor = Anchor.CentreLeft;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
RelativePositionAxes = Axes.X;
|
RelativePositionAxes = Axes.X;
|
||||||
@ -82,32 +80,18 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
AutoSizeAxes = Axes.X;
|
AutoSizeAxes = Axes.X;
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Size = new Vector2(14, box_height),
|
|
||||||
},
|
|
||||||
new Triangle
|
new Triangle
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
Scale = new Vector2(1, -1),
|
Scale = new Vector2(1, -1),
|
||||||
Size = new Vector2(10, 5),
|
Size = new Vector2(10, 5),
|
||||||
Y = box_height,
|
|
||||||
},
|
},
|
||||||
new Triangle
|
new Triangle
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
Size = new Vector2(10, 5),
|
Size = new Vector2(10, 5),
|
||||||
Y = -box_height,
|
|
||||||
},
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
Size = new Vector2(14, box_height),
|
|
||||||
},
|
},
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
@ -121,7 +105,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours) => Colour = colours.Red1;
|
private void load(OverlayColourProvider colours) => Colour = colours.Highlight1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
|
using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
|
||||||
|
|
||||||
@ -27,15 +30,18 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class PreviewTimeVisualisation : PointVisualisation
|
private partial class PreviewTimeVisualisation : PointVisualisation, IHasTooltip
|
||||||
{
|
{
|
||||||
public PreviewTimeVisualisation(double time)
|
public PreviewTimeVisualisation(double time)
|
||||||
: base(time)
|
: base(time)
|
||||||
{
|
{
|
||||||
|
Alpha = 0.8f;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours) => Colour = colours.Green1;
|
private void load(OsuColour colours) => Colour = colours.Green1;
|
||||||
|
|
||||||
|
public LocalisableString TooltipText => $"{StartTime.ToEditorFormattedString()} preview time";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,29 +23,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
|
|||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new MarkerPart { RelativeSizeAxes = Axes.Both },
|
|
||||||
new ControlPointPart
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Y = -10,
|
|
||||||
Height = 0.35f
|
|
||||||
},
|
|
||||||
new BookmarkPart
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Height = 0.35f
|
|
||||||
},
|
|
||||||
new PreviewTimePart
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Height = 0.35f
|
|
||||||
},
|
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
Name = "centre line",
|
Name = "centre line",
|
||||||
@ -75,13 +52,35 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
new PreviewTimePart
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0.4f,
|
||||||
|
},
|
||||||
|
new ControlPointPart
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0.4f
|
||||||
|
},
|
||||||
|
new BookmarkPart
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0.4f
|
||||||
|
},
|
||||||
new BreakPart
|
new BreakPart
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Height = 0.10f
|
Height = 0.15f
|
||||||
}
|
},
|
||||||
|
new MarkerPart { RelativeSizeAxes = Axes.Both },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,15 +11,11 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class PointVisualisation : Circle
|
public partial class PointVisualisation : Circle
|
||||||
{
|
{
|
||||||
|
public readonly double StartTime;
|
||||||
|
|
||||||
public const float MAX_WIDTH = 4;
|
public const float MAX_WIDTH = 4;
|
||||||
|
|
||||||
public PointVisualisation(double startTime)
|
public PointVisualisation(double startTime)
|
||||||
: this()
|
|
||||||
{
|
|
||||||
X = (float)startTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PointVisualisation()
|
|
||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.Both;
|
RelativePositionAxes = Axes.Both;
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
@ -28,7 +24,10 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Width = MAX_WIDTH;
|
Width = MAX_WIDTH;
|
||||||
Height = 0.75f;
|
Height = 0.4f;
|
||||||
|
|
||||||
|
X = (float)startTime;
|
||||||
|
StartTime = startTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
new TextFlowContainer(s => s.Font = s.Font.With(size: 14))
|
new TextFlowContainer(s => s.Font = s.Font.With(size: 14))
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding { Horizontal = 15, Vertical = 8 },
|
Padding = new MarginPadding { Horizontal = 15, Vertical = 2 },
|
||||||
Text = "beat snap",
|
Text = "beat snap",
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
TextAnchor = Anchor.TopCentre,
|
TextAnchor = Anchor.TopCentre,
|
||||||
@ -159,7 +159,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
},
|
},
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.Absolute, 30),
|
new Dimension(GridSizeMode.Absolute, 40),
|
||||||
new Dimension(GridSizeMode.Absolute, 20),
|
new Dimension(GridSizeMode.Absolute, 20),
|
||||||
new Dimension(GridSizeMode.Absolute, 15)
|
new Dimension(GridSizeMode.Absolute, 15)
|
||||||
}
|
}
|
||||||
@ -526,7 +526,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
AlwaysDisplayed = alwaysDisplayed;
|
AlwaysDisplayed = alwaysDisplayed;
|
||||||
Divisor = divisor;
|
Divisor = divisor;
|
||||||
|
|
||||||
Size = new Vector2(6f, 12) * BindableBeatDivisor.GetSize(divisor);
|
Size = new Vector2(6f, 18) * BindableBeatDivisor.GetSize(divisor);
|
||||||
Alpha = alwaysDisplayed ? 1 : 0;
|
Alpha = alwaysDisplayed ? 1 : 0;
|
||||||
|
|
||||||
InternalChild = new Box { RelativeSizeAxes = Axes.Both };
|
InternalChild = new Box { RelativeSizeAxes = Axes.Both };
|
||||||
|
@ -9,6 +9,7 @@ using Humanizer;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@ -405,5 +406,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
CommitIfPlacementActive();
|
CommitIfPlacementActive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (Beatmap.IsNotNull())
|
||||||
|
Beatmap.HitObjectAdded -= hitObjectAdded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,10 +85,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
SelectionBox = CreateSelectionBox(),
|
SelectionBox = CreateSelectionBox(),
|
||||||
});
|
});
|
||||||
|
|
||||||
SelectedItems.CollectionChanged += (_, _) =>
|
SelectedItems.BindCollectionChanged((_, _) => Scheduler.AddOnce(updateVisibility), true);
|
||||||
{
|
|
||||||
Scheduler.AddOnce(updateVisibility);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SelectionBox CreateSelectionBox()
|
public SelectionBox CreateSelectionBox()
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Overlays;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
@ -23,7 +25,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.TopCentre;
|
||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colours)
|
||||||
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
@ -32,21 +38,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Width = bar_width,
|
Width = bar_width,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Colour = ColourInfo.GradientVertical(colours.Colour2.Opacity(0.6f), colours.Colour2.Opacity(0)),
|
||||||
},
|
},
|
||||||
new Triangle
|
new Triangle
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
Size = new Vector2(triangle_width, triangle_width * 0.8f),
|
Size = new Vector2(triangle_width, triangle_width * 0.8f),
|
||||||
Scale = new Vector2(1, -1)
|
Scale = new Vector2(1, -1),
|
||||||
|
Colour = colours.Colour2,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours)
|
|
||||||
{
|
|
||||||
Colour = colours.Red1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
public DifficultyPointPiece(HitObject hitObject)
|
public DifficultyPointPiece(HitObject hitObject)
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
|
Y = -2.5f;
|
||||||
|
|
||||||
speedMultiplier = (hitObject as IHasSliderVelocity)?.SliderVelocityMultiplierBindable.GetBoundCopy();
|
speedMultiplier = (hitObject as IHasSliderVelocity)?.SliderVelocityMultiplierBindable.GetBoundCopy();
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
public SamplePointPiece(HitObject hitObject)
|
public SamplePointPiece(HitObject hitObject)
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
|
Y = 2.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AlternativeColor { get; init; }
|
public bool AlternativeColor { get; init; }
|
||||||
@ -148,10 +149,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
bank = new LabelledTextBox
|
bank = new LabelledTextBox
|
||||||
{
|
{
|
||||||
Label = "Bank Name",
|
Label = "Bank Name",
|
||||||
|
SelectAllOnFocus = true,
|
||||||
},
|
},
|
||||||
additionBank = new LabelledTextBox
|
additionBank = new LabelledTextBox
|
||||||
{
|
{
|
||||||
Label = "Addition Bank",
|
Label = "Addition Bank",
|
||||||
|
SelectAllOnFocus = true,
|
||||||
},
|
},
|
||||||
volume = new IndeterminateSliderWithTextBoxInput<int>("Volume", new BindableInt(100)
|
volume = new IndeterminateSliderWithTextBoxInput<int>("Volume", new BindableInt(100)
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
[Cached]
|
[Cached]
|
||||||
public partial class Timeline : ZoomableScrollContainer, IPositionSnapProvider
|
public partial class Timeline : ZoomableScrollContainer, IPositionSnapProvider
|
||||||
{
|
{
|
||||||
private const float timeline_height = 72;
|
private const float timeline_height = 80;
|
||||||
private const float timeline_expanded_height = 94;
|
private const float timeline_expanded_height = 94;
|
||||||
|
|
||||||
private readonly Drawable userContent;
|
private readonly Drawable userContent;
|
||||||
@ -97,6 +97,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
// We don't want the centre marker to scroll
|
// We don't want the centre marker to scroll
|
||||||
AddInternal(centreMarker = new CentreMarker());
|
AddInternal(centreMarker = new CentreMarker());
|
||||||
|
|
||||||
|
ticks = new TimelineTickDisplay
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding { Vertical = 2, },
|
||||||
|
};
|
||||||
|
|
||||||
AddRange(new Drawable[]
|
AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
controlPoints = new TimelineControlPointDisplay
|
controlPoints = new TimelineControlPointDisplay
|
||||||
@ -104,6 +109,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = timeline_expanded_height,
|
Height = timeline_expanded_height,
|
||||||
},
|
},
|
||||||
|
ticks,
|
||||||
mainContent = new Container
|
mainContent = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -119,8 +125,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
MidColour = colours.BlueDark,
|
MidColour = colours.BlueDark,
|
||||||
HighColour = colours.BlueDarker,
|
HighColour = colours.BlueDarker,
|
||||||
},
|
},
|
||||||
|
ticks.CreateProxy(),
|
||||||
centreMarker.CreateProxy(),
|
centreMarker.CreateProxy(),
|
||||||
ticks = new TimelineTickDisplay(),
|
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
Name = "zero marker",
|
Name = "zero marker",
|
||||||
@ -175,7 +181,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
if (visible.NewValue)
|
if (visible.NewValue)
|
||||||
{
|
{
|
||||||
this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint);
|
this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint);
|
||||||
mainContent.MoveToY(20, 200, Easing.OutQuint);
|
mainContent.MoveToY(15, 200, Easing.OutQuint);
|
||||||
|
|
||||||
// delay the fade in else masking looks weird.
|
// delay the fade in else masking looks weird.
|
||||||
controlPoints.Delay(180).FadeIn(400, Easing.OutQuint);
|
controlPoints.Delay(180).FadeIn(400, Easing.OutQuint);
|
||||||
|
@ -134,6 +134,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
composerFocusMode.BindValueChanged(_ =>
|
composerFocusMode.BindValueChanged(_ =>
|
||||||
{
|
{
|
||||||
|
// Transforms should be kept in sync with other usages of composer focus mode.
|
||||||
if (!composerFocusMode.Value)
|
if (!composerFocusMode.Value)
|
||||||
timelineBackground.FadeIn(750, Easing.OutQuint);
|
timelineBackground.FadeIn(750, Easing.OutQuint);
|
||||||
else
|
else
|
||||||
|
@ -29,11 +29,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
public Action<BreakPeriod>? OnDeleted { get; init; }
|
public Action<BreakPeriod>? OnDeleted { get; init; }
|
||||||
|
|
||||||
private Box background = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OsuColour colours { get; set; } = null!;
|
|
||||||
|
|
||||||
public TimelineBreak(BreakPeriod b)
|
public TimelineBreak(BreakPeriod b)
|
||||||
{
|
{
|
||||||
Break.Value = b;
|
Break.Value = b;
|
||||||
@ -53,11 +48,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Horizontal = 5 },
|
Padding = new MarginPadding { Horizontal = 5 },
|
||||||
Child = background = new Box
|
Child = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colours.Gray5,
|
Colour = colours.Gray5,
|
||||||
Alpha = 0.7f,
|
Alpha = 0.9f,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new DragHandle(isStartHandle: true)
|
new DragHandle(isStartHandle: true)
|
||||||
@ -88,23 +83,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
|
||||||
{
|
|
||||||
updateState();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
|
||||||
{
|
|
||||||
updateState();
|
|
||||||
base.OnHoverLost(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateState()
|
|
||||||
{
|
|
||||||
background.FadeColour(IsHovered ? colours.Gray6 : colours.Gray5, 400, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MenuItem[] ContextMenuItems => new MenuItem[]
|
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||||
{
|
{
|
||||||
new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => OnDeleted?.Invoke(Break.Value)),
|
new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => OnDeleted?.Invoke(Break.Value)),
|
||||||
@ -159,7 +137,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
Anchor = Anchor,
|
Anchor = Anchor,
|
||||||
Origin = Anchor,
|
Origin = Anchor,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
CornerRadius = 5,
|
CornerRadius = 4,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Child = new Box
|
Child = new Box
|
||||||
{
|
{
|
||||||
@ -249,8 +227,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
if (active)
|
if (active)
|
||||||
colour = colour.Lighten(0.3f);
|
colour = colour.Lighten(0.3f);
|
||||||
|
|
||||||
this.FadeColour(colour, 400, Easing.OutQuint);
|
handle.FadeColour(colour, 400, Easing.OutQuint);
|
||||||
handle.ResizeWidthTo(active ? 20 : 10, 400, Easing.OutElasticHalf);
|
handle.ResizeWidthTo(active ? 10 : 8, 400, Easing.OutElasticHalf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
Origin = Anchor.TopLeft;
|
Origin = Anchor.TopLeft;
|
||||||
|
|
||||||
X = (float)group.Time;
|
// offset visually to avoid overlapping timeline tick display.
|
||||||
|
X = (float)group.Time + 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -519,7 +519,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
Type = EdgeEffectType.Shadow,
|
Type = EdgeEffectType.Shadow,
|
||||||
Radius = 5,
|
Radius = 5,
|
||||||
Colour = Color4.Black.Opacity(0.4f)
|
Colour = Color4.Black.Opacity(0.05f)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user