1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 09:27:29 +08:00

Merge branch 'master' into bspline-sliders

This commit is contained in:
Bartłomiej Dach 2023-11-20 12:03:29 +09:00
commit b6e3e42445
No known key found for this signature in database
122 changed files with 2268 additions and 400 deletions

View File

@ -21,10 +21,10 @@
] ]
}, },
"ppy.localisationanalyser.tools": { "ppy.localisationanalyser.tools": {
"version": "2023.712.0", "version": "2023.1117.0",
"commands": [ "commands": [
"localisation" "localisation"
] ]
} }
} }
} }

View File

@ -185,9 +185,11 @@ jobs:
- name: Add comment environment - name: Add comment environment
if: ${{ github.event_name == 'issue_comment' }} if: ${{ github.event_name == 'issue_comment' }}
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: | run: |
# Add comment environment # Add comment environment
echo '${{ github.event.comment.body }}' | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do echo $COMMENT_BODY | sed -r 's/\r$//' | grep -E '^\w+=' | while read -r line; do
opt=$(echo ${line} | cut -d '=' -f1) opt=$(echo ${line} | cut -d '=' -f1)
sed -i "s;^${opt}=.*$;${line};" "${{ needs.directory.outputs.GENERATOR_ENV }}" sed -i "s;^${opt}=.*$;${line};" "${{ needs.directory.outputs.GENERATOR_ENV }}"
done done

View File

@ -70,7 +70,7 @@ namespace osu.Android
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = MouseSettingsStrings.DisableMouseButtons, LabelText = MouseSettingsStrings.DisableClicksDuringGameplay,
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons), Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons),
}, },
}); });

View File

@ -11,6 +11,7 @@ using osu.Framework.Input.Handlers;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game; using osu.Game;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections.Input;
using osu.Game.Updater; using osu.Game.Updater;
using osu.Game.Utils; using osu.Game.Utils;
@ -97,6 +98,9 @@ namespace osu.Android
case AndroidJoystickHandler jh: case AndroidJoystickHandler jh:
return new AndroidJoystickSettings(jh); return new AndroidJoystickSettings(jh);
case AndroidTouchHandler th:
return new TouchSettings(th);
default: default:
return base.CreateSettingsSubsectionFor(handler); return base.CreateSettingsSubsectionFor(handler);
} }

View File

@ -10,8 +10,8 @@ using osu.Game;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using Squirrel;
using Squirrel.SimpleSplat; using Squirrel.SimpleSplat;
using Squirrel.Sources;
using LogLevel = Squirrel.SimpleSplat.LogLevel; using LogLevel = Squirrel.SimpleSplat.LogLevel;
using UpdateManager = osu.Game.Updater.UpdateManager; using UpdateManager = osu.Game.Updater.UpdateManager;
@ -63,7 +63,7 @@ namespace osu.Desktop.Updater
if (localUserInfo?.IsPlaying.Value == true) if (localUserInfo?.IsPlaying.Value == true)
return false; return false;
updateManager ??= new GithubUpdateManager(@"https://github.com/ppy/osu", false, github_token, @"osulazer"); updateManager ??= new Squirrel.UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false), @"osulazer");
var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false); var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);

View File

@ -23,7 +23,7 @@
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" /> <PackageReference Include="Clowd.Squirrel" Version="2.10.2" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" /> <PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="System.IO.Packaging" Version="7.0.0" /> <PackageReference Include="System.IO.Packaging" Version="7.0.0" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />

View File

@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Catch.Tests
private class TestLegacySkin : LegacySkin private class TestLegacySkin : LegacySkin
{ {
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage) public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> fallbackStore)
// Bypass LegacySkinResourceStore to avoid returning null for retrieving files due to bad skin info (SkinInfo.Files = null). // Bypass LegacySkinResourceStore to avoid returning null for retrieving files due to bad skin info (SkinInfo.Files = null).
: base(skin, null, storage) : base(skin, null, fallbackStore)
{ {
} }
} }

View File

@ -0,0 +1,204 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Input;
using osu.Framework.Screens;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
using osu.Game.Configuration;
using osu.Game.Input;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Storyboards;
using osu.Game.Tests.Visual;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModTouchDevice : RateAdjustedBeatmapTestScene
{
[Resolved]
private SessionStatics statics { get; set; } = null!;
private ScoreAccessibleSoloPlayer currentPlayer = null!;
private readonly ManualClock manualClock = new ManualClock { Rate = 0 };
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(manualClock), Audio);
[BackgroundDependencyLoader]
private void load()
{
Add(new TouchInputInterceptor());
}
public override void SetUpSteps()
{
AddStep("reset static", () => statics.SetValue(Static.TouchInputActive, false));
base.SetUpSteps();
}
[Test]
public void TestUserAlreadyHasTouchDeviceActive()
{
loadPlayer();
// it is presumed that a previous screen (i.e. song select) will set this up
AddStep("set up touchscreen user", () =>
{
currentPlayer.Score.ScoreInfo.Mods = currentPlayer.Score.ScoreInfo.Mods.Append(new OsuModTouchDevice()).ToArray();
statics.SetValue(Static.TouchInputActive, true);
});
AddStep("seek to 0", () => currentPlayer.GameplayClockContainer.Seek(0));
AddUntilStep("wait until 0", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0));
AddStep("touch circle", () =>
{
var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre);
InputManager.BeginTouch(touch);
InputManager.EndTouch(touch);
});
AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf<OsuModTouchDevice>());
}
[Test]
public void TestTouchDuringBreak()
{
loadPlayer();
AddStep("seek to 2000", () => currentPlayer.GameplayClockContainer.Seek(2000));
AddUntilStep("wait until 2000", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(2000));
AddUntilStep("wait until break entered", () => currentPlayer.IsBreakTime.Value);
AddStep("touch playfield", () =>
{
var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre);
InputManager.BeginTouch(touch);
InputManager.EndTouch(touch);
});
AddAssert("touch device mod not activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.None.InstanceOf<OsuModTouchDevice>());
}
[Test]
public void TestTouchMiss()
{
loadPlayer();
// ensure mouse is active (and that it's not suppressed due to touches in previous tests)
AddStep("click mouse", () => InputManager.Click(MouseButton.Left));
AddStep("seek to 200", () => currentPlayer.GameplayClockContainer.Seek(200));
AddUntilStep("wait until 200", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(200));
AddStep("touch playfield", () =>
{
var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre);
InputManager.BeginTouch(touch);
InputManager.EndTouch(touch);
});
AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf<OsuModTouchDevice>());
}
[Test]
public void TestIncompatibleModActive()
{
loadPlayer();
// this is only a veneer of enabling autopilot as having it actually active from the start is annoying to make happen
// given the tests' structure.
AddStep("enable autopilot", () => currentPlayer.Score.ScoreInfo.Mods = new Mod[] { new OsuModAutopilot() });
AddStep("seek to 0", () => currentPlayer.GameplayClockContainer.Seek(0));
AddUntilStep("wait until 0", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0));
AddStep("touch playfield", () =>
{
var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre);
InputManager.BeginTouch(touch);
InputManager.EndTouch(touch);
});
AddAssert("touch device mod not activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.None.InstanceOf<OsuModTouchDevice>());
}
[Test]
public void TestSecondObjectTouched()
{
loadPlayer();
// ensure mouse is active (and that it's not suppressed due to touches in previous tests)
AddStep("click mouse", () => InputManager.Click(MouseButton.Left));
AddStep("seek to 0", () => currentPlayer.GameplayClockContainer.Seek(0));
AddUntilStep("wait until 0", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0));
AddStep("click circle", () =>
{
InputManager.MoveMouseTo(currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre);
InputManager.Click(MouseButton.Left);
});
AddAssert("touch device mod not activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.None.InstanceOf<OsuModTouchDevice>());
AddStep("seek to 5000", () => currentPlayer.GameplayClockContainer.Seek(5000));
AddUntilStep("wait until 5000", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(5000));
AddStep("touch playfield", () =>
{
var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre);
InputManager.BeginTouch(touch);
InputManager.EndTouch(touch);
});
AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf<OsuModTouchDevice>());
}
private void loadPlayer()
{
AddStep("load player", () =>
{
Beatmap.Value = CreateWorkingBeatmap(new OsuBeatmap
{
HitObjects =
{
new HitCircle
{
Position = OsuPlayfield.BASE_SIZE / 2,
StartTime = 0,
},
new HitCircle
{
Position = OsuPlayfield.BASE_SIZE / 2,
StartTime = 5000,
},
},
Breaks =
{
new BreakPeriod(2000, 3000)
}
});
var p = new ScoreAccessibleSoloPlayer();
LoadScreen(currentPlayer = p);
});
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
}
private partial class ScoreAccessibleSoloPlayer : SoloPlayer
{
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
protected override bool PauseOnFocusLost => false;
public ScoreAccessibleSoloPlayer()
: base(new PlayerConfiguration
{
AllowPause = false,
ShowResults = false,
})
{
}
}
}
}

View File

@ -15,22 +15,22 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase(6.710442985146793d, 206, "diffcalc-test")] [TestCase(6.710442985146793d, 239, "diffcalc-test")]
[TestCase(1.4386882251130073d, 45, "zero-length-sliders")] [TestCase(1.4386882251130073d, 54, "zero-length-sliders")]
[TestCase(0.42506480230838789d, 2, "very-fast-slider")] [TestCase(0.42506480230838789d, 4, "very-fast-slider")]
[TestCase(0.14102693012101306d, 1, "nan-slider")] [TestCase(0.14102693012101306d, 2, "nan-slider")]
public void Test(double expectedStarRating, int expectedMaxCombo, string name) public void Test(double expectedStarRating, int expectedMaxCombo, string name)
=> base.Test(expectedStarRating, expectedMaxCombo, name); => base.Test(expectedStarRating, expectedMaxCombo, name);
[TestCase(8.9742952703071666d, 206, "diffcalc-test")] [TestCase(8.9742952703071666d, 239, "diffcalc-test")]
[TestCase(0.55071082800473514d, 2, "very-fast-slider")] [TestCase(1.743180218215227d, 54, "zero-length-sliders")]
[TestCase(1.743180218215227d, 45, "zero-length-sliders")] [TestCase(0.55071082800473514d, 4, "very-fast-slider")]
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime()); => Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
[TestCase(6.710442985146793d, 239, "diffcalc-test")] [TestCase(6.710442985146793d, 239, "diffcalc-test")]
[TestCase(0.42506480230838789d, 4, "very-fast-slider")]
[TestCase(1.4386882251130073d, 54, "zero-length-sliders")] [TestCase(1.4386882251130073d, 54, "zero-length-sliders")]
[TestCase(0.42506480230838789d, 4, "very-fast-slider")]
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name) public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic()); => Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());

View File

@ -133,8 +133,11 @@ namespace osu.Game.Rulesets.Osu.Tests
} }
[Test] [Test]
public void TestSimpleInput() public void TestSimpleInput([Values] bool disableMouseButtons)
{ {
// OsuSetting.MouseDisableButtons should not affect touch taps
AddStep($"{(disableMouseButtons ? "disable" : "enable")} mouse buttons", () => config.SetValue(OsuSetting.MouseDisableButtons, disableMouseButtons));
beginTouch(TouchSource.Touch1); beginTouch(TouchSource.Touch1);
assertKeyCounter(1, 0); assertKeyCounter(1, 0);
@ -468,7 +471,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[Test] [Test]
public void TestInputWhileMouseButtonsDisabled() public void TestInputWhileMouseButtonsDisabled()
{ {
AddStep("Disable mouse buttons", () => config.SetValue(OsuSetting.MouseDisableButtons, true)); AddStep("Disable gameplay taps", () => config.SetValue(OsuSetting.TouchDisableGameplayTaps, true));
beginTouch(TouchSource.Touch1); beginTouch(TouchSource.Touch1);
@ -620,6 +623,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Release all touches", () => AddStep("Release all touches", () =>
{ {
config.SetValue(OsuSetting.MouseDisableButtons, false); config.SetValue(OsuSetting.MouseDisableButtons, false);
config.SetValue(OsuSetting.TouchDisableGameplayTaps, false);
foreach (TouchSource source in InputManager.CurrentState.Touch.ActiveSources) foreach (TouchSource source in InputManager.CurrentState.Touch.ActiveSources)
InputManager.EndTouch(new Touch(source, osuInputManager.ScreenSpaceDrawQuad.Centre)); InputManager.EndTouch(new Touch(source, osuInputManager.ScreenSpaceDrawQuad.Centre));
}); });

View File

@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Tests
if (hit) if (hit)
assertAllMaxJudgements(); assertAllMaxJudgements();
else else
AddAssert("Tracking dropped", assertMidSliderJudgementFail); assertMidSliderJudgementFail();
AddAssert("Head judgement is first", () => judgementResults.First().HitObject is SliderHeadCircle); AddAssert("Head judgement is first", () => judgementResults.First().HitObject is SliderHeadCircle);
@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 },
}); });
AddAssert("Tracking lost", assertMidSliderJudgementFail); assertMidSliderJudgementFail();
} }
/// <summary> /// <summary>
@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider },
}); });
AddAssert("Tracking retained, sliderhead miss", assertHeadMissTailTracked); assertHeadMissTailTracked();
} }
/// <summary> /// <summary>
@ -302,7 +302,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 },
}); });
AddAssert("Tracking re-acquired", assertMidSliderJudgements); assertMidSliderJudgements();
} }
/// <summary> /// <summary>
@ -328,7 +328,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 },
}); });
AddAssert("Tracking lost", assertMidSliderJudgementFail); assertMidSliderJudgementFail();
} }
/// <summary> /// <summary>
@ -350,7 +350,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 },
}); });
AddAssert("Tracking acquired", assertMidSliderJudgements); assertMidSliderJudgements();
} }
/// <summary> /// <summary>
@ -373,7 +373,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 },
}); });
AddAssert("Tracking acquired", assertMidSliderJudgements); assertMidSliderJudgements();
} }
[Test] [Test]
@ -387,7 +387,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 },
}); });
AddAssert("Tracking acquired", assertMidSliderJudgements); assertMidSliderJudgements();
} }
/// <summary> /// <summary>
@ -412,7 +412,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 },
}); });
AddAssert("Tracking acquired", assertMidSliderJudgements); assertMidSliderJudgements();
} }
/// <summary> /// <summary>
@ -454,7 +454,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.201f), Actions = { OsuAction.LeftButton }, Time = time_slider_end }, new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.201f), Actions = { OsuAction.LeftButton }, Time = time_slider_end },
}); });
AddAssert("Tracking dropped", assertMidSliderJudgementFail); assertMidSliderJudgementFail();
} }
private void assertAllMaxJudgements() private void assertAllMaxJudgements()
@ -465,11 +465,21 @@ namespace osu.Game.Rulesets.Osu.Tests
}, () => Is.EqualTo(judgementResults.Select(j => (j.HitObject, j.Judgement.MaxResult)))); }, () => Is.EqualTo(judgementResults.Select(j => (j.HitObject, j.Judgement.MaxResult))));
} }
private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.SmallTickHit && !judgementResults.First().IsHit; private void assertHeadMissTailTracked()
{
AddAssert("Tracking retained", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.LargeTickHit));
AddAssert("Slider head missed", () => judgementResults.First().IsHit, () => Is.False);
}
private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.SmallTickHit; private void assertMidSliderJudgements()
{
AddAssert("Tracking acquired", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.LargeTickHit));
}
private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.SmallTickMiss; private void assertMidSliderJudgementFail()
{
AddAssert("Tracking lost", () => judgementResults[^2].Type, () => Is.EqualTo(HitResult.IgnoreMiss));
}
private void performTest(List<ReplayFrame> frames, Slider? slider = null, double? bpm = null, int? tickRate = null) private void performTest(List<ReplayFrame> frames, Slider? slider = null, double? bpm = null, int? tickRate = null)
{ {

View File

@ -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;
@ -36,6 +37,12 @@ namespace osu.Game.Rulesets.Osu.Tests
AddSliderStep("Spin rate", 0.5, 5, 1, val => spinRate.Value = val); AddSliderStep("Spin rate", 0.5, 5, 1, val => spinRate.Value = val);
} }
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Reset rate", () => spinRate.Value = 1);
}
[TestCase(true)] [TestCase(true)]
[TestCase(false)] [TestCase(false)]
public void TestVariousSpinners(bool autoplay) public void TestVariousSpinners(bool autoplay)
@ -46,6 +53,36 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep($"{term} Small", () => SetContents(_ => testSingle(7, autoplay))); AddStep($"{term} Small", () => SetContents(_ => testSingle(7, autoplay)));
} }
[Test]
public void TestSpinnerNoBonus()
{
AddStep("Set high spin rate", () => spinRate.Value = 5);
Spinner spinner;
AddStep("add spinner", () => SetContents(_ =>
{
spinner = new Spinner
{
StartTime = Time.Current,
EndTime = Time.Current + 750,
Samples = new List<HitSampleInfo>
{
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
}
};
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { OverallDifficulty = 0 });
return drawableSpinner = new TestDrawableSpinner(spinner, true, spinRate)
{
Anchor = Anchor.Centre,
Depth = depthIndex++,
Scale = new Vector2(0.75f)
};
}));
}
[Test] [Test]
public void TestSpinningSamplePitchShift() public void TestSpinningSamplePitchShift()
{ {
@ -153,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
base.Update(); base.Update();
if (auto) if (auto)
RotationTracker.AddRotation((float)(Clock.ElapsedFrameTime * spinRate.Value)); RotationTracker.AddRotation((float)Math.Min(180, Clock.ElapsedFrameTime * spinRate.Value));
} }
} }
} }

