mirror of
https://github.com/ppy/osu.git
synced 2024-12-13 03:42:57 +08:00
Merge branch 'master' into bspline-sliders
This commit is contained in:
commit
b6e3e42445
@ -21,10 +21,10 @@
|
||||
]
|
||||
},
|
||||
"ppy.localisationanalyser.tools": {
|
||||
"version": "2023.712.0",
|
||||
"version": "2023.1117.0",
|
||||
"commands": [
|
||||
"localisation"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
.github/workflows/diffcalc.yml
vendored
4
.github/workflows/diffcalc.yml
vendored
@ -185,9 +185,11 @@ jobs:
|
||||
|
||||
- name: Add comment environment
|
||||
if: ${{ github.event_name == 'issue_comment' }}
|
||||
env:
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
run: |
|
||||
# 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)
|
||||
sed -i "s;^${opt}=.*$;${line};" "${{ needs.directory.outputs.GENERATOR_ENV }}"
|
||||
done
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Android
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = MouseSettingsStrings.DisableMouseButtons,
|
||||
LabelText = MouseSettingsStrings.DisableClicksDuringGameplay,
|
||||
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons),
|
||||
},
|
||||
});
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Input.Handlers;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.Settings.Sections.Input;
|
||||
using osu.Game.Updater;
|
||||
using osu.Game.Utils;
|
||||
|
||||
@ -97,6 +98,9 @@ namespace osu.Android
|
||||
case AndroidJoystickHandler jh:
|
||||
return new AndroidJoystickSettings(jh);
|
||||
|
||||
case AndroidTouchHandler th:
|
||||
return new TouchSettings(th);
|
||||
|
||||
default:
|
||||
return base.CreateSettingsSubsectionFor(handler);
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ using osu.Game;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens.Play;
|
||||
using Squirrel;
|
||||
using Squirrel.SimpleSplat;
|
||||
using Squirrel.Sources;
|
||||
using LogLevel = Squirrel.SimpleSplat.LogLevel;
|
||||
using UpdateManager = osu.Game.Updater.UpdateManager;
|
||||
|
||||
@ -63,7 +63,7 @@ namespace osu.Desktop.Updater
|
||||
if (localUserInfo?.IsPlaying.Value == true)
|
||||
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);
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||
</ItemGroup>
|
||||
<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="System.IO.Packaging" Version="7.0.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
|
@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
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).
|
||||
: base(skin, null, storage)
|
||||
: base(skin, null, fallbackStore)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
204
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs
Normal file
204
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs
Normal 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,
|
||||
})
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,22 +15,22 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||
|
||||
[TestCase(6.710442985146793d, 206, "diffcalc-test")]
|
||||
[TestCase(1.4386882251130073d, 45, "zero-length-sliders")]
|
||||
[TestCase(0.42506480230838789d, 2, "very-fast-slider")]
|
||||
[TestCase(0.14102693012101306d, 1, "nan-slider")]
|
||||
[TestCase(6.710442985146793d, 239, "diffcalc-test")]
|
||||
[TestCase(1.4386882251130073d, 54, "zero-length-sliders")]
|
||||
[TestCase(0.42506480230838789d, 4, "very-fast-slider")]
|
||||
[TestCase(0.14102693012101306d, 2, "nan-slider")]
|
||||
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
||||
|
||||
[TestCase(8.9742952703071666d, 206, "diffcalc-test")]
|
||||
[TestCase(0.55071082800473514d, 2, "very-fast-slider")]
|
||||
[TestCase(1.743180218215227d, 45, "zero-length-sliders")]
|
||||
[TestCase(8.9742952703071666d, 239, "diffcalc-test")]
|
||||
[TestCase(1.743180218215227d, 54, "zero-length-sliders")]
|
||||
[TestCase(0.55071082800473514d, 4, "very-fast-slider")]
|
||||
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
|
||||
|
||||
[TestCase(6.710442985146793d, 239, "diffcalc-test")]
|
||||
[TestCase(0.42506480230838789d, 4, "very-fast-slider")]
|
||||
[TestCase(1.4386882251130073d, 54, "zero-length-sliders")]
|
||||
[TestCase(0.42506480230838789d, 4, "very-fast-slider")]
|
||||
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
|
||||
|
||||
|
@ -133,8 +133,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
assertKeyCounter(1, 0);
|
||||
@ -468,7 +471,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Test]
|
||||
public void TestInputWhileMouseButtonsDisabled()
|
||||
{
|
||||
AddStep("Disable mouse buttons", () => config.SetValue(OsuSetting.MouseDisableButtons, true));
|
||||
AddStep("Disable gameplay taps", () => config.SetValue(OsuSetting.TouchDisableGameplayTaps, true));
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
@ -620,6 +623,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddStep("Release all touches", () =>
|
||||
{
|
||||
config.SetValue(OsuSetting.MouseDisableButtons, false);
|
||||
config.SetValue(OsuSetting.TouchDisableGameplayTaps, false);
|
||||
foreach (TouchSource source in InputManager.CurrentState.Touch.ActiveSources)
|
||||
InputManager.EndTouch(new Touch(source, osuInputManager.ScreenSpaceDrawQuad.Centre));
|
||||
});
|
||||
|
@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
if (hit)
|
||||
assertAllMaxJudgements();
|
||||
else
|
||||
AddAssert("Tracking dropped", assertMidSliderJudgementFail);
|
||||
assertMidSliderJudgementFail();
|
||||
|
||||
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 },
|
||||
});
|
||||
|
||||
AddAssert("Tracking lost", assertMidSliderJudgementFail);
|
||||
assertMidSliderJudgementFail();
|
||||
}
|
||||
|
||||
/// <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 },
|
||||
});
|
||||
|
||||
AddAssert("Tracking retained, sliderhead miss", assertHeadMissTailTracked);
|
||||
assertHeadMissTailTracked();
|
||||
}
|
||||
|
||||
/// <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 },
|
||||
});
|
||||
|
||||
AddAssert("Tracking re-acquired", assertMidSliderJudgements);
|
||||
assertMidSliderJudgements();
|
||||
}
|
||||
|
||||
/// <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 },
|
||||
});
|
||||
|
||||
AddAssert("Tracking lost", assertMidSliderJudgementFail);
|
||||
assertMidSliderJudgementFail();
|
||||
}
|
||||
|
||||
/// <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 },
|
||||
});
|
||||
|
||||
AddAssert("Tracking acquired", assertMidSliderJudgements);
|
||||
assertMidSliderJudgements();
|
||||
}
|
||||
|
||||
/// <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 },
|
||||
});
|
||||
|
||||
AddAssert("Tracking acquired", assertMidSliderJudgements);
|
||||
assertMidSliderJudgements();
|
||||
}
|
||||
|
||||
[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 },
|
||||
});
|
||||
|
||||
AddAssert("Tracking acquired", assertMidSliderJudgements);
|
||||
assertMidSliderJudgements();
|
||||
}
|
||||
|
||||
/// <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 },
|
||||
});
|
||||
|
||||
AddAssert("Tracking acquired", assertMidSliderJudgements);
|
||||
assertMidSliderJudgements();
|
||||
}
|
||||
|
||||
/// <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 },
|
||||
});
|
||||
|
||||
AddAssert("Tracking dropped", assertMidSliderJudgementFail);
|
||||
assertMidSliderJudgementFail();
|
||||
}
|
||||
|
||||
private void assertAllMaxJudgements()
|
||||
@ -465,11 +465,21 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
}, () => 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)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@ -36,6 +37,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddSliderStep("Spin rate", 0.5, 5, 1, val => spinRate.Value = val);
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("Reset rate", () => spinRate.Value = 1);
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestVariousSpinners(bool autoplay)
|
||||
@ -46,6 +53,36 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
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]
|
||||
public void TestSpinningSamplePitchShift()
|
||||
{
|
||||
@ -153,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
base.Update();
|
||||
if (auto)
|
||||
RotationTracker.AddRotation((float)(Clock.ElapsedFrameTime * spinRate.Value));
|
||||
RotationTracker.AddRotation((float)Math.Min(180, Clock.ElapsedFrameTime * spinRate.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 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);
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
typeof(ModNoFail),
|
||||
typeof(ModAutoplay),
|
||||
typeof(OsuModMagnetised),
|
||||
typeof(OsuModRepel)
|
||||
typeof(OsuModRepel),
|
||||
typeof(ModTouchDevice)
|
||||
};
|
||||
|
||||
public bool PerformFail() => false;
|
||||
|
@ -1,18 +1,14 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModTouchDevice : Mod
|
||||
public class OsuModTouchDevice : ModTouchDevice
|
||||
{
|
||||
public override string Name => "Touch Device";
|
||||
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;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray();
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
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_modulated_base_frequency = 0.5f;
|
||||
|
||||
private SkinnableSound maxBonusSample;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of bonus score gained from spinning after the required number of spins, for display purposes.
|
||||
/// </summary>
|
||||
@ -109,6 +112,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME,
|
||||
Looping = true,
|
||||
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();
|
||||
|
||||
spinningSample.ClearSamples();
|
||||
maxBonusSample.ClearSamples();
|
||||
}
|
||||
|
||||
protected override void LoadSamples()
|
||||
@ -136,6 +144,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
spinningSample.Samples = HitObject.CreateSpinningSamples().Cast<ISampleInfo>().ToArray();
|
||||
spinningSample.Frequency.Value = spinning_sample_initial_frequency;
|
||||
|
||||
maxBonusSample.Samples = new ISampleInfo[] { new SpinnerBonusMaxSampleInfo(HitObject.CreateHitSampleInfo()) };
|
||||
}
|
||||
|
||||
private void updateSpinningSample(ValueChangedEvent<bool> tracking)
|
||||
@ -157,6 +167,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.StopAllSamples();
|
||||
spinningSample?.Stop();
|
||||
maxBonusSample?.Stop();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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++;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,6 +135,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
classicSliderBehaviour = value;
|
||||
if (HeadCircle != null)
|
||||
HeadCircle.ClassicSliderBehaviour = value;
|
||||
if (TailCircle != null)
|
||||
TailCircle.ClassicSliderBehaviour = value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,6 +220,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
StartTime = e.Time,
|
||||
Position = EndPosition,
|
||||
StackHeight = StackHeight,
|
||||
ClassicSliderBehaviour = ClassicSliderBehaviour,
|
||||
});
|
||||
break;
|
||||
|
||||
@ -273,9 +276,9 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
}
|
||||
|
||||
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()
|
||||
// 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();
|
||||
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
@ -43,5 +45,12 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
}
|
||||
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
||||
public override Judgement CreateJudgement() => new SliderEndJudgement();
|
||||
|
||||
public class SliderEndJudgement : OsuJudgement
|
||||
{
|
||||
public override HitResult MaxResult => HitResult.LargeTickHit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public class SliderRepeat : SliderEndCircle
|
||||
@ -13,12 +9,5 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
: base(slider)
|
||||
{
|
||||
}
|
||||
|
||||
public override Judgement CreateJudgement() => new SliderRepeatJudgement();
|
||||
|
||||
public class SliderRepeatJudgement : OsuJudgement
|
||||
{
|
||||
public override HitResult MaxResult => HitResult.LargeTickHit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,16 +9,28 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
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)
|
||||
: 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 class TailJudgement : SliderEndJudgement
|
||||
{
|
||||
public override HitResult MaxResult => HitResult.LargeTickHit;
|
||||
public override HitResult MinResult => HitResult.IgnoreMiss;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
private readonly OsuInputManager osuInputManager;
|
||||
|
||||
private Bindable<bool> mouseDisabled = null!;
|
||||
private Bindable<bool> tapsDisabled = null!;
|
||||
|
||||
public OsuTouchInputMapper(OsuInputManager inputManager)
|
||||
{
|
||||
@ -43,9 +43,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
// The mouse button disable setting affects touch. It's a bit weird.
|
||||
// This is mostly just doing the same as what is done in RulesetInputManager to match behaviour.
|
||||
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
|
||||
tapsDisabled = config.GetBindable<bool>(OsuSetting.TouchDisableGameplayTaps);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// 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.
|
||||
// This case gets special handling to allow for empty-space stream tapping.
|
||||
|
@ -174,8 +174,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
private class TestLegacySkin : LegacySkin
|
||||
{
|
||||
public TestLegacySkin(IResourceStore<byte[]> storage, string fileName)
|
||||
: base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, null, storage, fileName)
|
||||
public TestLegacySkin(IResourceStore<byte[]> fallbackStore, string fileName)
|
||||
: base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, null, fallbackStore, fileName)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -147,11 +147,11 @@ namespace osu.Game.Tests.Mods
|
||||
new Mod[] { new OsuModDeflate(), new OsuModApproachDifferent() },
|
||||
new[] { typeof(OsuModDeflate), typeof(OsuModApproachDifferent) }
|
||||
},
|
||||
// system mod.
|
||||
// system mod not applicable in lazer.
|
||||
new object[]
|
||||
{
|
||||
new Mod[] { new OsuModHidden(), new OsuModTouchDevice() },
|
||||
new[] { typeof(OsuModTouchDevice) }
|
||||
new Mod[] { new OsuModHidden(), new ModScoreV2() },
|
||||
new[] { typeof(ModScoreV2) }
|
||||
},
|
||||
// multi mod.
|
||||
new object[]
|
||||
|
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20231108.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20231108.osk
Normal file
Binary file not shown.
@ -11,9 +11,9 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
[TestFixture]
|
||||
public class HitResultTest
|
||||
{
|
||||
[TestCase(new[] { HitResult.Perfect, HitResult.Great, HitResult.Good, HitResult.Ok, HitResult.Meh }, new[] { HitResult.Miss })]
|
||||
[TestCase(new[] { HitResult.LargeTickHit }, new[] { HitResult.LargeTickMiss })]
|
||||
[TestCase(new[] { HitResult.SmallTickHit }, new[] { HitResult.SmallTickMiss })]
|
||||
[TestCase(new[] { HitResult.Perfect, HitResult.Great, HitResult.Good, HitResult.Ok, HitResult.Meh }, new[] { HitResult.Miss, HitResult.IgnoreMiss })]
|
||||
[TestCase(new[] { HitResult.LargeTickHit }, new[] { HitResult.LargeTickMiss, HitResult.IgnoreMiss })]
|
||||
[TestCase(new[] { HitResult.SmallTickHit }, new[] { HitResult.SmallTickMiss, HitResult.IgnoreMiss })]
|
||||
[TestCase(new[] { HitResult.LargeBonus, HitResult.SmallBonus }, new[] { HitResult.IgnoreMiss })]
|
||||
[TestCase(new[] { HitResult.IgnoreHit }, new[] { HitResult.IgnoreMiss, HitResult.ComboBreak })]
|
||||
public void TestValidResultPairs(HitResult[] maxResults, HitResult[] minResults)
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Resources;
|
||||
using SharpCompress.Archives.Zip;
|
||||
|
||||
namespace osu.Game.Tests.Skins.IO
|
||||
@ -21,6 +22,25 @@ namespace osu.Game.Tests.Skins.IO
|
||||
{
|
||||
#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]
|
||||
public Task TestSingleImportDifferentFilename() => runSkinTest(async osu =>
|
||||
{
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.IO.Archives;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Components;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Skins
|
||||
@ -57,6 +58,8 @@ namespace osu.Game.Tests.Skins
|
||||
"Archives/modified-argon-pro-20231001.osk",
|
||||
// Covers player name text component.
|
||||
"Archives/modified-argon-20231106.osk",
|
||||
// Covers "Argon" accuracy/score/combo counters, and wedges
|
||||
"Archives/modified-argon-20231108.osk",
|
||||
};
|
||||
|
||||
/// <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]
|
||||
public void TestDeserialiseModifiedClassic()
|
||||
{
|
||||
@ -132,8 +149,8 @@ namespace osu.Game.Tests.Skins
|
||||
|
||||
private class TestSkin : Skin
|
||||
{
|
||||
public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? storage = null, string configurationFilename = "skin.ini")
|
||||
: base(skin, resources, storage, configurationFilename)
|
||||
public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? fallbackStore = null, string configurationFilename = "skin.ini")
|
||||
: base(skin, resources, fallbackStore, configurationFilename)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -95,8 +95,8 @@ namespace osu.Game.Tests.Skins
|
||||
{
|
||||
public const string SAMPLE_NAME = "test-sample";
|
||||
|
||||
public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? storage = null, string configurationFilename = "skin.ini")
|
||||
: base(skin, resources, storage, configurationFilename)
|
||||
public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? fallbackStore = null, string configurationFilename = "skin.ini")
|
||||
: base(skin, resources, fallbackStore, configurationFilename)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -181,6 +181,54 @@ namespace osu.Game.Tests.Visual.Background
|
||||
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]
|
||||
public void TestBackgroundTypeSwitch()
|
||||
{
|
||||
|
@ -47,6 +47,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
CanScaleX = true,
|
||||
CanScaleY = true,
|
||||
CanScaleDiagonally = true,
|
||||
CanFlipX = true,
|
||||
CanFlipY = true,
|
||||
|
||||
|
@ -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 =>
|
||||
{
|
||||
if (healthDisplay.IsNotNull())
|
||||
healthDisplay.BarHeight.Value = val;
|
||||
});
|
||||
|
||||
AddSliderStep("Width", 0, 1f, 0.98f, val =>
|
||||
{
|
||||
if (healthDisplay.IsNotNull())
|
||||
healthDisplay.Width = val;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
@ -96,7 +97,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@ -146,8 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddStep("Add big black box", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<BigBlackBox>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
skinEditor.ChildrenOfType<SkinComponentToolbox.ToolboxComponentButton>().First(b => b.ChildrenOfType<BigBlackBox>().FirstOrDefault() != null).TriggerClick();
|
||||
});
|
||||
|
||||
AddStep("store box", () =>
|
||||
@ -243,7 +243,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
void revertAndCheckUnchanged()
|
||||
{
|
||||
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())));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
|
||||
protected override Drawable CreateArgonImplementation() => new ArgonAccuracyCounter();
|
||||
protected override Drawable CreateDefaultImplementation() => new DefaultAccuracyCounter();
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacyAccuracyCounter();
|
||||
|
||||
|
@ -17,6 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached]
|
||||
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
|
||||
protected override Drawable CreateArgonImplementation() => new ArgonComboCounter();
|
||||
protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter();
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter();
|
||||
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached(typeof(HealthProcessor))]
|
||||
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 CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) };
|
||||
|
||||
|
@ -17,6 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached(typeof(ScoreProcessor))]
|
||||
private ScoreProcessor scoreProcessor = TestGameplayState.Create(new OsuRuleset()).ScoreProcessor;
|
||||
|
||||
protected override Drawable CreateArgonImplementation() => new ArgonScoreCounter();
|
||||
protected override Drawable CreateDefaultImplementation() => new DefaultScoreCounter();
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacyScoreCounter();
|
||||
|
||||
|
@ -133,6 +133,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private bool assertAllAvailableModsSelected()
|
||||
{
|
||||
var allAvailableMods = availableMods.Value
|
||||
.Where(pair => pair.Key != ModType.System)
|
||||
.SelectMany(pair => pair.Value)
|
||||
.Where(mod => mod.UserPlayable && mod.HasImplementation)
|
||||
.ToList();
|
||||
|
@ -6,11 +6,13 @@ using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
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));
|
||||
}
|
||||
|
||||
[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 Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen;
|
||||
|
@ -12,6 +12,7 @@ using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
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);
|
||||
}
|
||||
|
||||
[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()
|
||||
{
|
||||
var player = playToCompletion();
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
new[] { "Plain", "This is plain comment" },
|
||||
new[] { "Pinned", "This is pinned comment" },
|
||||
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[]
|
||||
{
|
||||
|
@ -17,18 +17,16 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
BarGraph graph;
|
||||
|
||||
Children = new[]
|
||||
Child = graph = new BarGraph
|
||||
{
|
||||
graph = new BarGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(0.5f),
|
||||
},
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(0.5f),
|
||||
};
|
||||
|
||||
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("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i));
|
||||
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("Left to right", () => graph.Direction = BarDirection.LeftToRight);
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
86
osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs
Normal file
86
osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs
Normal 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),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,6 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
@ -298,7 +297,7 @@ This is a line after the fenced code block!
|
||||
{
|
||||
public LinkInline Link;
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer
|
||||
public override OsuMarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer
|
||||
{
|
||||
UrlAdded = link => Link = link,
|
||||
};
|
||||
|
@ -203,6 +203,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
|
||||
public IBeatmap Beatmap { get; }
|
||||
|
||||
// ReSharper disable once NotNullOrRequiredMemberIsNotInitialized
|
||||
public TestBeatmapConverter(IBeatmap beatmap)
|
||||
{
|
||||
Beatmap = beatmap;
|
||||
|
@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddingFlow()
|
||||
public void TestAddingFlow([Values] bool withSystemModActive)
|
||||
{
|
||||
ModPresetColumn modPresetColumn = null!;
|
||||
|
||||
@ -181,7 +181,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddUntilStep("items loaded", () => modPresetColumn.IsLoaded && modPresetColumn.ItemsLoaded);
|
||||
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);
|
||||
|
||||
AddStep("click add preset button", () =>
|
||||
@ -209,6 +215,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
AddUntilStep("popover closed", () => !this.ChildrenOfType<OsuPopover>().Any());
|
||||
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", () =>
|
||||
{
|
||||
|
@ -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() });
|
||||
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]
|
||||
@ -113,6 +117,10 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("set customised mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
|
||||
AddStep("activate panel", () => panel.AsNonNull().TriggerClick());
|
||||
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)
|
||||
|
@ -1,7 +1,9 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
@ -17,6 +19,11 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
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[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
@ -39,13 +46,13 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
ChildrenEnumerable = team?.Players.Select(createPlayerText).Take(5) ?? Enumerable.Empty<Drawable>()
|
||||
ChildrenEnumerable = players.Take(split).Select(createPlayerText),
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
ChildrenEnumerable = team?.Players.Select(createPlayerText).Skip(5) ?? Enumerable.Empty<Drawable>()
|
||||
ChildrenEnumerable = players.Skip(split).Select(createPlayerText),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
@ -74,7 +74,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics
|
||||
|
||||
#region Tooltip implementation
|
||||
|
||||
public virtual ITooltip GetCustomTooltip() => null;
|
||||
public virtual ITooltip GetCustomTooltip() => null!;
|
||||
public virtual object TooltipContent => null;
|
||||
|
||||
#endregion
|
||||
|
@ -108,6 +108,8 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.MouseDisableWheel, false);
|
||||
SetDefault(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay);
|
||||
|
||||
SetDefault(OsuSetting.TouchDisableGameplayTaps, false);
|
||||
|
||||
// Graphics
|
||||
SetDefault(OsuSetting.ShowFpsDisplay, false);
|
||||
|
||||
@ -330,6 +332,10 @@ namespace osu.Game.Configuration
|
||||
|
||||
ShowHealthDisplayWhenCantFail,
|
||||
FadePlayfieldWhenHealthLow,
|
||||
|
||||
/// <summary>
|
||||
/// Disables mouse buttons clicks during gameplay.
|
||||
/// </summary>
|
||||
MouseDisableButtons,
|
||||
MouseDisableWheel,
|
||||
ConfineMouseMode,
|
||||
@ -408,6 +414,7 @@ namespace osu.Game.Configuration
|
||||
EditorLimitedDistanceSnap,
|
||||
ReplaySettingsOverlay,
|
||||
AutomaticallyDownloadMissingBeatmaps,
|
||||
EditorShowSpeedChanges
|
||||
EditorShowSpeedChanges,
|
||||
TouchDisableGameplayTaps,
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,9 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
@ -24,6 +26,7 @@ namespace osu.Game.Configuration
|
||||
SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
|
||||
SetDefault(Static.LastModSelectPanelSamplePlaybackTime, (double?)null);
|
||||
SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
|
||||
SetDefault(Static.TouchInputActive, RuntimeInfo.IsMobile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -63,6 +66,12 @@ namespace osu.Game.Configuration
|
||||
/// 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.
|
||||
/// </summary>
|
||||
LastModSelectPanelSamplePlaybackTime
|
||||
LastModSelectPanelSamplePlaybackTime,
|
||||
|
||||
/// <summary>
|
||||
/// Whether the last positional input received was a touch input.
|
||||
/// Used in touchscreen detection scenarios (<see cref="TouchInputInterceptor"/>).
|
||||
/// </summary>
|
||||
TouchInputActive,
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -10,6 +12,7 @@ using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
namespace osu.Game.Graphics.Backgrounds
|
||||
@ -18,6 +21,10 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
private readonly InterpolatingFramedClock storyboardClock;
|
||||
|
||||
private AudioContainer storyboardContainer = null!;
|
||||
private DrawableStoryboard? drawableStoryboard;
|
||||
private CancellationTokenSource? loadCancellationSource = new CancellationTokenSource();
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private MusicController? musicController { get; set; }
|
||||
|
||||
@ -33,18 +40,59 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
if (!Beatmap.Storyboard.HasDrawable)
|
||||
return;
|
||||
|
||||
if (Beatmap.Storyboard.ReplacesBackground)
|
||||
Sprite.Alpha = 0;
|
||||
|
||||
LoadComponentAsync(new AudioContainer
|
||||
AddInternal(storyboardContainer = new AudioContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
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()
|
||||
|
@ -5,7 +5,6 @@ using Markdig.Extensions.Footnotes;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Overlays;
|
||||
@ -62,7 +61,7 @@ namespace osu.Game.Graphics.Containers.Markdown.Footnotes
|
||||
lastFootnote = Text = footnote;
|
||||
}
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow() => new FootnoteMarkdownTextFlowContainer();
|
||||
public override OsuMarkdownTextFlowContainer CreateTextFlow() => new FootnoteMarkdownTextFlowContainer();
|
||||
}
|
||||
|
||||
private partial class FootnoteMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Graphics.Containers.Markdown
|
||||
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);
|
||||
|
||||
|
@ -145,6 +145,13 @@ namespace osu.Game.Graphics.UserInterface
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
switch (direction)
|
||||
|
72
osu.Game/Input/TouchInputInterceptor.cs
Normal file
72
osu.Game/Input/TouchInputInterceptor.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
|
||||
/// <summary>
|
||||
/// "Volume can still be adjusted using the mouse wheel by holding "Alt""
|
||||
/// "Volume can still be adjusted using the mouse wheel by holding "Alt""
|
||||
/// </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""");
|
||||
|
||||
/// <summary>
|
||||
/// "Disable mouse buttons during gameplay"
|
||||
/// "Disable clicks during gameplay"
|
||||
/// </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>
|
||||
/// "Enable high precision mouse to adjust sensitivity"
|
||||
|
@ -12,43 +12,53 @@ namespace osu.Game.Localisation.SkinComponents
|
||||
/// <summary>
|
||||
/// "Sprite name"
|
||||
/// </summary>
|
||||
public static LocalisableString SpriteName => new TranslatableString(getKey(@"sprite_name"), "Sprite name");
|
||||
public static LocalisableString SpriteName => new TranslatableString(getKey(@"sprite_name"), @"Sprite name");
|
||||
|
||||
/// <summary>
|
||||
/// "The filename of the sprite"
|
||||
/// </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>
|
||||
/// "Font"
|
||||
/// </summary>
|
||||
public static LocalisableString Font => new TranslatableString(getKey(@"font"), "Font");
|
||||
public static LocalisableString Font => new TranslatableString(getKey(@"font"), @"Font");
|
||||
|
||||
/// <summary>
|
||||
/// "The font to use."
|
||||
/// </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>
|
||||
/// "Text"
|
||||
/// </summary>
|
||||
public static LocalisableString TextElementText => new TranslatableString(getKey(@"text_element_text"), "Text");
|
||||
public static LocalisableString TextElementText => new TranslatableString(getKey(@"text_element_text"), @"Text");
|
||||
|
||||
/// <summary>
|
||||
/// "The text to be displayed."
|
||||
/// </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>
|
||||
/// "Corner radius"
|
||||
/// </summary>
|
||||
public static LocalisableString CornerRadius => new TranslatableString(getKey(@"corner_radius"), "Corner radius");
|
||||
public static LocalisableString CornerRadius => new TranslatableString(getKey(@"corner_radius"), @"Corner radius");
|
||||
|
||||
/// <summary>
|
||||
/// "How rounded the corners should be."
|
||||
/// </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'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}";
|
||||
}
|
||||
}
|
||||
|
24
osu.Game/Localisation/TouchSettingsStrings.cs
Normal file
24
osu.Game/Localisation/TouchSettingsStrings.cs
Normal 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}";
|
||||
}
|
||||
}
|
@ -407,6 +407,8 @@ namespace osu.Game
|
||||
})
|
||||
});
|
||||
|
||||
base.Content.Add(new TouchInputInterceptor());
|
||||
|
||||
KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider);
|
||||
KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets);
|
||||
|
||||
@ -575,14 +577,14 @@ namespace osu.Game
|
||||
|
||||
case JoystickHandler jh:
|
||||
return new JoystickSettings(jh);
|
||||
|
||||
case TouchHandler th:
|
||||
return new TouchSettings(th);
|
||||
}
|
||||
}
|
||||
|
||||
switch (handler)
|
||||
{
|
||||
case TouchHandler th:
|
||||
return new TouchSettings(th);
|
||||
|
||||
case MidiHandler:
|
||||
return new InputSection.HandlerSection(handler);
|
||||
|
||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
AutoSizeAxes = Axes.Both,
|
||||
CornerRadius = 4,
|
||||
Masking = true,
|
||||
Child = avatar = new UpdateableAvatar(showGuestOnNull: false)
|
||||
Child = avatar = new UpdateableAvatar(showUserPanelOnHover: true, showGuestOnNull: false)
|
||||
{
|
||||
Size = new Vector2(height),
|
||||
},
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Comments
|
||||
|
||||
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
|
||||
{
|
||||
@ -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)
|
||||
: base(url)
|
||||
public CommentMarkdownImage(LinkInline linkInline)
|
||||
: base(linkInline)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ namespace osu.Game.Overlays.Comments
|
||||
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
avatar = new UpdateableAvatar(api.LocalUser.Value)
|
||||
avatar = new UpdateableAvatar(api.LocalUser.Value, isInteractive: false)
|
||||
{
|
||||
Size = new Vector2(50),
|
||||
CornerExponent = 2,
|
||||
|
@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Comments
|
||||
Size = new Vector2(avatar_size),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UpdateableAvatar(Comment.User)
|
||||
new UpdateableAvatar(Comment.User, showUserPanelOnHover: true)
|
||||
{
|
||||
Size = new Vector2(avatar_size),
|
||||
Masking = true,
|
||||
|
@ -115,7 +115,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
Name = nameTextBox.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)!
|
||||
}));
|
||||
|
||||
|
@ -153,7 +153,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private void useCurrentMods()
|
||||
{
|
||||
saveableMods = selectedMods.Value.ToHashSet();
|
||||
saveableMods = selectedMods.Value.Where(mod => mod.Type != ModType.System).ToHashSet();
|
||||
updateState();
|
||||
}
|
||||
|
||||
@ -168,7 +168,7 @@ namespace osu.Game.Overlays.Mods
|
||||
if (!selectedMods.Value.Any())
|
||||
return false;
|
||||
|
||||
return !saveableMods.SetEquals(selectedMods.Value);
|
||||
return !saveableMods.SetEquals(selectedMods.Value.Where(mod => mod.Type != ModType.System));
|
||||
}
|
||||
|
||||
private void save()
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
@ -56,17 +55,14 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
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,
|
||||
// which will also have the side effect of activating the preset (see `updateActiveState()`).
|
||||
selectedMods.Value = Preset.Value.Mods.ToArray();
|
||||
var selectedSystemMods = selectedMods.Value.Where(mod => mod.Type == ModType.System);
|
||||
// will also have the side effect of activating the preset (see `updateActiveState()`).
|
||||
selectedMods.Value = Preset.Value.Mods.Concat(selectedSystemMods).ToArray();
|
||||
}
|
||||
|
||||
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
|
||||
// (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>();
|
||||
selectedMods.Value = selectedMods.Value.Except(Preset.Value.Mods).ToArray();
|
||||
}
|
||||
|
||||
private void selectedModsChanged()
|
||||
@ -79,7 +75,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
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
|
||||
|
@ -41,6 +41,7 @@ namespace osu.Game.Overlays.Mods
|
||||
private void updateEnabledState()
|
||||
{
|
||||
Enabled.Value = availableMods.Value
|
||||
.Where(pair => pair.Key != ModType.System)
|
||||
.SelectMany(pair => pair.Value)
|
||||
.Any(modState => !modState.Active.Value && modState.Visible);
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = MouseSettingsStrings.DisableMouseButtons,
|
||||
LabelText = MouseSettingsStrings.DisableClicksDuringGameplay,
|
||||
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons)
|
||||
},
|
||||
};
|
||||
|
@ -3,38 +3,48 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Handlers.Touch;
|
||||
using osu.Framework.Input.Handlers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
[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,
|
||||
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" });
|
||||
|
||||
protected override LocalisableString Header => handler.Description;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -153,6 +154,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
Items = new[]
|
||||
{
|
||||
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 EditorMenuItemSpacer(),
|
||||
new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()),
|
||||
@ -406,7 +409,14 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
cp.Colour = colours.Yellow;
|
||||
});
|
||||
|
||||
changeHandler?.Dispose();
|
||||
|
||||
skins.EnsureMutableSkin();
|
||||
|
||||
var targetContainer = getTarget(selectedTarget.Value);
|
||||
|
||||
if (targetContainer != null)
|
||||
changeHandler = new SkinEditorChangeHandler(targetContainer);
|
||||
hasBegunMutating = true;
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Utils;
|
||||
@ -31,8 +32,44 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
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)
|
||||
{
|
||||
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
|
||||
scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero);
|
||||
|
||||
@ -120,7 +157,20 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
if (Precision.AlmostEquals(MathF.Abs(drawableItem.Rotation) % 180, 90))
|
||||
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;
|
||||
@ -169,8 +219,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
base.OnSelectionChanged();
|
||||
|
||||
SelectionBox.CanScaleX = true;
|
||||
SelectionBox.CanScaleY = true;
|
||||
SelectionBox.CanScaleX = allSelectedSupportManualSizing(Axes.X);
|
||||
SelectionBox.CanScaleY = allSelectedSupportManualSizing(Axes.Y);
|
||||
SelectionBox.CanScaleDiagonally = true;
|
||||
SelectionBox.CanFlipX = true;
|
||||
SelectionBox.CanFlipY = true;
|
||||
SelectionBox.CanReverse = false;
|
||||
@ -215,7 +266,15 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
yield return new OsuMenuItem("Reset scale", MenuItemType.Standard, () =>
|
||||
{
|
||||
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();
|
||||
|
@ -7,7 +7,6 @@ using Markdig.Extensions.Yaml;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Game.Graphics.Containers.Markdown;
|
||||
|
||||
namespace osu.Game.Overlays.Wiki.Markdown
|
||||
@ -53,7 +52,7 @@ namespace osu.Game.Overlays.Wiki.Markdown
|
||||
base.AddMarkdownComponent(markdownObject, container, level);
|
||||
}
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer();
|
||||
public override OsuMarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer();
|
||||
|
||||
private partial class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
|
||||
{
|
||||
|
@ -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 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)
|
||||
=> base.CreateParagraph(paragraphBlock, level).With(p => p.Margin = new MarginPadding { Bottom = 10 });
|
||||
|
@ -59,6 +59,13 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// </summary>
|
||||
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>
|
||||
/// Create a fresh <see cref="Mod"/> instance based on this mod.
|
||||
/// </summary>
|
||||
|
@ -156,6 +156,10 @@ namespace osu.Game.Rulesets.Mods
|
||||
[JsonIgnore]
|
||||
public virtual bool ValidForMultiplayerAsFreeMod => true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public virtual bool AlwaysValidForSubmission => false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this mod requires configuration to apply changes to the game.
|
||||
/// </summary>
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public sealed override bool ValidForMultiplayer => 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;
|
||||
|
||||
|
24
osu.Game/Rulesets/Mods/ModTouchDevice.cs
Normal file
24
osu.Game/Rulesets/Mods/ModTouchDevice.cs
Normal 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) };
|
||||
}
|
||||
}
|
@ -204,6 +204,8 @@ namespace osu.Game.Rulesets
|
||||
|
||||
public ModAutoplay? GetAutoplayMod() => CreateMod<ModAutoplay>();
|
||||
|
||||
public ModTouchDevice? GetTouchDeviceMod() => CreateMod<ModTouchDevice>();
|
||||
|
||||
/// <summary>
|
||||
/// Create a transformer which adds lookups specific to a ruleset to skin sources.
|
||||
/// </summary>
|
||||
|
@ -350,6 +350,9 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (maxResult.IsBonus() && minResult != HitResult.IgnoreMiss)
|
||||
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)
|
||||
throw new ArgumentOutOfRangeException(nameof(minResult), $"{HitResult.LargeTickMiss} is the only valid minimum result for a {maxResult} judgement.");
|
||||
|
||||
|
@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.UI
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
|
||||
tapsDisabled = config.GetBindable<bool>(OsuSetting.TouchDisableGameplayTaps);
|
||||
}
|
||||
|
||||
#region Action mapping (for replays)
|
||||
@ -124,6 +125,7 @@ namespace osu.Game.Rulesets.UI
|
||||
#region Setting application (disables etc.)
|
||||
|
||||
private Bindable<bool> mouseDisabled;
|
||||
private Bindable<bool> tapsDisabled;
|
||||
|
||||
protected override bool Handle(UIEvent e)
|
||||
{
|
||||
@ -147,9 +149,9 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,8 @@ namespace osu.Game.Screens
|
||||
{
|
||||
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 readonly bool animateOnEnter;
|
||||
|
@ -3,11 +3,14 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -34,6 +37,9 @@ namespace osu.Game.Screens.Backgrounds
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private GameHost gameHost { get; set; }
|
||||
|
||||
protected virtual bool AllowStoryboardBackground => true;
|
||||
|
||||
public BackgroundScreenDefault(bool animateOnEnter = true)
|
||||
@ -71,6 +77,34 @@ namespace osu.Game.Screens.Backgrounds
|
||||
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 CancellationTokenSource cancellationTokenSource;
|
||||
|
||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
private bool canScaleX;
|
||||
|
||||
/// <summary>
|
||||
/// Whether horizontal scaling support should be enabled.
|
||||
/// Whether horizontal scaling (from the left or right edge) support should be enabled.
|
||||
/// </summary>
|
||||
public bool CanScaleX
|
||||
{
|
||||
@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
private bool canScaleY;
|
||||
|
||||
/// <summary>
|
||||
/// Whether vertical scaling support should be enabled.
|
||||
/// Whether vertical scaling (from the top or bottom edge) support should be enabled.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
@ -245,7 +266,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
};
|
||||
|
||||
if (CanScaleX) addXScaleComponents();
|
||||
if (CanScaleX && CanScaleY) addFullScaleComponents();
|
||||
if (CanScaleDiagonally) addFullScaleComponents();
|
||||
if (CanScaleY) addYScaleComponents();
|
||||
if (CanFlipX) addXFlipComponents();
|
||||
if (CanFlipY) addYFlipComponents();
|
||||
|
@ -1095,6 +1095,19 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
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))
|
||||
{
|
||||
switchToNewDifficulty(rulesetInfo, false);
|
||||
|
@ -85,7 +85,8 @@ namespace osu.Game.Screens.Menu
|
||||
private readonly List<MainMenuButton> buttonsTopLevel = new List<MainMenuButton>();
|
||||
private readonly List<MainMenuButton> buttonsPlay = new List<MainMenuButton>();
|
||||
|
||||
private Sample sampleBack;
|
||||
private Sample sampleBackToLogo;
|
||||
private Sample sampleLogoSwoosh;
|
||||
|
||||
private readonly LogoTrackingContainer logoTrackingContainer;
|
||||
|
||||
@ -104,7 +105,7 @@ namespace osu.Game.Screens.Menu
|
||||
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),
|
||||
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)
|
||||
{
|
||||
VisibleState = ButtonSystemState.Play,
|
||||
@ -127,14 +128,14 @@ namespace osu.Game.Screens.Menu
|
||||
[BackgroundDependencyLoader(true)]
|
||||
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.Multi, @"button-generic-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.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-default-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
|
||||
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);
|
||||
|
||||
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.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D));
|
||||
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-default-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D));
|
||||
|
||||
if (host.CanExit)
|
||||
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);
|
||||
|
||||
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()
|
||||
@ -197,6 +199,7 @@ namespace osu.Game.Screens.Menu
|
||||
{
|
||||
if (State == ButtonSystemState.Initial)
|
||||
{
|
||||
StopSamplePlayback();
|
||||
logo?.TriggerClick();
|
||||
return true;
|
||||
}
|
||||
@ -260,10 +263,15 @@ namespace osu.Game.Screens.Menu
|
||||
{
|
||||
case ButtonSystemState.TopLevel:
|
||||
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;
|
||||
|
||||
case ButtonSystemState.Play:
|
||||
StopSamplePlayback();
|
||||
backButton.TriggerClick();
|
||||
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()
|
||||
{
|
||||
switch (state)
|
||||
@ -346,6 +361,9 @@ namespace osu.Game.Screens.Menu
|
||||
logo?.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo);
|
||||
logo?.ScaleTo(1, 800, Easing.OutExpo);
|
||||
}, buttonArea.Alpha * 150);
|
||||
|
||||
if (lastState == ButtonSystemState.TopLevel)
|
||||
sampleLogoSwoosh?.Play();
|
||||
break;
|
||||
|
||||
case ButtonSystemState.TopLevel:
|
||||
|
@ -7,6 +7,8 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -89,8 +91,10 @@ namespace osu.Game.Screens.Menu
|
||||
private SongTicker songTicker;
|
||||
private Container logoTarget;
|
||||
|
||||
private Sample reappearSampleSwoosh;
|
||||
|
||||
[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);
|
||||
loginDisplayed = statics.GetBindable<bool>(Static.LoginOverlayDisplayed);
|
||||
@ -162,6 +166,8 @@ namespace osu.Game.Screens.Menu
|
||||
Buttons.OnSettings = () => settings?.ToggleVisibility();
|
||||
Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility();
|
||||
|
||||
reappearSampleSwoosh = audio.Samples.Get(@"Menu/reappear-swoosh");
|
||||
|
||||
preloadSongSelect();
|
||||
}
|
||||
|
||||
@ -291,6 +297,10 @@ namespace osu.Game.Screens.Menu
|
||||
{
|
||||
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());
|
||||
|
||||
// we may have consumed our preloaded instance, so let's make another.
|
||||
|
@ -51,6 +51,7 @@ namespace osu.Game.Screens.Menu
|
||||
private readonly Action clickAction;
|
||||
private Sample sampleClick;
|
||||
private Sample sampleHover;
|
||||
private SampleChannel sampleChannel;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
@ -225,7 +226,8 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
private void trigger()
|
||||
{
|
||||
sampleClick?.Play();
|
||||
sampleChannel = sampleClick?.GetChannel();
|
||||
sampleChannel?.Play();
|
||||
|
||||
clickAction?.Invoke();
|
||||
|
||||
@ -237,6 +239,8 @@ namespace osu.Game.Screens.Menu
|
||||
public override bool HandleNonPositionalInput => state == ButtonState.Expanded;
|
||||
public override bool HandlePositionalInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f;
|
||||
|
||||
public void StopSamplePlayback() => sampleChannel?.Stop();
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
iconText.Alpha = Math.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1);
|
||||
|
@ -52,6 +52,8 @@ namespace osu.Game.Screens.Menu
|
||||
private readonly IntroSequence intro;
|
||||
|
||||
private Sample sampleClick;
|
||||
private SampleChannel sampleClickChannel;
|
||||
|
||||
private Sample sampleBeat;
|
||||
private Sample sampleDownbeat;
|
||||
|
||||
@ -391,7 +393,11 @@ namespace osu.Game.Screens.Menu
|
||||
flashLayer.FadeOut(1500, Easing.OutExpo);
|
||||
|
||||
if (Action?.Invoke() == true)
|
||||
sampleClick.Play();
|
||||
{
|
||||
StopSamplePlayback();
|
||||
sampleClickChannel = sampleClick.GetChannel();
|
||||
sampleClickChannel.Play();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -440,6 +446,8 @@ namespace osu.Game.Screens.Menu
|
||||
private Container currentProxyTarget;
|
||||
private Drawable proxy;
|
||||
|
||||
public void StopSamplePlayback() => sampleClickChannel?.Stop();
|
||||
|
||||
public Drawable ProxyToContainer(Container c)
|
||||
{
|
||||
if (currentProxyTarget != null)
|
||||
|
@ -115,7 +115,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex(@"27252d"),
|
||||
},
|
||||
avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both },
|
||||
avatar = new UpdateableAvatar(showUserPanelOnHover: true) { RelativeSizeAxes = Axes.Both },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -289,7 +289,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
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]
|
||||
private void load(OverlayColourProvider colours)
|
||||
|
@ -10,8 +10,6 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
public partial class ArgonKeyCounterDisplay : KeyCounterDisplay
|
||||
{
|
||||
private const int duration = 100;
|
||||
|
||||
protected override FillFlowContainer<KeyCounter> KeyFlow { get; }
|
||||
|
||||
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 void UpdateVisibility()
|
||||
=> KeyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration);
|
||||
}
|
||||
}
|
||||
|
110
osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs
Normal file
110
osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
68
osu.Game/Screens/Play/HUD/ArgonComboCounter.cs
Normal file
68
osu.Game/Screens/Play/HUD/ArgonComboCounter.cs
Normal 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 },
|
||||
};
|
||||
}
|
||||
}
|
173
osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs
Normal file
173
osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -35,13 +35,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Precision = 1
|
||||
};
|
||||
|
||||
[SettingSource("Bar length")]
|
||||
public BindableFloat BarLength { get; } = new BindableFloat(0.98f)
|
||||
{
|
||||
MinValue = 0.2f,
|
||||
MaxValue = 1,
|
||||
Precision = 0.01f,
|
||||
};
|
||||
[SettingSource("Use relative size")]
|
||||
public BindableBool UseRelativeSize { get; } = new BindableBool(true);
|
||||
|
||||
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]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChild = new Container
|
||||
@ -107,7 +120,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
background = new BackgroundPath
|
||||
{
|
||||
PathRadius = main_path_radius,
|
||||
PathRadius = MAIN_PATH_RADIUS,
|
||||
},
|
||||
glowBar = new BarPath
|
||||
{
|
||||
@ -127,7 +140,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Blending = BlendingParameters.Additive,
|
||||
BarColour = main_bar_colour,
|
||||
GlowColour = main_bar_glow_colour,
|
||||
PathRadius = main_path_radius,
|
||||
PathRadius = MAIN_PATH_RADIUS,
|
||||
GlowPortion = 0.6f,
|
||||
},
|
||||
}
|
||||
@ -140,17 +153,15 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true);
|
||||
|
||||
BarLength.BindValueChanged(l => Width = l.NewValue, true);
|
||||
BarHeight.BindValueChanged(_ => updatePath());
|
||||
updatePath();
|
||||
}
|
||||
// we're about to set `RelativeSizeAxes` depending on the value of `UseRelativeSize`.
|
||||
// setting `RelativeSizeAxes` internally transforms absolute sizing to relative and back to keep the size the same,
|
||||
// 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)
|
||||
{
|
||||
if ((invalidation & Invalidation.DrawSize) > 0)
|
||||
updatePath();
|
||||
|
||||
return base.OnInvalidate(invalidation, source);
|
||||
BarHeight.BindValueChanged(_ => updatePath(), true);
|
||||
}
|
||||
|
||||
private void updateCurrent()
|
||||
@ -168,6 +179,12 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!drawSizeLayout.IsValid)
|
||||
{
|
||||
updatePath();
|
||||
drawSizeLayout.Validate();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@ -236,11 +253,17 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
private void updatePath()
|
||||
{
|
||||
float barLength = DrawWidth - main_path_radius * 2;
|
||||
float curveStart = barLength - 70;
|
||||
float curveEnd = barLength - 40;
|
||||
float usableWidth = DrawWidth - padding;
|
||||
|
||||
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();
|
||||
|
||||
@ -256,6 +279,9 @@ namespace osu.Game.Screens.Play.HUD
|
||||
new PathControlPoint(new Vector2(barLength, BarHeight.Value)),
|
||||
});
|
||||
|
||||
if (DrawWidth - padding < rescale_cutoff)
|
||||
rescalePathProportionally();
|
||||
|
||||
List<Vector2> vertices = new List<Vector2>();
|
||||
barPath.GetPathToProgress(vertices, 0.0, 1.0);
|
||||
|
||||
@ -264,6 +290,24 @@ namespace osu.Game.Screens.Play.HUD
|
||||
glowBar.Vertices = vertices;
|
||||
|
||||
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()
|
||||
|
51
osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs
Normal file
51
osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
private readonly ArgonSongProgressGraph graph;
|
||||
private readonly ArgonSongProgressBar bar;
|
||||
private readonly Container graphContainer;
|
||||
private readonly Container content;
|
||||
|
||||
private const float bar_height = 10;
|
||||
|
||||
@ -30,43 +31,50 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
public ArgonSongProgress()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Anchor = Anchor.BottomCentre;
|
||||
Origin = Anchor.BottomCentre;
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
Children = new Drawable[]
|
||||
|
||||
Child = content = new Container
|
||||
{
|
||||
info = new SongProgressInfo
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Origin = Anchor.TopLeft,
|
||||
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
|
||||
info = new SongProgressInfo
|
||||
{
|
||||
Name = "Difficulty graph",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive
|
||||
Origin = Anchor.TopLeft,
|
||||
Name = "Info",
|
||||
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]
|
||||
@ -100,7 +108,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
Height = bar.Height + bar_height + info.Height;
|
||||
content.Height = bar.Height + bar_height + info.Height;
|
||||
graphContainer.Height = bar.Height;
|
||||
}
|
||||
|
||||
|
51
osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs
Normal file
51
osu.Game/Screens/Play/HUD/ArgonWedgePiece.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,6 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public partial class DefaultKeyCounterDisplay : KeyCounterDisplay
|
||||
{
|
||||
private const int duration = 100;
|
||||
private const double key_fade_time = 80;
|
||||
|
||||
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)
|
||||
{
|
||||
FadeTime = key_fade_time,
|
||||
@ -41,10 +31,6 @@ namespace osu.Game.Screens.Play.HUD
|
||||
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;
|
||||
|
||||
public Color4 KeyDownTextColor
|
||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
@ -27,6 +28,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
private readonly DefaultSongProgressBar bar;
|
||||
private readonly DefaultSongProgressGraph graph;
|
||||
private readonly SongProgressInfo info;
|
||||
private readonly Container content;
|
||||
|
||||
[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))]
|
||||
public Bindable<bool> ShowGraph { get; } = new BindableBool(true);
|
||||
@ -37,31 +39,36 @@ namespace osu.Game.Screens.Play.HUD
|
||||
public DefaultSongProgress()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Anchor = Anchor.BottomRight;
|
||||
Origin = Anchor.BottomRight;
|
||||
|
||||
Children = new Drawable[]
|
||||
Child = content = new Container
|
||||
{
|
||||
info = new SongProgressInfo
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
graph = new DefaultSongProgressGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Height = graph_height,
|
||||
Margin = new MarginPadding { Bottom = bottom_bar_height },
|
||||
},
|
||||
bar = new DefaultSongProgressBar(bottom_bar_height, graph_height, handle_size)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
OnSeek = time => player?.Seek(time),
|
||||
},
|
||||
info = new SongProgressInfo
|
||||
{
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
graph = new DefaultSongProgressGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Height = graph_height,
|
||||
Margin = new MarginPadding { Bottom = bottom_bar_height },
|
||||
},
|
||||
bar = new DefaultSongProgressBar(bottom_bar_height, graph_height, handle_size)
|
||||
{
|
||||
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;
|
||||
|
||||
if (!Precision.AlmostEquals(Height, newHeight, 5f))
|
||||
Height = newHeight;
|
||||
content.Height = newHeight;
|
||||
}
|
||||
|
||||
private void updateBarVisibility()
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user