mirror of
https://github.com/ppy/osu.git
synced 2025-03-05 13:13:22 +08:00
Merge branch 'skin-size-editing' into gameplay-hud-redesign/counters
This commit is contained in:
commit
b7972e3c84
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
|
||||
|
@ -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" />
|
||||
|
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());
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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[]
|
||||
|
@ -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)
|
||||
|
@ -47,6 +47,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
CanScaleX = true,
|
||||
CanScaleY = true,
|
||||
CanScaleDiagonally = true,
|
||||
CanFlipX = true,
|
||||
CanFlipY = true,
|
||||
|
||||
|
@ -47,12 +47,6 @@ 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())
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Input.Handlers;
|
||||
using osu.Framework.Localisation;
|
||||
@ -28,11 +29,14 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager osuConfig)
|
||||
{
|
||||
Add(new SettingsCheckbox
|
||||
if (!RuntimeInfo.IsMobile) // don't allow disabling the only input method (touch) on mobile.
|
||||
{
|
||||
LabelText = CommonStrings.Enabled,
|
||||
Current = handler.Enabled
|
||||
});
|
||||
Add(new SettingsCheckbox
|
||||
{
|
||||
LabelText = CommonStrings.Enabled,
|
||||
Current = handler.Enabled
|
||||
});
|
||||
}
|
||||
|
||||
Add(new SettingsCheckbox
|
||||
{
|
||||
|
@ -406,7 +406,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();
|
||||
|
@ -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;
|
||||
|
||||
|
22
osu.Game/Rulesets/Mods/ModTouchDevice.cs
Normal file
22
osu.Game/Rulesets/Mods/ModTouchDevice.cs
Normal file
@ -0,0 +1,22 @@
|
||||
// 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 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.");
|
||||
|
||||
|
@ -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();
|
||||
|
@ -35,9 +35,6 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Precision = 1
|
||||
};
|
||||
|
||||
[SettingSource("Bar length")]
|
||||
public BindableFloat BarLength { get; } = new BindableFloat(0.98f);
|
||||
|
||||
[SettingSource("Use relative size")]
|
||||
public BindableBool UseRelativeSize { get; } = new BindableBool(true);
|
||||
|
||||
@ -137,22 +134,11 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
Current.BindValueChanged(_ => Scheduler.AddOnce(updateCurrent), true);
|
||||
|
||||
// update relative axes first before reading width from bar length.
|
||||
RelativeSizeAxes = UseRelativeSize.Value ? Axes.X : Axes.None;
|
||||
Width = BarLength.Value;
|
||||
|
||||
UseRelativeSize.BindValueChanged(v =>
|
||||
{
|
||||
RelativeSizeAxes = v.NewValue ? Axes.X : Axes.None;
|
||||
float newWidth = Width;
|
||||
|
||||
BarLength.MinValue = v.NewValue ? 0.2f : 200f;
|
||||
BarLength.MaxValue = v.NewValue ? 1f : 1000f;
|
||||
BarLength.Precision = v.NewValue ? 0.01f : 1f;
|
||||
BarLength.Value = newWidth;
|
||||
}, true);
|
||||
|
||||
BarLength.ValueChanged += l => Width = l.NewValue;
|
||||
BarHeight.BindValueChanged(_ => updatePath(), true);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
@ -22,8 +23,9 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
||||
{
|
||||
new PlayerCheckbox
|
||||
{
|
||||
LabelText = MouseSettingsStrings.DisableClicksDuringGameplay,
|
||||
Current = config.GetBindable<bool>(OsuSetting.MouseDisableButtons)
|
||||
// TODO: change to touchscreen detection once https://github.com/ppy/osu/pull/25348 makes it in
|
||||
LabelText = RuntimeInfo.IsDesktop ? MouseSettingsStrings.DisableClicksDuringGameplay : TouchSettingsStrings.DisableTapsDuringGameplay,
|
||||
Current = config.GetBindable<bool>(RuntimeInfo.IsDesktop ? OsuSetting.MouseDisableButtons : OsuSetting.TouchDisableGameplayTaps)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
60
osu.Game/Screens/Play/PlayerTouchInputDetector.cs
Normal file
60
osu.Game/Screens/Play/PlayerTouchInputDetector.cs
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
public partial class PlayerTouchInputDetector : Component
|
||||
{
|
||||
[Resolved]
|
||||
private Player player { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private GameplayState gameplayState { get; set; } = null!;
|
||||
|
||||
private IBindable<bool> touchActive = new BindableBool();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(SessionStatics statics)
|
||||
{
|
||||
touchActive = statics.GetBindable<bool>(Static.TouchInputActive);
|
||||
touchActive.BindValueChanged(_ => updateState());
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (!touchActive.Value)
|
||||
return;
|
||||
|
||||
if (gameplayState.HasPassed || gameplayState.HasFailed || gameplayState.HasQuit)
|
||||
return;
|
||||
|
||||
if (gameplayState.Score.ScoreInfo.Mods.OfType<ModTouchDevice>().Any())
|
||||
return;
|
||||
|
||||
if (player.IsBreakTime.Value)
|
||||
return;
|
||||
|
||||
var touchDeviceMod = gameplayState.Ruleset.GetTouchDeviceMod();
|
||||
if (touchDeviceMod == null)
|
||||
return;
|
||||
|
||||
var candidateMods = player.Score.ScoreInfo.Mods.Append(touchDeviceMod).ToArray();
|
||||
|
||||
if (!ModUtils.CheckCompatibleSet(candidateMods, out _))
|
||||
return;
|
||||
|
||||
// `Player` (probably rightly so) assumes immutability of mods,
|
||||
// so this will not be shown immediately on the mod display in the top right.
|
||||
// if this is to change, the mod immutability should be revisited.
|
||||
player.Score.ScoreInfo.Mods = candidateMods;
|
||||
}
|
||||
}
|
||||
}
|
@ -44,6 +44,18 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
if (DrawableRuleset == null)
|
||||
{
|
||||
// base load must have failed (e.g. due to an unknown mod); bail.
|
||||
return;
|
||||
}
|
||||
|
||||
AddInternal(new PlayerTouchInputDetector());
|
||||
}
|
||||
|
||||
protected override void LoadAsyncComplete()
|
||||
{
|
||||
base.LoadAsyncComplete();
|
||||
|
@ -279,6 +279,7 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new SongSelectTouchInputDetector()
|
||||
});
|
||||
|
||||
if (ShowFooter)
|
||||
|
69
osu.Game/Screens/Select/SongSelectTouchInputDetector.cs
Normal file
69
osu.Game/Screens/Select/SongSelectTouchInputDetector.cs
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
{
|
||||
public partial class SongSelectTouchInputDetector : Component
|
||||
{
|
||||
[Resolved]
|
||||
private Bindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||
|
||||
private IBindable<bool> touchActive = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(SessionStatics statics)
|
||||
{
|
||||
touchActive = statics.GetBindable<bool>(Static.TouchInputActive);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ruleset.BindValueChanged(_ => Scheduler.AddOnce(updateState));
|
||||
mods.BindValueChanged(_ => Scheduler.AddOnce(updateState));
|
||||
mods.BindDisabledChanged(_ => Scheduler.AddOnce(updateState));
|
||||
touchActive.BindValueChanged(_ => Scheduler.AddOnce(updateState));
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (mods.Disabled)
|
||||
return;
|
||||
|
||||
var touchDeviceMod = ruleset.Value.CreateInstance().GetTouchDeviceMod();
|
||||
|
||||
if (touchDeviceMod == null)
|
||||
return;
|
||||
|
||||
bool touchDeviceModEnabled = mods.Value.Any(mod => mod is ModTouchDevice);
|
||||
|
||||
if (touchActive.Value && !touchDeviceModEnabled)
|
||||
{
|
||||
var candidateMods = mods.Value.Append(touchDeviceMod).ToArray();
|
||||
|
||||
if (!ModUtils.CheckCompatibleSet(candidateMods, out _))
|
||||
return;
|
||||
|
||||
mods.Value = candidateMods;
|
||||
}
|
||||
|
||||
if (!touchActive.Value && touchDeviceModEnabled)
|
||||
mods.Value = mods.Value.Where(mod => mod is not ModTouchDevice).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
@ -132,7 +132,7 @@ namespace osu.Game.Skinning
|
||||
health.Anchor = Anchor.TopLeft;
|
||||
health.Origin = Anchor.TopLeft;
|
||||
health.UseRelativeSize.Value = false;
|
||||
health.BarLength.Value = 300;
|
||||
health.Width = 300;
|
||||
health.BarHeight.Value = 30f;
|
||||
health.Position = new Vector2(components_x_offset, 20f);
|
||||
|
||||
|
@ -19,11 +19,16 @@ namespace osu.Game.Skinning
|
||||
public override bool HandleNonPositionalInput => false;
|
||||
public override bool HandlePositionalInput => false;
|
||||
|
||||
public LegacySongProgress()
|
||||
{
|
||||
// User shouldn't be able to adjust width/height of this as `CircularProgress` doesn't
|
||||
// handle stretched cases well.
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(33);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
@ -39,7 +44,7 @@ namespace osu.Game.Skinning
|
||||
},
|
||||
new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(33),
|
||||
Masking = true,
|
||||
BorderColour = Colour4.White,
|
||||
BorderThickness = 2,
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
@ -18,6 +19,10 @@ namespace osu.Game.Skinning
|
||||
// todo: can probably make this better via deserialisation directly using a common interface.
|
||||
component.Position = drawableInfo.Position;
|
||||
component.Rotation = drawableInfo.Rotation;
|
||||
if (drawableInfo.Width is float width && width != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.X) != true)
|
||||
component.Width = width;
|
||||
if (drawableInfo.Height is float height && height != 0 && (component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.Y) != true)
|
||||
component.Height = height;
|
||||
component.Scale = drawableInfo.Scale;
|
||||
component.Anchor = drawableInfo.Anchor;
|
||||
component.Origin = drawableInfo.Origin;
|
||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
@ -35,6 +36,10 @@ namespace osu.Game.Skinning
|
||||
|
||||
public Vector2 Scale { get; set; }
|
||||
|
||||
public float? Width { get; set; }
|
||||
|
||||
public float? Height { get; set; }
|
||||
|
||||
public Anchor Anchor { get; set; }
|
||||
|
||||
public Anchor Origin { get; set; }
|
||||
@ -62,6 +67,13 @@ namespace osu.Game.Skinning
|
||||
Position = component.Position;
|
||||
Rotation = component.Rotation;
|
||||
Scale = component.Scale;
|
||||
|
||||
if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.X) != true)
|
||||
Width = component.Width;
|
||||
|
||||
if ((component as CompositeDrawable)?.AutoSizeAxes.HasFlagFast(Axes.Y) != true)
|
||||
Height = component.Height;
|
||||
|
||||
Anchor = component.Anchor;
|
||||
Origin = component.Origin;
|
||||
|
||||
|
@ -121,7 +121,7 @@ namespace osu.Game.Utils
|
||||
if (!CheckCompatibleSet(mods, out invalidMods))
|
||||
return false;
|
||||
|
||||
return checkValid(mods, m => m.Type != ModType.System && m.HasImplementation, out invalidMods);
|
||||
return checkValid(mods, m => m.HasImplementation, out invalidMods);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -823,6 +823,7 @@ See the LICENCE file in the repository root for full licence text.
|
||||
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002ENumerics_002E_002A/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002ESecurity_002ECryptography_002ERSA/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=TagLib_002EMpeg4_002EBox/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
|
Loading…
Reference in New Issue
Block a user