View File

@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit
SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 0; SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 0;
SelectionBox.CanFlipY = SelectionBox.CanScaleY = quad.Height > 0; SelectionBox.CanFlipY = SelectionBox.CanScaleY = quad.Height > 0;
SelectionBox.CanScaleDiagonally = SelectionBox.CanScaleX && SelectionBox.CanScaleY;
SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider);
} }

View File

@ -33,7 +33,8 @@ namespace osu.Game.Rulesets.Osu.Mods
typeof(ModNoFail), typeof(ModNoFail),
typeof(ModAutoplay), typeof(ModAutoplay),
typeof(OsuModMagnetised), typeof(OsuModMagnetised),
typeof(OsuModRepel) typeof(OsuModRepel),
typeof(ModTouchDevice)
}; };
public bool PerformFail() => false; public bool PerformFail() => false;

View File

@ -1,18 +1,14 @@
// 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 osu.Framework.Localisation; using System;
using System.Linq;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
public class OsuModTouchDevice : Mod public class OsuModTouchDevice : ModTouchDevice
{ {
public override string Name => "Touch Device"; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray();
public override string Acronym => "TD";
public override LocalisableString Description => "Automatically applied to plays on devices with a touchscreen.";
public override double ScoreMultiplier => 1;
public override ModType Type => ModType.System;
} }
} }

View File

@ -4,6 +4,7 @@
#nullable disable #nullable disable
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -45,6 +46,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_initial_frequency = 1.0f;
private const float spinning_sample_modulated_base_frequency = 0.5f; private const float spinning_sample_modulated_base_frequency = 0.5f;
private SkinnableSound maxBonusSample;
/// <summary> /// <summary>
/// The amount of bonus score gained from spinning after the required number of spins, for display purposes. /// The amount of bonus score gained from spinning after the required number of spins, for display purposes.
/// </summary> /// </summary>
@ -109,6 +112,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME, MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME,
Looping = true, Looping = true,
Frequency = { Value = spinning_sample_initial_frequency } Frequency = { Value = spinning_sample_initial_frequency }
},
maxBonusSample = new SkinnableSound
{
MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME,
} }
}); });
@ -128,6 +135,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.OnFree(); base.OnFree();
spinningSample.ClearSamples(); spinningSample.ClearSamples();
maxBonusSample.ClearSamples();
} }
protected override void LoadSamples() protected override void LoadSamples()
@ -136,6 +144,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
spinningSample.Samples = HitObject.CreateSpinningSamples().Cast<ISampleInfo>().ToArray(); spinningSample.Samples = HitObject.CreateSpinningSamples().Cast<ISampleInfo>().ToArray();
spinningSample.Frequency.Value = spinning_sample_initial_frequency; spinningSample.Frequency.Value = spinning_sample_initial_frequency;
maxBonusSample.Samples = new ISampleInfo[] { new SpinnerBonusMaxSampleInfo(HitObject.CreateHitSampleInfo()) };
} }
private void updateSpinningSample(ValueChangedEvent<bool> tracking) private void updateSpinningSample(ValueChangedEvent<bool> tracking)
@ -157,6 +167,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.StopAllSamples(); base.StopAllSamples();
spinningSample?.Stop(); spinningSample?.Stop();
maxBonusSample?.Stop();
} }
protected override void AddNestedHitObject(DrawableHitObject hitObject) protected override void AddNestedHitObject(DrawableHitObject hitObject)
@ -322,10 +333,38 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
var tick = ticks.FirstOrDefault(t => !t.Result.HasResult); var tick = ticks.FirstOrDefault(t => !t.Result.HasResult);
// tick may be null if we've hit the spin limit. // tick may be null if we've hit the spin limit.
tick?.TriggerResult(true); if (tick == null)
{
// we still want to play a sound. this will probably be a new sound in the future, but for now let's continue playing the bonus sound.
// TODO: this doesn't concurrency. i can't figure out how to make it concurrency. samples are bad and need a refactor.
maxBonusSample.Play();
}
else
tick.TriggerResult(true);
completedFullSpins.Value++; completedFullSpins.Value++;
} }
} }
public class SpinnerBonusMaxSampleInfo : HitSampleInfo
{
public override IEnumerable<string> LookupNames
{
get
{
foreach (string name in base.LookupNames)
yield return name;
foreach (string name in base.LookupNames)
yield return name.Replace("-max", string.Empty);
}
}
public SpinnerBonusMaxSampleInfo(HitSampleInfo sampleInfo)
: base("spinnerbonus-max", sampleInfo.Bank, sampleInfo.Suffix, sampleInfo.Volume)
{
}
}
} }
} }

View File

@ -135,6 +135,8 @@ namespace osu.Game.Rulesets.Osu.Objects
classicSliderBehaviour = value; classicSliderBehaviour = value;
if (HeadCircle != null) if (HeadCircle != null)
HeadCircle.ClassicSliderBehaviour = value; HeadCircle.ClassicSliderBehaviour = value;
if (TailCircle != null)
TailCircle.ClassicSliderBehaviour = value;
} }
} }
@ -218,6 +220,7 @@ namespace osu.Game.Rulesets.Osu.Objects
StartTime = e.Time, StartTime = e.Time,
Position = EndPosition, Position = EndPosition,
StackHeight = StackHeight, StackHeight = StackHeight,
ClassicSliderBehaviour = ClassicSliderBehaviour,
}); });
break; break;
@ -273,9 +276,9 @@ namespace osu.Game.Rulesets.Osu.Objects
} }
public override Judgement CreateJudgement() => ClassicSliderBehaviour public override Judgement CreateJudgement() => ClassicSliderBehaviour
// See logic in `DrawableSlider.CheckForResult()` // Final combo is provided by the slider itself - see logic in `DrawableSlider.CheckForResult()`
? new OsuJudgement() ? new OsuJudgement()
// Of note, this creates a combo discrepancy for non-classic-mod sliders (there is no combo increase for tail or slider judgement). // Final combo is provided by the tail circle - see `SliderTailCircle`
: new OsuIgnoreJudgement(); : new OsuIgnoreJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty; protected override HitWindows CreateHitWindows() => HitWindows.Empty;

View File

@ -3,6 +3,8 @@
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects namespace osu.Game.Rulesets.Osu.Objects
@ -43,5 +45,12 @@ namespace osu.Game.Rulesets.Osu.Objects
} }
protected override HitWindows CreateHitWindows() => HitWindows.Empty; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public override Judgement CreateJudgement() => new SliderEndJudgement();
public class SliderEndJudgement : OsuJudgement
{
public override HitResult MaxResult => HitResult.LargeTickHit;
}
} }
} }

View File

@ -1,10 +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.
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects namespace osu.Game.Rulesets.Osu.Objects
{ {
public class SliderRepeat : SliderEndCircle public class SliderRepeat : SliderEndCircle
@ -13,12 +9,5 @@ namespace osu.Game.Rulesets.Osu.Objects
: base(slider) : base(slider)
{ {
} }
public override Judgement CreateJudgement() => new SliderRepeatJudgement();
public class SliderRepeatJudgement : OsuJudgement
{
public override HitResult MaxResult => HitResult.LargeTickHit;
}
} }
} }

View File

@ -9,16 +9,28 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
public class SliderTailCircle : SliderEndCircle public class SliderTailCircle : SliderEndCircle
{ {
/// <summary>
/// Whether to treat this <see cref="SliderHeadCircle"/> as a normal <see cref="HitCircle"/> for judgement purposes.
/// If <c>false</c>, this <see cref="SliderHeadCircle"/> will be judged as a <see cref="SliderTick"/> instead.
/// </summary>
public bool ClassicSliderBehaviour;
public SliderTailCircle(Slider slider) public SliderTailCircle(Slider slider)
: base(slider) : base(slider)
{ {
} }
public override Judgement CreateJudgement() => new SliderTailJudgement(); public override Judgement CreateJudgement() => ClassicSliderBehaviour ? new LegacyTailJudgement() : new TailJudgement();
public class SliderTailJudgement : OsuJudgement public class LegacyTailJudgement : OsuJudgement
{ {
public override HitResult MaxResult => HitResult.SmallTickHit; public override HitResult MaxResult => HitResult.SmallTickHit;
} }
public class TailJudgement : SliderEndJudgement
{
public override HitResult MaxResult => HitResult.LargeTickHit;
public override HitResult MinResult => HitResult.IgnoreMiss;
}
} }
} }

View File

@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly OsuInputManager osuInputManager; private readonly OsuInputManager osuInputManager;
private Bindable<bool> mouseDisabled = null!; private Bindable<bool> tapsDisabled = null!;
public OsuTouchInputMapper(OsuInputManager inputManager) public OsuTouchInputMapper(OsuInputManager inputManager)
{ {
@ -43,9 +43,7 @@ namespace osu.Game.Rulesets.Osu.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
// The mouse button disable setting affects touch. It's a bit weird. tapsDisabled = config.GetBindable<bool>(OsuSetting.TouchDisableGameplayTaps);
// This is mostly just doing the same as what is done in RulesetInputManager to match behaviour.
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
} }
// Required to handle touches outside of the playfield when screen scaling is enabled. // Required to handle touches outside of the playfield when screen scaling is enabled.
@ -64,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI
: OsuAction.LeftButton; : OsuAction.LeftButton;
// Ignore any taps which trigger an action which is already handled. But track them for potential positional input in the future. // Ignore any taps which trigger an action which is already handled. But track them for potential positional input in the future.
bool shouldResultInAction = osuInputManager.AllowGameplayInputs && !mouseDisabled.Value && trackedTouches.All(t => t.Action != action); bool shouldResultInAction = osuInputManager.AllowGameplayInputs && !tapsDisabled.Value && trackedTouches.All(t => t.Action != action);
// If we can actually accept as an action, check whether this tap was on a circle's receptor. // If we can actually accept as an action, check whether this tap was on a circle's receptor.
// This case gets special handling to allow for empty-space stream tapping. // This case gets special handling to allow for empty-space stream tapping.

View File

@ -174,8 +174,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
private class TestLegacySkin : LegacySkin private class TestLegacySkin : LegacySkin
{ {
public TestLegacySkin(IResourceStore<byte[]> storage, string fileName) public TestLegacySkin(IResourceStore<byte[]> fallbackStore, string fileName)
: base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, null, storage, fileName) : base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, null, fallbackStore, fileName)
{ {
} }
} }

View File

@ -147,11 +147,11 @@ namespace osu.Game.Tests.Mods
new Mod[] { new OsuModDeflate(), new OsuModApproachDifferent() }, new Mod[] { new OsuModDeflate(), new OsuModApproachDifferent() },
new[] { typeof(OsuModDeflate), typeof(OsuModApproachDifferent) } new[] { typeof(OsuModDeflate), typeof(OsuModApproachDifferent) }
}, },
// system mod. // system mod not applicable in lazer.
new object[] new object[]
{ {
new Mod[] { new OsuModHidden(), new OsuModTouchDevice() }, new Mod[] { new OsuModHidden(), new ModScoreV2() },
new[] { typeof(OsuModTouchDevice) } new[] { typeof(ModScoreV2) }
}, },
// multi mod. // multi mod.
new object[] new object[]

View File

@ -11,9 +11,9 @@ namespace osu.Game.Tests.Rulesets.Scoring
[TestFixture] [TestFixture]
public class HitResultTest public class HitResultTest
{ {
[TestCase(new[] { HitResult.Perfect, HitResult.Great, HitResult.Good, HitResult.Ok, HitResult.Meh }, new[] { HitResult.Miss })] [TestCase(new[] { HitResult.Perfect, HitResult.Great, HitResult.Good, HitResult.Ok, HitResult.Meh }, new[] { HitResult.Miss, HitResult.IgnoreMiss })]
[TestCase(new[] { HitResult.LargeTickHit }, new[] { HitResult.LargeTickMiss })] [TestCase(new[] { HitResult.LargeTickHit }, new[] { HitResult.LargeTickMiss, HitResult.IgnoreMiss })]
[TestCase(new[] { HitResult.SmallTickHit }, new[] { HitResult.SmallTickMiss })] [TestCase(new[] { HitResult.SmallTickHit }, new[] { HitResult.SmallTickMiss, HitResult.IgnoreMiss })]
[TestCase(new[] { HitResult.LargeBonus, HitResult.SmallBonus }, new[] { HitResult.IgnoreMiss })] [TestCase(new[] { HitResult.LargeBonus, HitResult.SmallBonus }, new[] { HitResult.IgnoreMiss })]
[TestCase(new[] { HitResult.IgnoreHit }, new[] { HitResult.IgnoreMiss, HitResult.ComboBreak })] [TestCase(new[] { HitResult.IgnoreHit }, new[] { HitResult.IgnoreMiss, HitResult.ComboBreak })]
public void TestValidResultPairs(HitResult[] maxResults, HitResult[] minResults) public void TestValidResultPairs(HitResult[] maxResults, HitResult[] minResults)

View File

@ -13,6 +13,7 @@ using osu.Game.Database;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Tests.Resources;
using SharpCompress.Archives.Zip; using SharpCompress.Archives.Zip;
namespace osu.Game.Tests.Skins.IO namespace osu.Game.Tests.Skins.IO
@ -21,6 +22,25 @@ namespace osu.Game.Tests.Skins.IO
{ {
#region Testing filename metadata inclusion #region Testing filename metadata inclusion
[TestCase("Archives/modified-classic-20220723.osk")]
[TestCase("Archives/modified-default-20230117.osk")]
[TestCase("Archives/modified-argon-20231106.osk")]
public Task TestImportModifiedSkinHasResources(string archive) => runSkinTest(async osu =>
{
using (var stream = TestResources.OpenResource(archive))
{
var imported = await loadSkinIntoOsu(osu, new ImportTask(stream, "skin.osk"));
// When the import filename doesn't match, it should be appended (and update the skin.ini).
var skinManager = osu.Dependencies.Get<SkinManager>();
skinManager.CurrentSkinInfo.Value = imported;
Assert.That(skinManager.CurrentSkin.Value.LayoutInfos.Count, Is.EqualTo(2));
}
});
[Test] [Test]
public Task TestSingleImportDifferentFilename() => runSkinTest(async osu => public Task TestSingleImportDifferentFilename() => runSkinTest(async osu =>
{ {

View File

@ -15,6 +15,7 @@ using osu.Game.IO.Archives;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Skinning.Components;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Skins namespace osu.Game.Tests.Skins
@ -57,6 +58,8 @@ namespace osu.Game.Tests.Skins
"Archives/modified-argon-pro-20231001.osk", "Archives/modified-argon-pro-20231001.osk",
// Covers player name text component. // Covers player name text component.
"Archives/modified-argon-20231106.osk", "Archives/modified-argon-20231106.osk",
// Covers "Argon" accuracy/score/combo counters, and wedges
"Archives/modified-argon-20231108.osk",
}; };
/// <summary> /// <summary>
@ -100,6 +103,20 @@ namespace osu.Game.Tests.Skins
} }
} }
[Test]
public void TestDeserialiseModifiedArgon()
{
using (var stream = TestResources.OpenResource("Archives/modified-argon-20231106.osk"))
using (var storage = new ZipArchiveReader(stream))
{
var skin = new TestSkin(new SkinInfo(), null, storage);
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(10));
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(PlayerName)));
}
}
[Test] [Test]
public void TestDeserialiseModifiedClassic() public void TestDeserialiseModifiedClassic()
{ {
@ -132,8 +149,8 @@ namespace osu.Game.Tests.Skins
private class TestSkin : Skin private class TestSkin : Skin
{ {
public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? storage = null, string configurationFilename = "skin.ini") public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? fallbackStore = null, string configurationFilename = "skin.ini")
: base(skin, resources, storage, configurationFilename) : base(skin, resources, fallbackStore, configurationFilename)
{ {
} }

View File

@ -95,8 +95,8 @@ namespace osu.Game.Tests.Skins
{ {
public const string SAMPLE_NAME = "test-sample"; public const string SAMPLE_NAME = "test-sample";
public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? storage = null, string configurationFilename = "skin.ini") public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? fallbackStore = null, string configurationFilename = "skin.ini")
: base(skin, resources, storage, configurationFilename) : base(skin, resources, fallbackStore, configurationFilename)
{ {
} }

View File

@ -181,6 +181,54 @@ namespace osu.Game.Tests.Visual.Background
AddStep("restore default beatmap", () => Beatmap.SetDefault()); AddStep("restore default beatmap", () => Beatmap.SetDefault());
} }
[Test]
public void TestBeatmapBackgroundWithStoryboardUnloadedOnSuspension()
{
BackgroundScreenBeatmap nestedScreen = null;
setSupporter(true);
setSourceMode(BackgroundSource.BeatmapWithStoryboard);
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithStoryboard());
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackgroundWithStoryboard));
AddUntilStep("storyboard present", () => screen.ChildrenOfType<DrawableStoryboard>().SingleOrDefault()?.IsLoaded == true);
AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
AddUntilStep("storyboard unloaded", () => !screen.ChildrenOfType<DrawableStoryboard>().Any());
AddStep("go back", () => screen.MakeCurrent());
AddUntilStep("storyboard reloaded", () => screen.ChildrenOfType<DrawableStoryboard>().SingleOrDefault()?.IsLoaded == true);
}
[Test]
public void TestBeatmapBackgroundWithStoryboardButBeatmapHasNone()
{
BackgroundScreenBeatmap nestedScreen = null;
setSupporter(true);
setSourceMode(BackgroundSource.BeatmapWithStoryboard);
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground());
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackgroundWithStoryboard));
AddUntilStep("no storyboard loaded", () => !screen.ChildrenOfType<DrawableStoryboard>().Any());
AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
AddUntilStep("still no storyboard", () => !screen.ChildrenOfType<DrawableStoryboard>().Any());
AddStep("go back", () => screen.MakeCurrent());
AddUntilStep("still no storyboard", () => !screen.ChildrenOfType<DrawableStoryboard>().Any());
}
[Test] [Test]
public void TestBackgroundTypeSwitch() public void TestBackgroundTypeSwitch()
{ {

View File

@ -47,6 +47,7 @@ namespace osu.Game.Tests.Visual.Editing
CanScaleX = true, CanScaleX = true,
CanScaleY = true, CanScaleY = true,
CanScaleDiagonally = true,
CanFlipX = true, CanFlipX = true,
CanFlipY = true, CanFlipY = true,

View File

@ -47,17 +47,17 @@ namespace osu.Game.Tests.Visual.Gameplay
}; };
}); });
AddSliderStep("Width", 0, 1f, 1f, val =>
{
if (healthDisplay.IsNotNull())
healthDisplay.BarLength.Value = val;
});
AddSliderStep("Height", 0, 64, 0, val => AddSliderStep("Height", 0, 64, 0, val =>
{ {
if (healthDisplay.IsNotNull()) if (healthDisplay.IsNotNull())
healthDisplay.BarHeight.Value = val; healthDisplay.BarHeight.Value = val;
}); });
AddSliderStep("Width", 0, 1f, 0.98f, val =>
{
if (healthDisplay.IsNotNull())
healthDisplay.Width = val;
});
} }
[Test] [Test]

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
@ -96,7 +97,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Begin drag top left", () => AddStep("Begin drag top left", () =>
{ {
InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft - new Vector2(box1.ScreenSpaceDrawQuad.Width / 4)); InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft - new Vector2(box1.ScreenSpaceDrawQuad.Width / 4, box1.ScreenSpaceDrawQuad.Height / 8));
InputManager.PressButton(MouseButton.Left); InputManager.PressButton(MouseButton.Left);
}); });
@ -146,8 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("Add big black box", () => AddStep("Add big black box", () =>
{ {
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<BigBlackBox>().First()); skinEditor.ChildrenOfType<SkinComponentToolbox.ToolboxComponentButton>().First(b => b.ChildrenOfType<BigBlackBox>().FirstOrDefault() != null).TriggerClick();
InputManager.Click(MouseButton.Left);
}); });
AddStep("store box", () => AddStep("store box", () =>
@ -243,7 +243,9 @@ namespace osu.Game.Tests.Visual.Gameplay
void revertAndCheckUnchanged() void revertAndCheckUnchanged()
{ {
AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue)); AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue));
AddAssert("Current state is same as default", () => defaultState.SequenceEqual(changeHandler.GetCurrentState())); AddAssert("Current state is same as default",
() => Encoding.UTF8.GetString(defaultState),
() => Is.EqualTo(Encoding.UTF8.GetString(changeHandler.GetCurrentState())));
} }
} }

View File

@ -17,6 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached] [Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
protected override Drawable CreateArgonImplementation() => new ArgonAccuracyCounter();
protected override Drawable CreateDefaultImplementation() => new DefaultAccuracyCounter(); protected override Drawable CreateDefaultImplementation() => new DefaultAccuracyCounter();
protected override Drawable CreateLegacyImplementation() => new LegacyAccuracyCounter(); protected override Drawable CreateLegacyImplementation() => new LegacyAccuracyCounter();

View File

@ -17,6 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached] [Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
protected override Drawable CreateArgonImplementation() => new ArgonComboCounter();
protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter(); protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter();
protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter(); protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter();

View File

@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached(typeof(HealthProcessor))] [Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f) }; protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), Width = 1f };
protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) }; protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) };
protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) }; protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) };

View File

@ -17,6 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached(typeof(ScoreProcessor))] [Cached(typeof(ScoreProcessor))]
private ScoreProcessor scoreProcessor = TestGameplayState.Create(new OsuRuleset()).ScoreProcessor; private ScoreProcessor scoreProcessor = TestGameplayState.Create(new OsuRuleset()).ScoreProcessor;
protected override Drawable CreateArgonImplementation() => new ArgonScoreCounter();
protected override Drawable CreateDefaultImplementation() => new DefaultScoreCounter(); protected override Drawable CreateDefaultImplementation() => new DefaultScoreCounter();
protected override Drawable CreateLegacyImplementation() => new LegacyScoreCounter(); protected override Drawable CreateLegacyImplementation() => new LegacyScoreCounter();

View File

@ -133,6 +133,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private bool assertAllAvailableModsSelected() private bool assertAllAvailableModsSelected()
{ {
var allAvailableMods = availableMods.Value var allAvailableMods = availableMods.Value
.Where(pair => pair.Key != ModType.System)
.SelectMany(pair => pair.Value) .SelectMany(pair => pair.Value)
.Where(mod => mod.UserPlayable && mod.HasImplementation) .Where(mod => mod.UserPlayable && mod.HasImplementation)
.ToList(); .ToList();

View File

@ -6,11 +6,13 @@ using NUnit.Framework;
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.UserInterface;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; 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.Overlays;
using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
@ -232,6 +234,35 @@ namespace osu.Game.Tests.Visual.Navigation
() => Is.EqualTo(beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0).ID)); () => Is.EqualTo(beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0).ID));
} }
[Test]
public void TestCreateNewDifficultyOnNonExistentBeatmap()
{
AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault() != null);
AddStep("open editor", () => Game.ChildrenOfType<ButtonSystem>().Single().OnEdit.Invoke());
AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded);
AddStep("click on file", () =>
{
var item = getEditor().ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value.ToString() == "File");
item.TriggerClick();
});
AddStep("click on create new difficulty", () =>
{
var item = getEditor().ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value.ToString() == "Create new difficulty");
item.TriggerClick();
});
AddStep("click on catch", () =>
{
var item = getEditor().ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value.ToString() == "osu!catch");
item.TriggerClick();
});
AddAssert("save dialog displayed", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog is SaveRequiredPopupDialog);
AddStep("press save", () => Game.ChildrenOfType<DialogOverlay>().Single().CurrentDialog!.PerformOkAction());
AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded);
AddAssert("editor beatmap uses catch ruleset", () => getEditorBeatmap().BeatmapInfo.Ruleset.ShortName == "fruits");
}
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;

View File

@ -12,6 +12,7 @@ using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -835,6 +836,110 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("exit dialog is shown", () => Game.Dependencies.Get<IDialogOverlay>().CurrentDialog is ConfirmExitDialog); AddAssert("exit dialog is shown", () => Game.Dependencies.Get<IDialogOverlay>().CurrentDialog is ConfirmExitDialog);
} }
[Test]
public void TestTouchScreenDetectionAtSongSelect()
{
AddStep("touch logo", () =>
{
var button = Game.ChildrenOfType<OsuLogo>().Single();
var touch = new Touch(TouchSource.Touch1, button.ScreenSpaceDrawQuad.Centre);
InputManager.BeginTouch(touch);
InputManager.EndTouch(touch);
});
AddAssert("touch screen detected active", () => Game.Dependencies.Get<SessionStatics>().Get<bool>(Static.TouchInputActive), () => Is.True);
AddStep("click settings button", () =>
{
var button = Game.ChildrenOfType<MainMenuButton>().Last();
InputManager.MoveMouseTo(button);
InputManager.Click(MouseButton.Left);
});
AddAssert("touch screen detected inactive", () => Game.Dependencies.Get<SessionStatics>().Get<bool>(Static.TouchInputActive), () => Is.False);
AddStep("close settings sidebar", () => InputManager.Key(Key.Escape));
Screens.Select.SongSelect songSelect = null;
AddRepeatStep("go to solo", () => InputManager.Key(Key.P), 3);
AddUntilStep("wait for song select", () => (songSelect = Game.ScreenStack.CurrentScreen as Screens.Select.SongSelect) != null);
AddUntilStep("wait for beatmap sets loaded", () => songSelect.BeatmapSetsLoaded);
AddStep("switch to osu! ruleset", () =>
{
InputManager.PressKey(Key.LControl);
InputManager.Key(Key.Number1);
InputManager.ReleaseKey(Key.LControl);
});
AddStep("touch beatmap wedge", () =>
{
var wedge = Game.ChildrenOfType<BeatmapInfoWedge>().Single();
var touch = new Touch(TouchSource.Touch2, wedge.ScreenSpaceDrawQuad.Centre);
InputManager.BeginTouch(touch);
InputManager.EndTouch(touch);
});
AddUntilStep("touch device mod activated", () => Game.SelectedMods.Value, () => Has.One.InstanceOf<ModTouchDevice>());
AddStep("switch to mania ruleset", () =>
{
InputManager.PressKey(Key.LControl);
InputManager.Key(Key.Number4);
InputManager.ReleaseKey(Key.LControl);
});
AddUntilStep("touch device mod not activated", () => Game.SelectedMods.Value, () => Has.None.InstanceOf<ModTouchDevice>());
AddStep("touch beatmap wedge", () =>
{
var wedge = Game.ChildrenOfType<BeatmapInfoWedge>().Single();
var touch = new Touch(TouchSource.Touch2, wedge.ScreenSpaceDrawQuad.Centre);
InputManager.BeginTouch(touch);
InputManager.EndTouch(touch);
});
AddUntilStep("touch device mod not activated", () => Game.SelectedMods.Value, () => Has.None.InstanceOf<ModTouchDevice>());
AddStep("switch to osu! ruleset", () =>
{
InputManager.PressKey(Key.LControl);
InputManager.Key(Key.Number1);
InputManager.ReleaseKey(Key.LControl);
});
AddUntilStep("touch device mod activated", () => Game.SelectedMods.Value, () => Has.One.InstanceOf<ModTouchDevice>());
AddStep("click beatmap wedge", () =>
{
InputManager.MoveMouseTo(Game.ChildrenOfType<BeatmapInfoWedge>().Single());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("touch device mod not activated", () => Game.SelectedMods.Value, () => Has.None.InstanceOf<ModTouchDevice>());
}
[Test]
public void TestTouchScreenDetectionInGame()
{
PushAndConfirm(() => new TestPlaySongSelect());
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
AddStep("select", () => InputManager.Key(Key.Enter));
Player player = null;
AddUntilStep("wait for player", () =>
{
DismissAnyNotifications();
return (player = Game.ScreenStack.CurrentScreen as Player) != null;
});
AddUntilStep("wait for track playing", () => Game.Beatmap.Value.Track.IsRunning);
AddStep("touch", () =>
{
var touch = new Touch(TouchSource.Touch2, Game.ScreenSpaceDrawQuad.Centre);
InputManager.BeginTouch(touch);
InputManager.EndTouch(touch);
});
AddUntilStep("touch device mod added to score", () => player.Score.ScoreInfo.Mods, () => Has.One.InstanceOf<ModTouchDevice>());
AddStep("exit player", () => player.Exit());
AddUntilStep("touch device mod still active", () => Game.SelectedMods.Value, () => Has.One.InstanceOf<ModTouchDevice>());
}
private Func<Player> playToResults() private Func<Player> playToResults()
{ {
var player = playToCompletion(); var player = playToCompletion();

View File

@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online
new[] { "Plain", "This is plain comment" }, new[] { "Plain", "This is plain comment" },
new[] { "Pinned", "This is pinned comment" }, new[] { "Pinned", "This is pinned comment" },
new[] { "Link", "Please visit https://osu.ppy.sh" }, new[] { "Link", "Please visit https://osu.ppy.sh" },
new[] { "Big Image", "![](Backgrounds/bg1)" }, new[] { "Big Image", "![](Backgrounds/bg1 \"Big Image\")" },
new[] { "Small Image", "![](Cursor/cursortrail)" }, new[] { "Small Image", "![](Cursor/cursortrail)" },
new[] new[]
{ {

View File

@ -17,18 +17,16 @@ namespace osu.Game.Tests.Visual.Online
{ {
BarGraph graph; BarGraph graph;
Children = new[] Child = graph = new BarGraph
{ {
graph = new BarGraph RelativeSizeAxes = Axes.Both,
{ Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Size = new Vector2(0.5f),
Origin = Anchor.Centre,
Size = new Vector2(0.5f),
},
}; };
AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Select(i => (float)i)); AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Select(i => (float)i));
AddStep("small values", () => graph.Values = Enumerable.Range(1, 10).Select(i => i * 0.01f).Concat(new[] { 100f }));
AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i)); AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i));
AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i)); AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i));
AddStep("empty values", () => graph.Values = Array.Empty<float>()); AddStep("empty values", () => graph.Values = Array.Empty<float>());
@ -36,6 +34,14 @@ namespace osu.Game.Tests.Visual.Online
AddStep("Top to bottom", () => graph.Direction = BarDirection.TopToBottom); AddStep("Top to bottom", () => graph.Direction = BarDirection.TopToBottom);
AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight); AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight);
AddStep("Right to left", () => graph.Direction = BarDirection.RightToLeft); AddStep("Right to left", () => graph.Direction = BarDirection.RightToLeft);
AddToggleStep("Toggle movement", enabled =>
{
if (enabled)
graph.MoveToY(-10, 1000).Then().MoveToY(10, 1000).Loop();
else
graph.ClearTransforms();
});
} }
} }
} }

View File

@ -0,0 +1,86 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Testing;
using osu.Game.Graphics.Cursor;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users;
using osu.Game.Users.Drawables;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Online
{
public partial class TestSceneUserClickableAvatar : OsuManualInputManagerTestScene
{
[SetUp]
public void SetUp() => Schedule(() =>
{
Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(10f),
Children = new[]
{
generateUser(@"peppy", 2, CountryCode.AU, @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", false, "99EB47"),
generateUser(@"flyte", 3103765, CountryCode.JP, @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", true),
generateUser(@"joshika39", 17032217, CountryCode.RS, @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", false),
new UpdateableAvatar(),
new UpdateableAvatar()
},
};
});
[Test]
public void TestClickableAvatarHover()
{
AddStep("hover avatar with user panel", () => InputManager.MoveMouseTo(this.ChildrenOfType<ClickableAvatar>().ElementAt(1)));
AddUntilStep("wait for tooltip to show", () => this.ChildrenOfType<ClickableAvatar.UserCardTooltip>().FirstOrDefault()?.State.Value == Visibility.Visible);
AddStep("hover out", () => InputManager.MoveMouseTo(new Vector2(0)));
AddUntilStep("wait for tooltip to hide", () => this.ChildrenOfType<ClickableAvatar.UserCardTooltip>().FirstOrDefault()?.State.Value == Visibility.Hidden);
AddStep("hover avatar without user panel", () => InputManager.MoveMouseTo(this.ChildrenOfType<ClickableAvatar>().ElementAt(0)));
AddUntilStep("wait for tooltip to show", () => this.ChildrenOfType<OsuTooltipContainer.OsuTooltip>().FirstOrDefault()?.State.Value == Visibility.Visible);
AddStep("hover out", () => InputManager.MoveMouseTo(new Vector2(0)));
AddUntilStep("wait for tooltip to hide", () => this.ChildrenOfType<OsuTooltipContainer.OsuTooltip>().FirstOrDefault()?.State.Value == Visibility.Hidden);
}
private Drawable generateUser(string username, int id, CountryCode countryCode, string cover, bool showPanel, string? color = null)
{
var user = new APIUser
{
Username = username,
Id = id,
CountryCode = countryCode,
CoverUrl = cover,
Colour = color ?? "000000",
Status =
{
Value = new UserStatusOnline()
},
};
return new ClickableAvatar(user, showPanel)
{
Width = 50,
Height = 50,
CornerRadius = 10,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Radius = 1,
Colour = Color4.Black.Opacity(0.2f),
},
};
}
}
}

View File

@ -10,7 +10,6 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -298,7 +297,7 @@ This is a line after the fenced code block!
{ {
public LinkInline Link; public LinkInline Link;
public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer public override OsuMarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer
{ {
UrlAdded = link => Link = link, UrlAdded = link => Link = link,
}; };

View File

@ -203,6 +203,7 @@ namespace osu.Game.Tests.Visual.Ranking
public IBeatmap Beatmap { get; } public IBeatmap Beatmap { get; }
// ReSharper disable once NotNullOrRequiredMemberIsNotInitialized
public TestBeatmapConverter(IBeatmap beatmap) public TestBeatmapConverter(IBeatmap beatmap)
{ {
Beatmap = beatmap; Beatmap = beatmap;

View File

@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.UserInterface
} }
[Test] [Test]
public void TestAddingFlow() public void TestAddingFlow([Values] bool withSystemModActive)
{ {
ModPresetColumn modPresetColumn = null!; ModPresetColumn modPresetColumn = null!;
@ -181,7 +181,13 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("items loaded", () => modPresetColumn.IsLoaded && modPresetColumn.ItemsLoaded); AddUntilStep("items loaded", () => modPresetColumn.IsLoaded && modPresetColumn.ItemsLoaded);
AddAssert("add preset button disabled", () => !this.ChildrenOfType<AddPresetButton>().Single().Enabled.Value); AddAssert("add preset button disabled", () => !this.ChildrenOfType<AddPresetButton>().Single().Enabled.Value);
AddStep("set mods", () => SelectedMods.Value = new Mod[] { new OsuModDaycore(), new OsuModClassic() }); AddStep("set mods", () =>
{
var newMods = new Mod[] { new OsuModDaycore(), new OsuModClassic() };
if (withSystemModActive)
newMods = newMods.Append(new OsuModTouchDevice()).ToArray();
SelectedMods.Value = newMods;
});
AddAssert("add preset button enabled", () => this.ChildrenOfType<AddPresetButton>().Single().Enabled.Value); AddAssert("add preset button enabled", () => this.ChildrenOfType<AddPresetButton>().Single().Enabled.Value);
AddStep("click add preset button", () => AddStep("click add preset button", () =>
@ -209,6 +215,9 @@ namespace osu.Game.Tests.Visual.UserInterface
}); });
AddUntilStep("popover closed", () => !this.ChildrenOfType<OsuPopover>().Any()); AddUntilStep("popover closed", () => !this.ChildrenOfType<OsuPopover>().Any());
AddUntilStep("preset creation occurred", () => this.ChildrenOfType<ModPresetPanel>().Count() == 4); AddUntilStep("preset creation occurred", () => this.ChildrenOfType<ModPresetPanel>().Count() == 4);
AddAssert("preset has correct mods",
() => this.ChildrenOfType<ModPresetPanel>().Single(panel => panel.Preset.Value.Name == "new preset").Preset.Value.Mods,
() => Has.Count.EqualTo(2));
AddStep("click add preset button", () => AddStep("click add preset button", () =>
{ {

View File

@ -86,6 +86,10 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("set mods to HD+HR+DT", () => SelectedMods.Value = new Mod[] { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime() }); AddStep("set mods to HD+HR+DT", () => SelectedMods.Value = new Mod[] { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime() });
AddAssert("panel is not active", () => !panel.AsNonNull().Active.Value); AddAssert("panel is not active", () => !panel.AsNonNull().Active.Value);
// system mods are not included in presets.
AddStep("set mods to HR+DT+TD", () => SelectedMods.Value = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime(), new OsuModTouchDevice() });
AddAssert("panel is active", () => panel.AsNonNull().Active.Value);
} }
[Test] [Test]
@ -113,6 +117,10 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("set customised mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); AddStep("set customised mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
AddStep("activate panel", () => panel.AsNonNull().TriggerClick()); AddStep("activate panel", () => panel.AsNonNull().TriggerClick());
assertSelectedModsEquivalentTo(new Mod[] { new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } }); assertSelectedModsEquivalentTo(new Mod[] { new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } });
AddStep("set system mod", () => SelectedMods.Value = new[] { new OsuModTouchDevice() });
AddStep("activate panel", () => panel.AsNonNull().TriggerClick());
assertSelectedModsEquivalentTo(new Mod[] { new OsuModTouchDevice(), new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } });
} }
private void assertSelectedModsEquivalentTo(IEnumerable<Mod> mods) private void assertSelectedModsEquivalentTo(IEnumerable<Mod> mods)

View File

@ -1,7 +1,9 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -17,6 +19,11 @@ namespace osu.Game.Tournament.Components
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
var players = team?.Players ?? new BindableList<TournamentUser>();
// split the players into two even columns, favouring the first column if odd.
int split = (int)Math.Ceiling(players.Count / 2f);
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new FillFlowContainer new FillFlowContainer
@ -39,13 +46,13 @@ namespace osu.Game.Tournament.Components
{ {
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
ChildrenEnumerable = team?.Players.Select(createPlayerText).Take(5) ?? Enumerable.Empty<Drawable>() ChildrenEnumerable = players.Take(split).Select(createPlayerText),
}, },
new FillFlowContainer new FillFlowContainer
{ {
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
ChildrenEnumerable = team?.Players.Select(createPlayerText).Skip(5) ?? Enumerable.Empty<Drawable>() ChildrenEnumerable = players.Skip(split).Select(createPlayerText),
}, },
} }
}, },

View File

@ -74,7 +74,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics
#region Tooltip implementation #region Tooltip implementation
public virtual ITooltip GetCustomTooltip() => null; public virtual ITooltip GetCustomTooltip() => null!;
public virtual object TooltipContent => null; public virtual object TooltipContent => null;
#endregion #endregion

View File

@ -108,6 +108,8 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.MouseDisableWheel, false); SetDefault(OsuSetting.MouseDisableWheel, false);
SetDefault(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); SetDefault(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay);
SetDefault(OsuSetting.TouchDisableGameplayTaps, false);
// Graphics // Graphics
SetDefault(OsuSetting.ShowFpsDisplay, false); SetDefault(OsuSetting.ShowFpsDisplay, false);
@ -330,6 +332,10 @@ namespace osu.Game.Configuration
ShowHealthDisplayWhenCantFail, ShowHealthDisplayWhenCantFail,
FadePlayfieldWhenHealthLow, FadePlayfieldWhenHealthLow,
/// <summary>
/// Disables mouse buttons clicks during gameplay.
/// </summary>
MouseDisableButtons, MouseDisableButtons,
MouseDisableWheel, MouseDisableWheel,
ConfineMouseMode, ConfineMouseMode,
@ -408,6 +414,7 @@ namespace osu.Game.Configuration
EditorLimitedDistanceSnap, EditorLimitedDistanceSnap,
ReplaySettingsOverlay, ReplaySettingsOverlay,
AutomaticallyDownloadMissingBeatmaps, AutomaticallyDownloadMissingBeatmaps,
EditorShowSpeedChanges EditorShowSpeedChanges,
TouchDisableGameplayTaps,
} }
} }

View File

@ -3,7 +3,9 @@
#nullable disable #nullable disable
using osu.Framework;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
@ -24,6 +26,7 @@ namespace osu.Game.Configuration
SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null); SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
SetDefault(Static.LastModSelectPanelSamplePlaybackTime, (double?)null); SetDefault(Static.LastModSelectPanelSamplePlaybackTime, (double?)null);
SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null); SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
SetDefault(Static.TouchInputActive, RuntimeInfo.IsMobile);
} }
/// <summary> /// <summary>
@ -63,6 +66,12 @@ namespace osu.Game.Configuration
/// The last playback time in milliseconds of an on/off sample (from <see cref="ModSelectPanel"/>). /// The last playback time in milliseconds of an on/off sample (from <see cref="ModSelectPanel"/>).
/// Used to debounce <see cref="ModSelectPanel"/> on/off sounds game-wide to avoid volume saturation, especially in activating mod presets with many mods. /// Used to debounce <see cref="ModSelectPanel"/> on/off sounds game-wide to avoid volume saturation, especially in activating mod presets with many mods.
/// </summary> /// </summary>
LastModSelectPanelSamplePlaybackTime LastModSelectPanelSamplePlaybackTime,
/// <summary>
/// Whether the last positional input received was a touch input.
/// Used in touchscreen detection scenarios (<see cref="TouchInputInterceptor"/>).
/// </summary>
TouchInputActive,
} }
} }

View File

@ -2,6 +2,8 @@
// 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.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -10,6 +12,7 @@ using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens;
using osu.Game.Storyboards.Drawables; using osu.Game.Storyboards.Drawables;
namespace osu.Game.Graphics.Backgrounds namespace osu.Game.Graphics.Backgrounds
@ -18,6 +21,10 @@ namespace osu.Game.Graphics.Backgrounds
{ {
private readonly InterpolatingFramedClock storyboardClock; private readonly InterpolatingFramedClock storyboardClock;
private AudioContainer storyboardContainer = null!;
private DrawableStoryboard? drawableStoryboard;
private CancellationTokenSource? loadCancellationSource = new CancellationTokenSource();
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private MusicController? musicController { get; set; } private MusicController? musicController { get; set; }
@ -33,18 +40,59 @@ namespace osu.Game.Graphics.Backgrounds
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
if (!Beatmap.Storyboard.HasDrawable) AddInternal(storyboardContainer = new AudioContainer
return;
if (Beatmap.Storyboard.ReplacesBackground)
Sprite.Alpha = 0;
LoadComponentAsync(new AudioContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Volume = { Value = 0 }, Volume = { Value = 0 },
Child = new DrawableStoryboard(Beatmap.Storyboard, mods.Value) { Clock = storyboardClock } });
}, AddInternal);
LoadStoryboard(false);
}
public void LoadStoryboard(bool async = true)
{
Debug.Assert(drawableStoryboard == null);
if (!Beatmap.Storyboard.HasDrawable)
return;
drawableStoryboard = new DrawableStoryboard(Beatmap.Storyboard, mods.Value)
{
Clock = storyboardClock
};
if (async)
LoadComponentAsync(drawableStoryboard, finishLoad, (loadCancellationSource = new CancellationTokenSource()).Token);
else
{
LoadComponent(drawableStoryboard);
finishLoad(drawableStoryboard);
}
void finishLoad(DrawableStoryboard s)
{
if (Beatmap.Storyboard.ReplacesBackground)
Sprite.FadeOut(BackgroundScreen.TRANSITION_LENGTH, Easing.InQuint);
storyboardContainer.FadeInFromZero(BackgroundScreen.TRANSITION_LENGTH, Easing.OutQuint);
storyboardContainer.Add(s);
}
}
public void UnloadStoryboard()
{
if (drawableStoryboard == null)
return;
loadCancellationSource?.Cancel();
loadCancellationSource = null;
// clear is intentionally used here for the storyboard to be disposed asynchronously.
storyboardContainer.Clear();
drawableStoryboard = null;
Sprite.Alpha = 1f;
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -5,7 +5,6 @@ using Markdig.Extensions.Footnotes;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Overlays; using osu.Game.Overlays;
@ -62,7 +61,7 @@ namespace osu.Game.Graphics.Containers.Markdown.Footnotes
lastFootnote = Text = footnote; lastFootnote = Text = footnote;
} }
public override MarkdownTextFlowContainer CreateTextFlow() => new FootnoteMarkdownTextFlowContainer(); public override OsuMarkdownTextFlowContainer CreateTextFlow() => new FootnoteMarkdownTextFlowContainer();
} }
private partial class FootnoteMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer private partial class FootnoteMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer

View File

@ -63,7 +63,7 @@ namespace osu.Game.Graphics.Containers.Markdown
Font = OsuFont.GetFont(Typeface.Inter, size: 14, weight: FontWeight.Regular), Font = OsuFont.GetFont(Typeface.Inter, size: 14, weight: FontWeight.Regular),
}; };
public override MarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer(); public override OsuMarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer();
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock); protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock);

View File

@ -145,6 +145,13 @@ namespace osu.Game.Graphics.UserInterface
float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? lengths[i] : barBreadth); float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? lengths[i] : barBreadth);
float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? lengths[i] : barBreadth); float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? lengths[i] : barBreadth);
if (barHeight == 0 || barWidth == 0)
continue;
// Apply minimum sizing to hide the fact that we don't have fractional anti-aliasing.
barHeight = Math.Max(barHeight, 1.5f);
barWidth = Math.Max(barWidth, 1.5f);
Vector2 topLeft; Vector2 topLeft;
switch (direction) switch (direction)

View File

@ -0,0 +1,72 @@
// 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.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osuTK;
using osuTK.Input;
namespace osu.Game.Input
{
/// <summary>
/// Intercepts all positional input events and sets the appropriate <see cref="Static.TouchInputActive"/> value
/// for consumption by particular game screens.
/// </summary>
public partial class TouchInputInterceptor : Component
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
private readonly BindableBool touchInputActive = new BindableBool();
[BackgroundDependencyLoader]
private void load(SessionStatics statics)
{
statics.BindWith(Static.TouchInputActive, touchInputActive);
}
protected override bool Handle(UIEvent e)
{
bool touchInputWasActive = touchInputActive.Value;
switch (e)
{
case MouseEvent:
if (e.CurrentState.Mouse.LastSource is not ISourcedFromTouch)
{
if (touchInputWasActive)
Logger.Log($@"Touch input deactivated due to received {e.GetType().ReadableName()}", LoggingTarget.Input);
touchInputActive.Value = false;
}
break;
case TouchEvent:
if (!touchInputWasActive)
Logger.Log($@"Touch input activated due to received {e.GetType().ReadableName()}", LoggingTarget.Input);
touchInputActive.Value = true;
break;
case KeyDownEvent keyDown:
if (keyDown.Key == Key.T && keyDown.ControlPressed && keyDown.ShiftPressed)
debugToggleTouchInputActive();
break;
}
return false;
}
[Conditional("TOUCH_INPUT_DEBUG")]
private void debugToggleTouchInputActive()
{
Logger.Log($@"Debug-toggling touch input to {(touchInputActive.Value ? @"inactive" : @"active")}", LoggingTarget.Information, LogLevel.Important);
touchInputActive.Toggle();
}
}
}

View File

@ -40,14 +40,14 @@ namespace osu.Game.Localisation
public static LocalisableString DisableMouseWheelVolumeAdjust => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust"), @"Disable mouse wheel adjusting volume during gameplay"); public static LocalisableString DisableMouseWheelVolumeAdjust => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust"), @"Disable mouse wheel adjusting volume during gameplay");
/// <summary> /// <summary>
/// "Volume can still be adjusted using the mouse wheel by holding "Alt"" /// "Volume can still be adjusted using the mouse wheel by holding &quot;Alt&quot;"
/// </summary> /// </summary>
public static LocalisableString DisableMouseWheelVolumeAdjustTooltip => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust_tooltip"), @"Volume can still be adjusted using the mouse wheel by holding ""Alt"""); public static LocalisableString DisableMouseWheelVolumeAdjustTooltip => new TranslatableString(getKey(@"disable_mouse_wheel_volume_adjust_tooltip"), @"Volume can still be adjusted using the mouse wheel by holding ""Alt""");
/// <summary> /// <summary>
/// "Disable mouse buttons during gameplay" /// "Disable clicks during gameplay"
/// </summary> /// </summary>
public static LocalisableString DisableMouseButtons => new TranslatableString(getKey(@"disable_mouse_buttons"), @"Disable mouse buttons during gameplay"); public static LocalisableString DisableClicksDuringGameplay => new TranslatableString(getKey(@"disable_clicks"), @"Disable clicks during gameplay");
/// <summary> /// <summary>
/// "Enable high precision mouse to adjust sensitivity" /// "Enable high precision mouse to adjust sensitivity"

View File

@ -12,43 +12,53 @@ namespace osu.Game.Localisation.SkinComponents
/// <summary> /// <summary>
/// "Sprite name" /// "Sprite name"
/// </summary> /// </summary>
public static LocalisableString SpriteName => new TranslatableString(getKey(@"sprite_name"), "Sprite name"); public static LocalisableString SpriteName => new TranslatableString(getKey(@"sprite_name"), @"Sprite name");
/// <summary> /// <summary>
/// "The filename of the sprite" /// "The filename of the sprite"
/// </summary> /// </summary>
public static LocalisableString SpriteNameDescription => new TranslatableString(getKey(@"sprite_name_description"), "The filename of the sprite"); public static LocalisableString SpriteNameDescription => new TranslatableString(getKey(@"sprite_name_description"), @"The filename of the sprite");
/// <summary> /// <summary>
/// "Font" /// "Font"
/// </summary> /// </summary>
public static LocalisableString Font => new TranslatableString(getKey(@"font"), "Font"); public static LocalisableString Font => new TranslatableString(getKey(@"font"), @"Font");
/// <summary> /// <summary>
/// "The font to use." /// "The font to use."
/// </summary> /// </summary>
public static LocalisableString FontDescription => new TranslatableString(getKey(@"font_description"), "The font to use."); public static LocalisableString FontDescription => new TranslatableString(getKey(@"font_description"), @"The font to use.");
/// <summary> /// <summary>
/// "Text" /// "Text"
/// </summary> /// </summary>
public static LocalisableString TextElementText => new TranslatableString(getKey(@"text_element_text"), "Text"); public static LocalisableString TextElementText => new TranslatableString(getKey(@"text_element_text"), @"Text");
/// <summary> /// <summary>
/// "The text to be displayed." /// "The text to be displayed."
/// </summary> /// </summary>
public static LocalisableString TextElementTextDescription => new TranslatableString(getKey(@"text_element_text_description"), "The text to be displayed."); public static LocalisableString TextElementTextDescription => new TranslatableString(getKey(@"text_element_text_description"), @"The text to be displayed.");
/// <summary> /// <summary>
/// "Corner radius" /// "Corner radius"
/// </summary> /// </summary>
public static LocalisableString CornerRadius => new TranslatableString(getKey(@"corner_radius"), "Corner radius"); public static LocalisableString CornerRadius => new TranslatableString(getKey(@"corner_radius"), @"Corner radius");
/// <summary> /// <summary>
/// "How rounded the corners should be." /// "How rounded the corners should be."
/// </summary> /// </summary>
public static LocalisableString CornerRadiusDescription => new TranslatableString(getKey(@"corner_radius_description"), "How rounded the corners should be."); public static LocalisableString CornerRadiusDescription => new TranslatableString(getKey(@"corner_radius_description"), @"How rounded the corners should be.");
private static string getKey(string key) => $"{prefix}:{key}"; /// <summary>
/// "Show label"
/// </summary>
public static LocalisableString ShowLabel => new TranslatableString(getKey(@"show_label"), @"Show label");
/// <summary>
/// "Whether the component&#39;s label should be shown."
/// </summary>
public static LocalisableString ShowLabelDescription => new TranslatableString(getKey(@"show_label_description"), @"Whether the component's label should be shown.");
private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -0,0 +1,24 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Localisation;
namespace osu.Game.Localisation
{
public static class TouchSettingsStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.TouchSettings";
/// <summary>
/// "Touch"
/// </summary>
public static LocalisableString Touch => new TranslatableString(getKey(@"touch"), @"Touch");
/// <summary>
/// "Disable taps during gameplay"
/// </summary>
public static LocalisableString DisableTapsDuringGameplay => new TranslatableString(getKey(@"disable_taps_during_gameplay"), @"Disable taps during gameplay");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -407,6 +407,8 @@ namespace osu.Game
}) })
}); });
base.Content.Add(new TouchInputInterceptor());
KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider); KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider);
KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets);
@ -575,14 +577,14 @@ namespace osu.Game
case JoystickHandler jh: case JoystickHandler jh:
return new JoystickSettings(jh); return new JoystickSettings(jh);
case TouchHandler th:
return new TouchSettings(th);
} }
} }
switch (handler) switch (handler)
{ {
case TouchHandler th:
return new TouchSettings(th);
case MidiHandler: case MidiHandler:
return new InputSection.HandlerSection(handler); return new InputSection.HandlerSection(handler);

View File

@ -55,7 +55,7 @@ namespace osu.Game.Overlays.BeatmapSet
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
CornerRadius = 4, CornerRadius = 4,
Masking = true, Masking = true,
Child = avatar = new UpdateableAvatar(showGuestOnNull: false) Child = avatar = new UpdateableAvatar(showUserPanelOnHover: true, showGuestOnNull: false)
{ {
Size = new Vector2(height), Size = new Vector2(height),
}, },

View File

@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Comments
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new CommentMarkdownHeading(headingBlock); protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new CommentMarkdownHeading(headingBlock);
public override MarkdownTextFlowContainer CreateTextFlow() => new CommentMarkdownTextFlowContainer(); public override OsuMarkdownTextFlowContainer CreateTextFlow() => new CommentMarkdownTextFlowContainer();
private partial class CommentMarkdownHeading : OsuMarkdownHeading private partial class CommentMarkdownHeading : OsuMarkdownHeading
{ {
@ -49,14 +49,14 @@ namespace osu.Game.Overlays.Comments
} }
} }
private partial class CommentMarkdownTextFlowContainer : MarkdownTextFlowContainer private partial class CommentMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
{ {
protected override void AddImage(LinkInline linkInline) => AddDrawable(new CommentMarkdownImage(linkInline.Url)); protected override void AddImage(LinkInline linkInline) => AddDrawable(new CommentMarkdownImage(linkInline));
private partial class CommentMarkdownImage : MarkdownImage private partial class CommentMarkdownImage : OsuMarkdownImage
{ {
public CommentMarkdownImage(string url) public CommentMarkdownImage(LinkInline linkInline)
: base(url) : base(linkInline)
{ {
} }

View File

@ -102,7 +102,7 @@ namespace osu.Game.Overlays.Comments
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 20 }, Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 20 },
Children = new Drawable[] Children = new Drawable[]
{ {
avatar = new UpdateableAvatar(api.LocalUser.Value) avatar = new UpdateableAvatar(api.LocalUser.Value, isInteractive: false)
{ {
Size = new Vector2(50), Size = new Vector2(50),
CornerExponent = 2, CornerExponent = 2,

View File

@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Comments
Size = new Vector2(avatar_size), Size = new Vector2(avatar_size),
Children = new Drawable[] Children = new Drawable[]
{ {
new UpdateableAvatar(Comment.User) new UpdateableAvatar(Comment.User, showUserPanelOnHover: true)
{ {
Size = new Vector2(avatar_size), Size = new Vector2(avatar_size),
Masking = true, Masking = true,

View File

@ -115,7 +115,7 @@ namespace osu.Game.Overlays.Mods
{ {
Name = nameTextBox.Current.Value, Name = nameTextBox.Current.Value,
Description = descriptionTextBox.Current.Value, Description = descriptionTextBox.Current.Value,
Mods = selectedMods.Value.ToArray(), Mods = selectedMods.Value.Where(mod => mod.Type != ModType.System).ToArray(),
Ruleset = r.Find<RulesetInfo>(ruleset.Value.ShortName)! Ruleset = r.Find<RulesetInfo>(ruleset.Value.ShortName)!
})); }));

View File

@ -153,7 +153,7 @@ namespace osu.Game.Overlays.Mods
private void useCurrentMods() private void useCurrentMods()
{ {
saveableMods = selectedMods.Value.ToHashSet(); saveableMods = selectedMods.Value.Where(mod => mod.Type != ModType.System).ToHashSet();
updateState(); updateState();
} }
@ -168,7 +168,7 @@ namespace osu.Game.Overlays.Mods
if (!selectedMods.Value.Any()) if (!selectedMods.Value.Any())
return false; return false;
return !saveableMods.SetEquals(selectedMods.Value); return !saveableMods.SetEquals(selectedMods.Value.Where(mod => mod.Type != ModType.System));
} }
private void save() private void save()

View File

@ -1,7 +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.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -56,17 +55,14 @@ namespace osu.Game.Overlays.Mods
protected override void Select() protected override void Select()
{ {
// if the preset is not active at the point of the user click, then set the mods using the preset directly, discarding any previous selections, var selectedSystemMods = selectedMods.Value.Where(mod => mod.Type == ModType.System);
// which will also have the side effect of activating the preset (see `updateActiveState()`). // will also have the side effect of activating the preset (see `updateActiveState()`).
selectedMods.Value = Preset.Value.Mods.ToArray(); selectedMods.Value = Preset.Value.Mods.Concat(selectedSystemMods).ToArray();
} }
protected override void Deselect() protected override void Deselect()
{ {
// if the preset is active when the user has clicked it, then it means that the set of active mods is exactly equal to the set of mods in the preset selectedMods.Value = selectedMods.Value.Except(Preset.Value.Mods).ToArray();
// (there are no other active mods than what the preset specifies, and the mod settings match exactly).
// therefore it's safe to just clear selected mods, since it will have the effect of toggling the preset off.
selectedMods.Value = Array.Empty<Mod>();
} }
private void selectedModsChanged() private void selectedModsChanged()
@ -79,7 +75,7 @@ namespace osu.Game.Overlays.Mods
private void updateActiveState() private void updateActiveState()
{ {
Active.Value = new HashSet<Mod>(Preset.Value.Mods).SetEquals(selectedMods.Value); Active.Value = new HashSet<Mod>(Preset.Value.Mods).SetEquals(selectedMods.Value.Where(mod => mod.Type != ModType.System));
} }
#region Filtering support #region Filtering support

View File

@ -41,6 +41,7 @@ namespace osu.Game.Overlays.Mods
private void updateEnabledState() private void updateEnabledState()
{ {
Enabled.Value = availableMods.Value Enabled.Value = availableMods.Value
.Where(pair => pair.Key != ModType.System)
.SelectMany(pair => pair.Value) .SelectMany(pair => pair.Value)
.Any(modState => !modState.Active.Value && modState.Visible); .Any(modState => !modState.Active.Value && modState.Visible);
} }

View File

@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = MouseSettingsStrings.DisableMouseButtons, LabelText = MouseSettingsStrings.DisableClicksDuringGameplay,
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons) Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons)
}, },
}; };

View File

@ -3,38 +3,48 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Input.Handlers;
using osu.Framework.Input.Handlers.Touch;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Localisation; using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Input namespace osu.Game.Overlays.Settings.Sections.Input
{ {
/// <summary>
/// Touch input settings subsection common to all touch handlers (even on different platforms).
/// </summary>
public partial class TouchSettings : SettingsSubsection public partial class TouchSettings : SettingsSubsection
{ {
private readonly TouchHandler handler; private readonly InputHandler handler;
public TouchSettings(TouchHandler handler) protected override LocalisableString Header => TouchSettingsStrings.Touch;
public TouchSettings(InputHandler handler)
{ {
this.handler = handler; this.handler = handler;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OsuConfigManager osuConfig)
{ {
Children = new Drawable[] if (!RuntimeInfo.IsMobile) // don't allow disabling the only input method (touch) on mobile.
{ {
new SettingsCheckbox Add(new SettingsCheckbox
{ {
LabelText = CommonStrings.Enabled, LabelText = CommonStrings.Enabled,
Current = handler.Enabled Current = handler.Enabled
}, });
}; }
Add(new SettingsCheckbox
{
LabelText = TouchSettingsStrings.DisableTapsDuringGameplay,
Current = osuConfig.GetBindable<bool>(OsuSetting.TouchDisableGameplayTaps)
});
} }
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"touchscreen" }); public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"touchscreen" });
protected override LocalisableString Header => handler.Description;
} }
} }

View File

@ -8,6 +8,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -153,6 +154,8 @@ namespace osu.Game.Overlays.SkinEditor
Items = new[] Items = new[]
{ {
new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()),
new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } },
new EditorMenuItemSpacer(),
new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))),
new EditorMenuItemSpacer(), new EditorMenuItemSpacer(),
new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()),
@ -406,7 +409,14 @@ namespace osu.Game.Overlays.SkinEditor
cp.Colour = colours.Yellow; cp.Colour = colours.Yellow;
}); });
changeHandler?.Dispose();
skins.EnsureMutableSkin(); skins.EnsureMutableSkin();
var targetContainer = getTarget(selectedTarget.Value);
if (targetContainer != null)
changeHandler = new SkinEditorChangeHandler(targetContainer);
hasBegunMutating = true; hasBegunMutating = true;
} }

View File

@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Utils; using osu.Framework.Utils;
@ -31,8 +32,44 @@ namespace osu.Game.Overlays.SkinEditor
UpdatePosition = updateDrawablePosition UpdatePosition = updateDrawablePosition
}; };
private bool allSelectedSupportManualSizing(Axes axis) => SelectedItems.All(b => (b as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(axis) == false);
public override bool HandleScale(Vector2 scale, Anchor anchor) public override bool HandleScale(Vector2 scale, Anchor anchor)
{ {
Axes adjustAxis;
switch (anchor)
{
// for corners, adjust scale.
case Anchor.TopLeft:
case Anchor.TopRight:
case Anchor.BottomLeft:
case Anchor.BottomRight:
adjustAxis = Axes.Both;
break;
// for edges, adjust size.
// autosize elements can't be easily handled so just disable sizing for now.
case Anchor.TopCentre:
case Anchor.BottomCentre:
if (!allSelectedSupportManualSizing(Axes.Y))
return false;
adjustAxis = Axes.Y;
break;
case Anchor.CentreLeft:
case Anchor.CentreRight:
if (!allSelectedSupportManualSizing(Axes.X))
return false;
adjustAxis = Axes.X;
break;
default:
throw new ArgumentOutOfRangeException(nameof(anchor), anchor, null);
}
// convert scale to screen space // convert scale to screen space
scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero); scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero);
@ -120,7 +157,20 @@ namespace osu.Game.Overlays.SkinEditor
if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90)) if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90))
currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X); currentScaledDelta = new Vector2(scaledDelta.Y, scaledDelta.X);
drawableItem.Scale *= currentScaledDelta; switch (adjustAxis)
{
case Axes.X:
drawableItem.Width *= currentScaledDelta.X;
break;
case Axes.Y:
drawableItem.Height *= currentScaledDelta.Y;
break;
case Axes.Both:
drawableItem.Scale *= currentScaledDelta;
break;
}
} }
return true; return true;
@ -169,8 +219,9 @@ namespace osu.Game.Overlays.SkinEditor
{ {
base.OnSelectionChanged(); base.OnSelectionChanged();
SelectionBox.CanScaleX = true; SelectionBox.CanScaleX = allSelectedSupportManualSizing(Axes.X);
SelectionBox.CanScaleY = true; SelectionBox.CanScaleY = allSelectedSupportManualSizing(Axes.Y);
SelectionBox.CanScaleDiagonally = true;
SelectionBox.CanFlipX = true; SelectionBox.CanFlipX = true;
SelectionBox.CanFlipY = true; SelectionBox.CanFlipY = true;
SelectionBox.CanReverse = false; SelectionBox.CanReverse = false;
@ -215,7 +266,15 @@ namespace osu.Game.Overlays.SkinEditor
yield return new OsuMenuItem("Reset scale", MenuItemType.Standard, () => yield return new OsuMenuItem("Reset scale", MenuItemType.Standard, () =>
{ {
foreach (var blueprint in SelectedBlueprints) foreach (var blueprint in SelectedBlueprints)
((Drawable)blueprint.Item).Scale = Vector2.One; {
var blueprintItem = ((Drawable)blueprint.Item);
blueprintItem.Scale = Vector2.One;
if (blueprintItem.RelativeSizeAxes.HasFlagFast(Axes.X))
blueprintItem.Width = 1;
if (blueprintItem.RelativeSizeAxes.HasFlagFast(Axes.Y))
blueprintItem.Height = 1;
}
}); });
yield return new EditorMenuItemSpacer(); yield return new EditorMenuItemSpacer();

View File

@ -7,7 +7,6 @@ using Markdig.Extensions.Yaml;
using Markdig.Syntax; using Markdig.Syntax;
using Markdig.Syntax.Inlines; using Markdig.Syntax.Inlines;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Game.Graphics.Containers.Markdown; using osu.Game.Graphics.Containers.Markdown;
namespace osu.Game.Overlays.Wiki.Markdown namespace osu.Game.Overlays.Wiki.Markdown
@ -53,7 +52,7 @@ namespace osu.Game.Overlays.Wiki.Markdown
base.AddMarkdownComponent(markdownObject, container, level); base.AddMarkdownComponent(markdownObject, container, level);
} }
public override MarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer(); public override OsuMarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer();
private partial class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer private partial class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
{ {

View File

@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Wiki
public override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(Typeface.Torus, weight: FontWeight.Bold)); public override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(Typeface.Torus, weight: FontWeight.Bold));
public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f => f.TextAnchor = Anchor.TopCentre); public override OsuMarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(f => f.TextAnchor = Anchor.TopCentre);
protected override MarkdownParagraph CreateParagraph(ParagraphBlock paragraphBlock, int level) protected override MarkdownParagraph CreateParagraph(ParagraphBlock paragraphBlock, int level)
=> base.CreateParagraph(paragraphBlock, level).With(p => p.Margin = new MarginPadding { Bottom = 10 }); => base.CreateParagraph(paragraphBlock, level).With(p => p.Margin = new MarginPadding { Bottom = 10 });

View File

@ -59,6 +59,13 @@ namespace osu.Game.Rulesets.Mods
/// </summary> /// </summary>
bool ValidForMultiplayerAsFreeMod { get; } bool ValidForMultiplayerAsFreeMod { get; }
/// <summary>
/// Indicates that this mod is always permitted in scenarios wherein a user is submitting a score regardless of other circumstances.
/// Intended for mods that are informational in nature and do not really affect gameplay by themselves,
/// but are more of a gauge of increased/decreased difficulty due to the user's configuration (e.g. <see cref="ModTouchDevice"/>).
/// </summary>
bool AlwaysValidForSubmission { get; }
/// <summary> /// <summary>
/// Create a fresh <see cref="Mod"/> instance based on this mod. /// Create a fresh <see cref="Mod"/> instance based on this mod.
/// </summary> /// </summary>

View File

@ -156,6 +156,10 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore] [JsonIgnore]
public virtual bool ValidForMultiplayerAsFreeMod => true; public virtual bool ValidForMultiplayerAsFreeMod => true;
/// <inheritdoc/>
[JsonIgnore]
public virtual bool AlwaysValidForSubmission => false;
/// <summary> /// <summary>
/// Whether this mod requires configuration to apply changes to the game. /// Whether this mod requires configuration to apply changes to the game.
/// </summary> /// </summary>

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mods
public sealed override bool ValidForMultiplayer => false; public sealed override bool ValidForMultiplayer => false;
public sealed override bool ValidForMultiplayerAsFreeMod => false; public sealed override bool ValidForMultiplayerAsFreeMod => false;
public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModAdaptiveSpeed) }; public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModAdaptiveSpeed), typeof(ModTouchDevice) };
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;

View File

@ -0,0 +1,24 @@
// 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 osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
public class ModTouchDevice : Mod, IApplicableMod
{
public sealed override string Name => "Touch Device";
public sealed override string Acronym => "TD";
public sealed override IconUsage? Icon => OsuIcon.PlayStyleTouch;
public sealed override LocalisableString Description => "Automatically applied to plays on devices with a touchscreen.";
public sealed override double ScoreMultiplier => 1;
public sealed override ModType Type => ModType.System;
public sealed override bool ValidForMultiplayer => false;
public sealed override bool ValidForMultiplayerAsFreeMod => false;
public sealed override bool AlwaysValidForSubmission => true;
public override Type[] IncompatibleMods => new[] { typeof(ICreateReplayData) };
}
}

View File

@ -204,6 +204,8 @@ namespace osu.Game.Rulesets
public ModAutoplay? GetAutoplayMod() => CreateMod<ModAutoplay>(); public ModAutoplay? GetAutoplayMod() => CreateMod<ModAutoplay>();
public ModTouchDevice? GetTouchDeviceMod() => CreateMod<ModTouchDevice>();
/// <summary> /// <summary>
/// Create a transformer which adds lookups specific to a ruleset to skin sources. /// Create a transformer which adds lookups specific to a ruleset to skin sources.
/// </summary> /// </summary>

View File

@ -350,6 +350,9 @@ namespace osu.Game.Rulesets.Scoring
if (maxResult.IsBonus() && minResult != HitResult.IgnoreMiss) if (maxResult.IsBonus() && minResult != HitResult.IgnoreMiss)
throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.IgnoreMiss} is the only valid minimum result for a {maxResult} judgement."); throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.IgnoreMiss} is the only valid minimum result for a {maxResult} judgement.");
if (minResult == HitResult.IgnoreMiss)
return;
if (maxResult == HitResult.LargeTickHit && minResult != HitResult.LargeTickMiss) if (maxResult == HitResult.LargeTickHit && minResult != HitResult.LargeTickMiss)
throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.LargeTickMiss} is the only valid minimum result for a {maxResult} judgement."); throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.LargeTickMiss} is the only valid minimum result for a {maxResult} judgement.");

View File

@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.UI
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons); mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
tapsDisabled = config.GetBindable<bool>(OsuSetting.TouchDisableGameplayTaps);
} }
#region Action mapping (for replays) #region Action mapping (for replays)
@ -124,6 +125,7 @@ namespace osu.Game.Rulesets.UI
#region Setting application (disables etc.) #region Setting application (disables etc.)
private Bindable<bool> mouseDisabled; private Bindable<bool> mouseDisabled;
private Bindable<bool> tapsDisabled;
protected override bool Handle(UIEvent e) protected override bool Handle(UIEvent e)
{ {
@ -147,9 +149,9 @@ namespace osu.Game.Rulesets.UI
protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e) protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e)
{ {
if (mouseDisabled.Value) if (tapsDisabled.Value)
{ {
// Only propagate positional data when mouse buttons are disabled. // Only propagate positional data when taps are disabled.
e = new TouchStateChangeEvent(e.State, e.Input, e.Touch, false, e.LastPosition); e = new TouchStateChangeEvent(e.State, e.Input, e.Touch, false, e.LastPosition);
} }

View File

@ -13,7 +13,8 @@ namespace osu.Game.Screens
{ {
public abstract partial class BackgroundScreen : Screen, IEquatable<BackgroundScreen> public abstract partial class BackgroundScreen : Screen, IEquatable<BackgroundScreen>
{ {
protected const float TRANSITION_LENGTH = 500; public const float TRANSITION_LENGTH = 500;
private const float x_movement_amount = 50; private const float x_movement_amount = 50;
private readonly bool animateOnEnter; private readonly bool animateOnEnter;

View File

@ -3,11 +3,14 @@
#nullable disable #nullable disable
using System.Diagnostics;
using System.Threading; using System.Threading;
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.Logging; using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -34,6 +37,9 @@ namespace osu.Game.Screens.Backgrounds
[Resolved] [Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved]
private GameHost gameHost { get; set; }
protected virtual bool AllowStoryboardBackground => true; protected virtual bool AllowStoryboardBackground => true;
public BackgroundScreenDefault(bool animateOnEnter = true) public BackgroundScreenDefault(bool animateOnEnter = true)
@ -71,6 +77,34 @@ namespace osu.Game.Screens.Backgrounds
void next() => Next(); void next() => Next();
} }
private ScheduledDelegate storyboardUnloadDelegate;
public override void OnSuspending(ScreenTransitionEvent e)
{
var backgroundScreenStack = Parent as BackgroundScreenStack;
Debug.Assert(backgroundScreenStack != null);
if (background is BeatmapBackgroundWithStoryboard storyboardBackground)
storyboardUnloadDelegate = gameHost.UpdateThread.Scheduler.AddDelayed(storyboardBackground.UnloadStoryboard, TRANSITION_LENGTH);
base.OnSuspending(e);
}
public override void OnResuming(ScreenTransitionEvent e)
{
if (background is BeatmapBackgroundWithStoryboard storyboardBackground)
{
if (storyboardUnloadDelegate?.Completed == false)
storyboardUnloadDelegate.Cancel();
else
storyboardBackground.LoadStoryboard();
storyboardUnloadDelegate = null;
}
base.OnResuming(e);
}
private ScheduledDelegate nextTask; private ScheduledDelegate nextTask;
private CancellationTokenSource cancellationTokenSource; private CancellationTokenSource cancellationTokenSource;

View File

@ -60,7 +60,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private bool canScaleX; private bool canScaleX;
/// <summary> /// <summary>
/// Whether horizontal scaling support should be enabled. /// Whether horizontal scaling (from the left or right edge) support should be enabled.
/// </summary> /// </summary>
public bool CanScaleX public bool CanScaleX
{ {
@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private bool canScaleY; private bool canScaleY;
/// <summary> /// <summary>
/// Whether vertical scaling support should be enabled. /// Whether vertical scaling (from the top or bottom edge) support should be enabled.
/// </summary> /// </summary>
public bool CanScaleY public bool CanScaleY
{ {
@ -91,6 +91,27 @@ namespace osu.Game.Screens.Edit.Compose.Components
} }
} }
private bool canScaleDiagonally;
/// <summary>
/// Whether diagonal scaling (from a corner) support should be enabled.
/// </summary>
/// <remarks>
/// There are some cases where we only want to allow proportional resizing, and not allow
/// one or both explicit directions of scale.
/// </remarks>
public bool CanScaleDiagonally
{
get => canScaleDiagonally;
set
{
if (canScaleDiagonally == value) return;
canScaleDiagonally = value;
recreate();
}
}
private bool canFlipX; private bool canFlipX;
/// <summary> /// <summary>
@ -245,7 +266,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
}; };
if (CanScaleX) addXScaleComponents(); if (CanScaleX) addXScaleComponents();
if (CanScaleX && CanScaleY) addFullScaleComponents(); if (CanScaleDiagonally) addFullScaleComponents();
if (CanScaleY) addYScaleComponents(); if (CanScaleY) addYScaleComponents();
if (CanFlipX) addXFlipComponents(); if (CanFlipX) addXFlipComponents();
if (CanFlipY) addYFlipComponents(); if (CanFlipY) addYFlipComponents();

View File

@ -1095,6 +1095,19 @@ namespace osu.Game.Screens.Edit
protected void CreateNewDifficulty(RulesetInfo rulesetInfo) protected void CreateNewDifficulty(RulesetInfo rulesetInfo)
{ {
if (isNewBeatmap)
{
dialogOverlay.Push(new SaveRequiredPopupDialog("This beatmap will be saved in order to create another difficulty.", () =>
{
if (!Save())
return;
CreateNewDifficulty(rulesetInfo);
}));
return;
}
if (!rulesetInfo.Equals(editorBeatmap.BeatmapInfo.Ruleset)) if (!rulesetInfo.Equals(editorBeatmap.BeatmapInfo.Ruleset))
{ {
switchToNewDifficulty(rulesetInfo, false); switchToNewDifficulty(rulesetInfo, false);

View File

@ -85,7 +85,8 @@ namespace osu.Game.Screens.Menu
private readonly List<MainMenuButton> buttonsTopLevel = new List<MainMenuButton>(); private readonly List<MainMenuButton> buttonsTopLevel = new List<MainMenuButton>();
private readonly List<MainMenuButton> buttonsPlay = new List<MainMenuButton>(); private readonly List<MainMenuButton> buttonsPlay = new List<MainMenuButton>();
private Sample sampleBack; private Sample sampleBackToLogo;
private Sample sampleLogoSwoosh;
private readonly LogoTrackingContainer logoTrackingContainer; private readonly LogoTrackingContainer logoTrackingContainer;
@ -104,7 +105,7 @@ namespace osu.Game.Screens.Menu
buttonArea.AddRange(new Drawable[] buttonArea.AddRange(new Drawable[]
{ {
new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel,
-WEDGE_WIDTH) -WEDGE_WIDTH)
{ {
VisibleState = ButtonSystemState.Play, VisibleState = ButtonSystemState.Play,
@ -127,14 +128,14 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(AudioManager audio, IdleTracker idleTracker, GameHost host) private void load(AudioManager audio, IdleTracker idleTracker, GameHost host)
{ {
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-default-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D));
if (host.CanExit) if (host.CanExit)
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
@ -155,7 +156,8 @@ namespace osu.Game.Screens.Menu
if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle);
sampleBack = audio.Samples.Get(@"Menu/button-back-select"); sampleBackToLogo = audio.Samples.Get(@"Menu/back-to-logo");
sampleLogoSwoosh = audio.Samples.Get(@"Menu/osu-logo-swoosh");
} }
private void onMultiplayer() private void onMultiplayer()
@ -197,6 +199,7 @@ namespace osu.Game.Screens.Menu
{ {
if (State == ButtonSystemState.Initial) if (State == ButtonSystemState.Initial)
{ {
StopSamplePlayback();
logo?.TriggerClick(); logo?.TriggerClick();
return true; return true;
} }
@ -260,10 +263,15 @@ namespace osu.Game.Screens.Menu
{ {
case ButtonSystemState.TopLevel: case ButtonSystemState.TopLevel:
State = ButtonSystemState.Initial; State = ButtonSystemState.Initial;
sampleBack?.Play();
// Samples are explicitly played here in response to user interaction and not when transitioning due to idle.
StopSamplePlayback();
sampleBackToLogo?.Play();
return true; return true;
case ButtonSystemState.Play: case ButtonSystemState.Play:
StopSamplePlayback();
backButton.TriggerClick(); backButton.TriggerClick();
return true; return true;
@ -272,6 +280,13 @@ namespace osu.Game.Screens.Menu
} }
} }
public void StopSamplePlayback()
{
buttonsPlay.ForEach(button => button.StopSamplePlayback());
buttonsTopLevel.ForEach(button => button.StopSamplePlayback());
logo?.StopSamplePlayback();
}
private bool onOsuLogo() private bool onOsuLogo()
{ {
switch (state) switch (state)
@ -346,6 +361,9 @@ namespace osu.Game.Screens.Menu
logo?.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); logo?.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo);
logo?.ScaleTo(1, 800, Easing.OutExpo); logo?.ScaleTo(1, 800, Easing.OutExpo);
}, buttonArea.Alpha * 150); }, buttonArea.Alpha * 150);
if (lastState == ButtonSystemState.TopLevel)
sampleLogoSwoosh?.Play();
break; break;
case ButtonSystemState.TopLevel: case ButtonSystemState.TopLevel:

View File

@ -7,6 +7,8 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using JetBrains.Annotations; using JetBrains.Annotations;
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.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -89,8 +91,10 @@ namespace osu.Game.Screens.Menu
private SongTicker songTicker; private SongTicker songTicker;
private Container logoTarget; private Container logoTarget;
private Sample reappearSampleSwoosh;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics, AudioManager audio)
{ {
holdDelay = config.GetBindable<double>(OsuSetting.UIHoldActivationDelay); holdDelay = config.GetBindable<double>(OsuSetting.UIHoldActivationDelay);
loginDisplayed = statics.GetBindable<bool>(Static.LoginOverlayDisplayed); loginDisplayed = statics.GetBindable<bool>(Static.LoginOverlayDisplayed);
@ -162,6 +166,8 @@ namespace osu.Game.Screens.Menu
Buttons.OnSettings = () => settings?.ToggleVisibility(); Buttons.OnSettings = () => settings?.ToggleVisibility();
Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility(); Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility();
reappearSampleSwoosh = audio.Samples.Get(@"Menu/reappear-swoosh");
preloadSongSelect(); preloadSongSelect();
} }
@ -291,6 +297,10 @@ namespace osu.Game.Screens.Menu
{ {
base.OnResuming(e); base.OnResuming(e);
// Ensures any playing `ButtonSystem` samples are stopped when returning to MainMenu (as to not overlap with the 'back' sample)
Buttons.StopSamplePlayback();
reappearSampleSwoosh?.Play();
ApplyToBackground(b => (b as BackgroundScreenDefault)?.Next()); ApplyToBackground(b => (b as BackgroundScreenDefault)?.Next());
// we may have consumed our preloaded instance, so let's make another. // we may have consumed our preloaded instance, so let's make another.

View File

@ -51,6 +51,7 @@ namespace osu.Game.Screens.Menu
private readonly Action clickAction; private readonly Action clickAction;
private Sample sampleClick; private Sample sampleClick;
private Sample sampleHover; private Sample sampleHover;
private SampleChannel sampleChannel;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
@ -225,7 +226,8 @@ namespace osu.Game.Screens.Menu
private void trigger() private void trigger()
{ {
sampleClick?.Play(); sampleChannel = sampleClick?.GetChannel();
sampleChannel?.Play();
clickAction?.Invoke(); clickAction?.Invoke();
@ -237,6 +239,8 @@ namespace osu.Game.Screens.Menu
public override bool HandleNonPositionalInput => state == ButtonState.Expanded; public override bool HandleNonPositionalInput => state == ButtonState.Expanded;
public override bool HandlePositionalInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f; public override bool HandlePositionalInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f;
public void StopSamplePlayback() => sampleChannel?.Stop();
protected override void Update() protected override void Update()
{ {
iconText.Alpha = Math.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); iconText.Alpha = Math.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1);

View File

@ -52,6 +52,8 @@ namespace osu.Game.Screens.Menu
private readonly IntroSequence intro; private readonly IntroSequence intro;
private Sample sampleClick; private Sample sampleClick;
private SampleChannel sampleClickChannel;
private Sample sampleBeat; private Sample sampleBeat;
private Sample sampleDownbeat; private Sample sampleDownbeat;
@ -391,7 +393,11 @@ namespace osu.Game.Screens.Menu
flashLayer.FadeOut(1500, Easing.OutExpo); flashLayer.FadeOut(1500, Easing.OutExpo);
if (Action?.Invoke() == true) if (Action?.Invoke() == true)
sampleClick.Play(); {
StopSamplePlayback();
sampleClickChannel = sampleClick.GetChannel();
sampleClickChannel.Play();
}
return true; return true;
} }
@ -440,6 +446,8 @@ namespace osu.Game.Screens.Menu
private Container currentProxyTarget; private Container currentProxyTarget;
private Drawable proxy; private Drawable proxy;
public void StopSamplePlayback() => sampleClickChannel?.Stop();
public Drawable ProxyToContainer(Container c) public Drawable ProxyToContainer(Container c)
{ {
if (currentProxyTarget != null) if (currentProxyTarget != null)

View File

@ -115,7 +115,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"27252d"), Colour = Color4Extensions.FromHex(@"27252d"),
}, },
avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both }, avatar = new UpdateableAvatar(showUserPanelOnHover: true) { RelativeSizeAxes = Axes.Both },
}; };
} }
} }

View File

@ -289,7 +289,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
set => avatar.User = value; set => avatar.User = value;
} }
private readonly UpdateableAvatar avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both }; private readonly UpdateableAvatar avatar = new UpdateableAvatar(showUserPanelOnHover: true) { RelativeSizeAxes = Axes.Both };
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colours) private void load(OverlayColourProvider colours)

View File

@ -10,8 +10,6 @@ namespace osu.Game.Screens.Play
{ {
public partial class ArgonKeyCounterDisplay : KeyCounterDisplay public partial class ArgonKeyCounterDisplay : KeyCounterDisplay
{ {
private const int duration = 100;
protected override FillFlowContainer<KeyCounter> KeyFlow { get; } protected override FillFlowContainer<KeyCounter> KeyFlow { get; }
public ArgonKeyCounterDisplay() public ArgonKeyCounterDisplay()
@ -25,16 +23,6 @@ namespace osu.Game.Screens.Play
}; };
} }
protected override void Update()
{
base.Update();
Size = KeyFlow.Size;
}
protected override KeyCounter CreateCounter(InputTrigger trigger) => new ArgonKeyCounter(trigger); protected override KeyCounter CreateCounter(InputTrigger trigger) => new ArgonKeyCounter(trigger);
protected override void UpdateVisibility()
=> KeyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration);
} }
} }

View File

@ -0,0 +1,110 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Localisation.SkinComponents;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonAccuracyCounter : GameplayAccuracyCounter, ISerialisableDrawable
{
protected override double RollingDuration => 500;
protected override Easing RollingEasing => Easing.OutQuint;
[SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{
Precision = 0.01f,
MinValue = 0,
MaxValue = 1,
};
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))]
public Bindable<bool> ShowLabel { get; } = new BindableBool(true);
public bool UsesFixedAnchor { get; set; }
protected override IHasText CreateText() => new ArgonAccuracyTextComponent
{
WireframeOpacity = { BindTarget = WireframeOpacity },
ShowLabel = { BindTarget = ShowLabel },
};
private partial class ArgonAccuracyTextComponent : CompositeDrawable, IHasText
{
private readonly ArgonCounterTextComponent wholePart;
private readonly ArgonCounterTextComponent fractionPart;
private readonly ArgonCounterTextComponent percentText;
public IBindable<float> WireframeOpacity { get; } = new BindableFloat();
public Bindable<bool> ShowLabel { get; } = new BindableBool();
public LocalisableString Text
{
get => wholePart.Text;
set
{
string[] split = value.ToString().Replace("%", string.Empty).Split(".");
wholePart.Text = split[0];
fractionPart.Text = "." + split[1];
}
}
public ArgonAccuracyTextComponent()
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new Container
{
AutoSizeAxes = Axes.Both,
Child = wholePart = new ArgonCounterTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersAccuracy.ToUpper())
{
RequiredDisplayDigits = { Value = 3 },
WireframeOpacity = { BindTarget = WireframeOpacity },
ShowLabel = { BindTarget = ShowLabel },
}
},
fractionPart = new ArgonCounterTextComponent(Anchor.TopLeft)
{
WireframeOpacity = { BindTarget = WireframeOpacity },
Scale = new Vector2(0.5f),
},
percentText = new ArgonCounterTextComponent(Anchor.TopLeft)
{
Text = @"%",
WireframeOpacity = { BindTarget = WireframeOpacity }
},
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
ShowLabel.BindValueChanged(s =>
{
fractionPart.Margin = new MarginPadding { Top = s.NewValue ? 12f * 2f + 4f : 4f }; // +4 to account for the extra spaces above the digits.
percentText.Margin = new MarginPadding { Top = s.NewValue ? 12f : 0 };
}, true);
}
}
}
}

View File

@ -0,0 +1,68 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Localisation.SkinComponents;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets.Scoring;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonComboCounter : ComboCounter
{
private ArgonCounterTextComponent text = null!;
protected override double RollingDuration => 500;
protected override Easing RollingEasing => Easing.OutQuint;
[SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{
Precision = 0.01f,
MinValue = 0,
MaxValue = 1,
};
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))]
public Bindable<bool> ShowLabel { get; } = new BindableBool(true);
[BackgroundDependencyLoader]
private void load(ScoreProcessor scoreProcessor)
{
Current.BindTo(scoreProcessor.Combo);
Current.BindValueChanged(combo =>
{
bool wasIncrease = combo.NewValue > combo.OldValue;
bool wasMiss = combo.OldValue > 1 && combo.NewValue == 0;
float newScale = Math.Clamp(text.NumberContainer.Scale.X * (wasIncrease ? 1.1f : 0.8f), 0.6f, 1.4f);
float duration = wasMiss ? 2000 : 500;
text.NumberContainer
.ScaleTo(new Vector2(newScale))
.ScaleTo(Vector2.One, duration, Easing.OutQuint);
if (wasMiss)
text.FlashColour(Color4.Red, duration, Easing.OutQuint);
});
}
protected override LocalisableString FormatCount(int count) => $@"{count}x";
protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper())
{
WireframeOpacity = { BindTarget = WireframeOpacity },
ShowLabel = { BindTarget = ShowLabel },
};
}
}

View File

@ -0,0 +1,173 @@
// 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 System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Localisation;
using osu.Framework.Text;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonCounterTextComponent : CompositeDrawable, IHasText
{
private readonly ArgonCounterSpriteText wireframesPart;
private readonly ArgonCounterSpriteText textPart;
private readonly OsuSpriteText labelText;
public IBindable<float> WireframeOpacity { get; } = new BindableFloat();
public Bindable<int> RequiredDisplayDigits { get; } = new BindableInt();
public Bindable<bool> ShowLabel { get; } = new BindableBool();
public Container NumberContainer { get; private set; }
public LocalisableString Text
{
get => textPart.Text;
set
{
int remainingCount = RequiredDisplayDigits.Value - value.ToString().Count(char.IsDigit);
string remainingText = remainingCount > 0 ? new string('#', remainingCount) : string.Empty;
wireframesPart.Text = remainingText + value;
textPart.Text = value;
}
}
public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null)
{
Anchor = anchor;
Origin = anchor;
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
labelText = new OsuSpriteText
{
Alpha = 0,
Text = label.GetValueOrDefault(),
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold),
Margin = new MarginPadding { Left = 2.5f },
},
NumberContainer = new Container
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
wireframesPart = new ArgonCounterSpriteText(wireframesLookup)
{
Anchor = anchor,
Origin = anchor,
},
textPart = new ArgonCounterSpriteText(textLookup)
{
Anchor = anchor,
Origin = anchor,
},
}
}
}
};
}
private string textLookup(char c)
{
switch (c)
{
case '.':
return @"dot";
case '%':
return @"percentage";
default:
return c.ToString();
}
}
private string wireframesLookup(char c)
{
if (c == '.') return @"dot";
return @"wireframes";
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
labelText.Colour = colours.Blue0;
}
protected override void LoadComplete()
{
base.LoadComplete();
WireframeOpacity.BindValueChanged(v => wireframesPart.Alpha = v.NewValue, true);
ShowLabel.BindValueChanged(s => labelText.Alpha = s.NewValue ? 1 : 0, true);
}
private partial class ArgonCounterSpriteText : OsuSpriteText
{
private readonly Func<char, string> getLookup;
private GlyphStore glyphStore = null!;
protected override char FixedWidthReferenceCharacter => '5';
public ArgonCounterSpriteText(Func<char, string> getLookup)
{
this.getLookup = getLookup;
Shadow = false;
UseFullGlyphHeight = false;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Spacing = new Vector2(-2f, 0f);
Font = new FontUsage(@"argon-counter", 1);
glyphStore = new GlyphStore(textures, getLookup);
}
protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore);
private class GlyphStore : ITexturedGlyphLookupStore
{
private readonly TextureStore textures;
private readonly Func<char, string> getLookup;
public GlyphStore(TextureStore textures, Func<char, string> getLookup)
{
this.textures = textures;
this.getLookup = getLookup;
}
public ITexturedCharacterGlyph? Get(string fontName, char character)
{
string lookup = getLookup(character);
var texture = textures.Get($"Gameplay/Fonts/{fontName}-{lookup}");
if (texture == null)
return null;
return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 0.125f);
}
public Task<ITexturedCharacterGlyph?> GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character));
}
}
}
}

View File

@ -35,13 +35,8 @@ namespace osu.Game.Screens.Play.HUD
Precision = 1 Precision = 1
}; };
[SettingSource("Bar length")] [SettingSource("Use relative size")]
public BindableFloat BarLength { get; } = new BindableFloat(0.98f) public BindableBool UseRelativeSize { get; } = new BindableBool(true);
{
MinValue = 0.2f,
MaxValue = 1,
Precision = 0.01f,
};
private BarPath mainBar = null!; private BarPath mainBar = null!;
@ -92,12 +87,30 @@ namespace osu.Game.Screens.Play.HUD
} }
} }
private const float main_path_radius = 10f; public const float MAIN_PATH_RADIUS = 10f;
private const float curve_start_offset = 70;
private const float curve_end_offset = 40;
private const float padding = MAIN_PATH_RADIUS * 2;
private const float curve_smoothness = 10;
private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize);
public ArgonHealthDisplay()
{
AddLayout(drawSizeLayout);
// sane default width specification.
// this only matters if the health display isn't part of the default skin
// (in which case width will be set to 300 via `ArgonSkin.GetDrawableComponent()`),
// and if the user hasn't applied their own modifications
// (which are applied via `SerialisedDrawableInfo.ApplySerialisedInfo()`).
Width = 0.98f;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
InternalChild = new Container InternalChild = new Container
@ -107,7 +120,7 @@ namespace osu.Game.Screens.Play.HUD
{ {
background = new BackgroundPath background = new BackgroundPath
{ {
PathRadius = main_path_radius, PathRadius = MAIN_PATH_RADIUS,
}, },
glowBar = new BarPath glowBar = new BarPath
{ {
@ -127,7 +140,7 @@ namespace osu.Game.Screens.Play.HUD
Blending = BlendingParameters.Additive, Blending = BlendingParameters.Additive,
BarColour = main_bar_colour, BarColour = main_bar_colour,
GlowColour = main_bar_glow_colour, GlowColour = main_bar_glow_colour,
PathRadius = main_path_radius, PathRadius = MAIN_PATH_RADIUS,
GlowPortion = 0.6f, GlowPortion = 0.6f,
}, },
} }
@ -140,17 +153,15 @@ namespace osu.Game.Screens.Play.HUD
Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true); Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true);
BarLength.BindValueChanged(l => Width = l.NewValue, true); // we're about to set `RelativeSizeAxes` depending on the value of `UseRelativeSize`.
BarHeight.BindValueChanged(_ => updatePath()); // setting `RelativeSizeAxes` internally transforms absolute sizing to relative and back to keep the size the same,
updatePath(); // but that is not what we want in this case, since the width at this point is valid in the *target* sizing mode.
} // to counteract this, store the numerical value here, and restore it after setting the correct initial relative sizing axes.
float previousWidth = Width;
UseRelativeSize.BindValueChanged(v => RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None, true);
Width = previousWidth;
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) BarHeight.BindValueChanged(_ => updatePath(), true);
{
if ((invalidation & Invalidation.DrawSize) > 0)
updatePath();
return base.OnInvalidate(invalidation, source);
} }
private void updateCurrent() private void updateCurrent()
@ -168,6 +179,12 @@ namespace osu.Game.Screens.Play.HUD
{ {
base.Update(); base.Update();
if (!drawSizeLayout.IsValid)
{
updatePath();
drawSizeLayout.Validate();
}
mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed);
glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed);
} }
@ -236,11 +253,17 @@ namespace osu.Game.Screens.Play.HUD
private void updatePath() private void updatePath()
{ {
float barLength = DrawWidth - main_path_radius * 2; float usableWidth = DrawWidth - padding;
float curveStart = barLength - 70;
float curveEnd = barLength - 40;
const float curve_smoothness = 10; if (usableWidth < 0) enforceMinimumWidth();
// the display starts curving at `curve_start_offset` units from the right and ends curving at `curve_end_offset`.
// to ensure that the curve is symmetric when it starts being narrow enough, add a `curve_end_offset` to the left side too.
const float rescale_cutoff = curve_start_offset + curve_end_offset;
float barLength = Math.Max(DrawWidth - padding, rescale_cutoff);
float curveStart = barLength - curve_start_offset;
float curveEnd = barLength - curve_end_offset;
Vector2 diagonalDir = (new Vector2(curveEnd, BarHeight.Value) - new Vector2(curveStart, 0)).Normalized(); Vector2 diagonalDir = (new Vector2(curveEnd, BarHeight.Value) - new Vector2(curveStart, 0)).Normalized();
@ -256,6 +279,9 @@ namespace osu.Game.Screens.Play.HUD
new PathControlPoint(new Vector2(barLength, BarHeight.Value)), new PathControlPoint(new Vector2(barLength, BarHeight.Value)),
}); });
if (DrawWidth - padding < rescale_cutoff)
rescalePathProportionally();
List<Vector2> vertices = new List<Vector2>(); List<Vector2> vertices = new List<Vector2>();
barPath.GetPathToProgress(vertices, 0.0, 1.0); barPath.GetPathToProgress(vertices, 0.0, 1.0);
@ -264,6 +290,24 @@ namespace osu.Game.Screens.Play.HUD
glowBar.Vertices = vertices; glowBar.Vertices = vertices;
updatePathVertices(); updatePathVertices();
void enforceMinimumWidth()
{
// Switch to absolute in order to be able to define a minimum width.
// Then switch back is required. Framework will handle the conversion for us.
Axes relativeAxes = RelativeSizeAxes;
RelativeSizeAxes = Axes.None;
Width = padding;
RelativeSizeAxes = relativeAxes;
}
void rescalePathProportionally()
{
foreach (var point in barPath.ControlPoints)
point.Position = new Vector2(point.Position.X / barLength * (DrawWidth - padding), point.Position.Y);
}
} }
private void updatePathVertices() private void updatePathVertices()

View File

@ -0,0 +1,51 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Localisation.SkinComponents;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonScoreCounter : GameplayScoreCounter, ISerialisableDrawable
{
protected override double RollingDuration => 500;
protected override Easing RollingEasing => Easing.OutQuint;
[SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{
Precision = 0.01f,
MinValue = 0,
MaxValue = 1,
};
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))]
public Bindable<bool> ShowLabel { get; } = new BindableBool(true);
public bool UsesFixedAnchor { get; set; }
protected override LocalisableString FormatCount(long count) => count.ToLocalisableString();
protected override IHasText CreateText() => new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper())
{
RequiredDisplayDigits = { BindTarget = RequiredDisplayDigits },
WireframeOpacity = { BindTarget = WireframeOpacity },
ShowLabel = { BindTarget = ShowLabel },
};
private partial class ArgonScoreTextComponent : ArgonCounterTextComponent
{
public ArgonScoreTextComponent(Anchor anchor, LocalisableString? label = null)
: base(anchor, label)
{
}
}
}
}

View File

@ -19,6 +19,7 @@ namespace osu.Game.Screens.Play.HUD
private readonly ArgonSongProgressGraph graph; private readonly ArgonSongProgressGraph graph;
private readonly ArgonSongProgressBar bar; private readonly ArgonSongProgressBar bar;
private readonly Container graphContainer; private readonly Container graphContainer;
private readonly Container content;
private const float bar_height = 10; private const float bar_height = 10;
@ -30,43 +31,50 @@ namespace osu.Game.Screens.Play.HUD
public ArgonSongProgress() public ArgonSongProgress()
{ {
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Anchor = Anchor.BottomCentre; Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre; Origin = Anchor.BottomCentre;
Masking = true; Masking = true;
CornerRadius = 5; CornerRadius = 5;
Children = new Drawable[]
Child = content = new Container
{ {
info = new SongProgressInfo RelativeSizeAxes = Axes.X,
Children = new Drawable[]
{ {
Origin = Anchor.TopLeft, info = new SongProgressInfo
Name = "Info",
Anchor = Anchor.TopLeft,
RelativeSizeAxes = Axes.X,
ShowProgress = false
},
bar = new ArgonSongProgressBar(bar_height)
{
Name = "Seek bar",
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
OnSeek = time => player?.Seek(time),
},
graphContainer = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Masking = true,
CornerRadius = 5,
Child = graph = new ArgonSongProgressGraph
{ {
Name = "Difficulty graph", Origin = Anchor.TopLeft,
RelativeSizeAxes = Axes.Both, Name = "Info",
Blending = BlendingParameters.Additive Anchor = Anchor.TopLeft,
RelativeSizeAxes = Axes.X,
ShowProgress = false
}, },
RelativeSizeAxes = Axes.X, bar = new ArgonSongProgressBar(bar_height)
}, {
Name = "Seek bar",
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
OnSeek = time => player?.Seek(time),
},
graphContainer = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Masking = true,
CornerRadius = 5,
Child = graph = new ArgonSongProgressGraph
{
Name = "Difficulty graph",
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive
},
RelativeSizeAxes = Axes.X,
},
}
}; };
RelativeSizeAxes = Axes.X;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -100,7 +108,7 @@ namespace osu.Game.Screens.Play.HUD
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
Height = bar.Height + bar_height + info.Height; content.Height = bar.Height + bar_height + info.Height;
graphContainer.Height = bar.Height; graphContainer.Height = bar.Height;
} }

View File

@ -0,0 +1,51 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Configuration;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonWedgePiece : CompositeDrawable, ISerialisableDrawable
{
public bool UsesFixedAnchor { get; set; }
[SettingSource("Inverted shear")]
public BindableBool InvertShear { get; } = new BindableBool();
public ArgonWedgePiece()
{
CornerRadius = 10f;
Size = new Vector2(400, 100);
}
[BackgroundDependencyLoader]
private void load()
{
Masking = true;
Shear = new Vector2(0.8f, 0f);
InternalChild = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#66CCFF").Opacity(0.0f), Color4Extensions.FromHex("#66CCFF").Opacity(0.25f)),
};
}
protected override void LoadComplete()
{
base.LoadComplete();
InvertShear.BindValueChanged(v => Shear = new Vector2(0.8f, 0f) * (v.NewValue ? -1 : 1), true);
}
}
}

View File

@ -10,7 +10,6 @@ namespace osu.Game.Screens.Play.HUD
{ {
public partial class DefaultKeyCounterDisplay : KeyCounterDisplay public partial class DefaultKeyCounterDisplay : KeyCounterDisplay
{ {
private const int duration = 100;
private const double key_fade_time = 80; private const double key_fade_time = 80;
protected override FillFlowContainer<KeyCounter> KeyFlow { get; } protected override FillFlowContainer<KeyCounter> KeyFlow { get; }
@ -25,15 +24,6 @@ namespace osu.Game.Screens.Play.HUD
}; };
} }
protected override void Update()
{
base.Update();
// Don't use autosize as it will shrink to zero when KeyFlow is hidden.
// In turn this can cause the display to be masked off screen and never become visible again.
Size = KeyFlow.Size;
}
protected override KeyCounter CreateCounter(InputTrigger trigger) => new DefaultKeyCounter(trigger) protected override KeyCounter CreateCounter(InputTrigger trigger) => new DefaultKeyCounter(trigger)
{ {
FadeTime = key_fade_time, FadeTime = key_fade_time,
@ -41,10 +31,6 @@ namespace osu.Game.Screens.Play.HUD
KeyUpTextColor = KeyUpTextColor, KeyUpTextColor = KeyUpTextColor,
}; };
protected override void UpdateVisibility() =>
// Isolate changing visibility of the key counters from fading this component.
KeyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration);
private Color4 keyDownTextColor = Color4.DarkGray; private Color4 keyDownTextColor = Color4.DarkGray;
public Color4 KeyDownTextColor public Color4 KeyDownTextColor

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
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.Containers;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -27,6 +28,7 @@ namespace osu.Game.Screens.Play.HUD
private readonly DefaultSongProgressBar bar; private readonly DefaultSongProgressBar bar;
private readonly DefaultSongProgressGraph graph; private readonly DefaultSongProgressGraph graph;
private readonly SongProgressInfo info; private readonly SongProgressInfo info;
private readonly Container content;
[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))] [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))]
public Bindable<bool> ShowGraph { get; } = new BindableBool(true); public Bindable<bool> ShowGraph { get; } = new BindableBool(true);
@ -37,31 +39,36 @@ namespace osu.Game.Screens.Play.HUD
public DefaultSongProgress() public DefaultSongProgress()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Anchor = Anchor.BottomRight; Anchor = Anchor.BottomRight;
Origin = Anchor.BottomRight; Origin = Anchor.BottomRight;
Children = new Drawable[] Child = content = new Container
{ {
info = new SongProgressInfo RelativeSizeAxes = Axes.X,
Children = new Drawable[]
{ {
Origin = Anchor.BottomLeft, info = new SongProgressInfo
Anchor = Anchor.BottomLeft, {
RelativeSizeAxes = Axes.X, Origin = Anchor.BottomLeft,
}, Anchor = Anchor.BottomLeft,
graph = new DefaultSongProgressGraph RelativeSizeAxes = Axes.X,
{ },
RelativeSizeAxes = Axes.X, graph = new DefaultSongProgressGraph
Origin = Anchor.BottomLeft, {
Anchor = Anchor.BottomLeft, RelativeSizeAxes = Axes.X,
Height = graph_height, Origin = Anchor.BottomLeft,
Margin = new MarginPadding { Bottom = bottom_bar_height }, Anchor = Anchor.BottomLeft,
}, Height = graph_height,
bar = new DefaultSongProgressBar(bottom_bar_height, graph_height, handle_size) Margin = new MarginPadding { Bottom = bottom_bar_height },
{ },
Anchor = Anchor.BottomLeft, bar = new DefaultSongProgressBar(bottom_bar_height, graph_height, handle_size)
Origin = Anchor.BottomLeft, {
OnSeek = time => player?.Seek(time), Anchor = Anchor.BottomLeft,
}, Origin = Anchor.BottomLeft,
OnSeek = time => player?.Seek(time),
},
}
}; };
} }
@ -107,7 +114,7 @@ namespace osu.Game.Screens.Play.HUD
float newHeight = bottom_bar_height + graph_height + handle_size.Y + info.Height - graph.Y; float newHeight = bottom_bar_height + graph_height + handle_size.Y + info.Height - graph.Y;
if (!Precision.AlmostEquals(Height, newHeight, 5f)) if (!Precision.AlmostEquals(Height, newHeight, 5f))
Height = newHeight; content.Height = newHeight;
} }
private void updateBarVisibility() private void updateBarVisibility()

Some files were not shown because too many files have changed in this diff Show More