diff --git a/osu.Android.props b/osu.Android.props
index 8a9bf1b9cd..05367c00f6 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
+
diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
index 642ecf00b8..8be74f1a7c 100644
--- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
+++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
@@ -11,6 +11,7 @@ using osu.Framework.Platform;
using osu.Game.Database;
using osu.Game.Input;
using osu.Game.Input.Bindings;
+using osu.Game.Rulesets;
using Realms;
namespace osu.Game.Tests.Database
@@ -42,7 +43,7 @@ namespace osu.Game.Tests.Database
KeyBindingContainer testContainer = new TestKeyBindingContainer();
- keyBindingStore.Register(testContainer);
+ keyBindingStore.Register(testContainer, Enumerable.Empty());
Assert.That(queryCount(), Is.EqualTo(3));
@@ -66,7 +67,7 @@ namespace osu.Game.Tests.Database
{
KeyBindingContainer testContainer = new TestKeyBindingContainer();
- keyBindingStore.Register(testContainer);
+ keyBindingStore.Register(testContainer, Enumerable.Empty());
using (var primaryUsage = realmContextFactory.GetForRead())
{
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
index da474a64ba..997eac709d 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
@@ -1,14 +1,15 @@
// Copyright (c) ppy Pty Ltd . 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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input.Handlers.Tablet;
-using osu.Framework.Platform;
+using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Overlays;
+using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections.Input;
using osuTK;
@@ -17,22 +18,34 @@ namespace osu.Game.Tests.Visual.Settings
[TestFixture]
public class TestSceneTabletSettings : OsuTestScene
{
- [BackgroundDependencyLoader]
- private void load(GameHost host)
- {
- var tabletHandler = new TestTabletHandler();
+ private TestTabletHandler tabletHandler;
+ private TabletSettings settings;
- AddRange(new Drawable[]
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("create settings", () =>
{
- new TabletSettings(tabletHandler)
+ tabletHandler = new TestTabletHandler();
+
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.None,
- Width = SettingsPanel.PANEL_WIDTH,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- }
+ settings = new TabletSettings(tabletHandler)
+ {
+ RelativeSizeAxes = Axes.None,
+ Width = SettingsPanel.PANEL_WIDTH,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ }
+ };
});
+ AddStep("set square size", () => tabletHandler.SetTabletSize(new Vector2(100, 100)));
+ }
+
+ [Test]
+ public void TestVariousTabletSizes()
+ {
AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100)));
AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Vector2(300, 300)));
AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Vector2(100, 300)));
@@ -40,6 +53,71 @@ namespace osu.Game.Tests.Visual.Settings
AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(Vector2.Zero));
}
+ [Test]
+ public void TestWideAspectRatioValidity()
+ {
+ AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100)));
+
+ AddStep("Reset to full area", () => settings.ChildrenOfType().First().TriggerClick());
+ ensureValid();
+
+ AddStep("rotate 10", () => tabletHandler.Rotation.Value = 10);
+ ensureInvalid();
+
+ AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f);
+ ensureInvalid();
+
+ AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f);
+ ensureInvalid();
+
+ AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f);
+ ensureValid();
+ }
+
+ [Test]
+ public void TestRotationValidity()
+ {
+ AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds);
+
+ AddStep("rotate 90", () => tabletHandler.Rotation.Value = 90);
+ ensureValid();
+
+ AddStep("rotate 180", () => tabletHandler.Rotation.Value = 180);
+
+ ensureValid();
+
+ AddStep("rotate 270", () => tabletHandler.Rotation.Value = 270);
+
+ ensureValid();
+
+ AddStep("rotate 360", () => tabletHandler.Rotation.Value = 360);
+
+ ensureValid();
+
+ AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0);
+ ensureValid();
+
+ AddStep("rotate 45", () => tabletHandler.Rotation.Value = 45);
+ ensureInvalid();
+
+ AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0);
+ ensureValid();
+ }
+
+ [Test]
+ public void TestOffsetValidity()
+ {
+ ensureValid();
+ AddStep("move right", () => tabletHandler.AreaOffset.Value = Vector2.Zero);
+ ensureInvalid();
+ AddStep("move back", () => tabletHandler.AreaOffset.Value = tabletHandler.AreaSize.Value / 2);
+ ensureValid();
+ }
+
+ private void ensureValid() => AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds);
+
+ private void ensureInvalid() => AddAssert("area invalid", () => !settings.AreaSelection.IsWithinBounds);
+
public class TestTabletHandler : ITabletHandler
{
public Bindable AreaOffset { get; } = new Bindable();
diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs
index 9089169877..03cb4031ca 100644
--- a/osu.Game/Input/RealmKeyBindingStore.cs
+++ b/osu.Game/Input/RealmKeyBindingStore.cs
@@ -46,52 +46,53 @@ namespace osu.Game.Input
}
///
- /// Register a new type of , adding default bindings from .
+ /// Register all defaults for this store.
///
/// The container to populate defaults from.
- public void Register(KeyBindingContainer container) => insertDefaults(container.DefaultKeyBindings);
-
- ///
- /// Register a ruleset, adding default bindings for each of its variants.
- ///
- /// The ruleset to populate defaults from.
- public void Register(RulesetInfo ruleset)
- {
- var instance = ruleset.CreateInstance();
-
- foreach (var variant in instance.AvailableVariants)
- insertDefaults(instance.GetDefaultKeyBindings(variant), ruleset.ID, variant);
- }
-
- private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null)
+ /// The rulesets to populate defaults from.
+ public void Register(KeyBindingContainer container, IEnumerable rulesets)
{
using (var usage = realmFactory.GetForWrite())
{
- // compare counts in database vs defaults
- foreach (var defaultsForAction in defaults.GroupBy(k => k.Action))
+ // intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed.
+ // this is much faster as a result.
+ var existingBindings = usage.Realm.All().ToList();
+
+ insertDefaults(usage, existingBindings, container.DefaultKeyBindings);
+
+ foreach (var ruleset in rulesets)
{
- int existingCount = usage.Realm.All().Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key);
-
- if (defaultsForAction.Count() <= existingCount)
- continue;
-
- foreach (var k in defaultsForAction.Skip(existingCount))
- {
- // insert any defaults which are missing.
- usage.Realm.Add(new RealmKeyBinding
- {
- KeyCombinationString = k.KeyCombination.ToString(),
- ActionInt = (int)k.Action,
- RulesetID = rulesetId,
- Variant = variant
- });
- }
+ var instance = ruleset.CreateInstance();
+ foreach (var variant in instance.AvailableVariants)
+ insertDefaults(usage, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ID, variant);
}
usage.Commit();
}
}
+ private void insertDefaults(RealmContextFactory.RealmUsage usage, List existingBindings, IEnumerable defaults, int? rulesetId = null, int? variant = null)
+ {
+ // compare counts in database vs defaults for each action type.
+ foreach (var defaultsForAction in defaults.GroupBy(k => k.Action))
+ {
+ // avoid performing redundant queries when the database is empty and needs to be re-filled.
+ int existingCount = existingBindings.Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key);
+
+ if (defaultsForAction.Count() <= existingCount)
+ continue;
+
+ // insert any defaults which are missing.
+ usage.Realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding
+ {
+ KeyCombinationString = k.KeyCombination.ToString(),
+ ActionInt = (int)k.Action,
+ RulesetID = rulesetId,
+ Variant = variant
+ }));
+ }
+ }
+
///
/// Keys which should not be allowed for gameplay input purposes.
///
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 762216e93c..c0d220007f 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -205,31 +205,7 @@ namespace osu.Game
dependencies.CacheAs(this);
dependencies.CacheAs(LocalConfig);
- AddFont(Resources, @"Fonts/osuFont");
-
- AddFont(Resources, @"Fonts/Torus/Torus-Regular");
- AddFont(Resources, @"Fonts/Torus/Torus-Light");
- AddFont(Resources, @"Fonts/Torus/Torus-SemiBold");
- AddFont(Resources, @"Fonts/Torus/Torus-Bold");
-
- AddFont(Resources, @"Fonts/Inter/Inter-Regular");
- AddFont(Resources, @"Fonts/Inter/Inter-RegularItalic");
- AddFont(Resources, @"Fonts/Inter/Inter-Light");
- AddFont(Resources, @"Fonts/Inter/Inter-LightItalic");
- AddFont(Resources, @"Fonts/Inter/Inter-SemiBold");
- AddFont(Resources, @"Fonts/Inter/Inter-SemiBoldItalic");
- AddFont(Resources, @"Fonts/Inter/Inter-Bold");
- AddFont(Resources, @"Fonts/Inter/Inter-BoldItalic");
-
- AddFont(Resources, @"Fonts/Noto/Noto-Basic");
- AddFont(Resources, @"Fonts/Noto/Noto-Hangul");
- AddFont(Resources, @"Fonts/Noto/Noto-CJK-Basic");
- AddFont(Resources, @"Fonts/Noto/Noto-CJK-Compatibility");
- AddFont(Resources, @"Fonts/Noto/Noto-Thai");
-
- AddFont(Resources, @"Fonts/Venera/Venera-Light");
- AddFont(Resources, @"Fonts/Venera/Venera-Bold");
- AddFont(Resources, @"Fonts/Venera/Venera-Black");
+ InitialiseFonts();
Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY;
@@ -351,10 +327,7 @@ namespace osu.Game
base.Content.Add(CreateScalingContainer().WithChildren(mainContent));
KeyBindingStore = new RealmKeyBindingStore(realmFactory);
- KeyBindingStore.Register(globalBindings);
-
- foreach (var r in RulesetStore.AvailableRulesets)
- KeyBindingStore.Register(r);
+ KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets);
dependencies.Cache(globalBindings);
@@ -368,6 +341,35 @@ namespace osu.Game
Ruleset.BindValueChanged(onRulesetChanged);
}
+ protected virtual void InitialiseFonts()
+ {
+ AddFont(Resources, @"Fonts/osuFont");
+
+ AddFont(Resources, @"Fonts/Torus/Torus-Regular");
+ AddFont(Resources, @"Fonts/Torus/Torus-Light");
+ AddFont(Resources, @"Fonts/Torus/Torus-SemiBold");
+ AddFont(Resources, @"Fonts/Torus/Torus-Bold");
+
+ AddFont(Resources, @"Fonts/Inter/Inter-Regular");
+ AddFont(Resources, @"Fonts/Inter/Inter-RegularItalic");
+ AddFont(Resources, @"Fonts/Inter/Inter-Light");
+ AddFont(Resources, @"Fonts/Inter/Inter-LightItalic");
+ AddFont(Resources, @"Fonts/Inter/Inter-SemiBold");
+ AddFont(Resources, @"Fonts/Inter/Inter-SemiBoldItalic");
+ AddFont(Resources, @"Fonts/Inter/Inter-Bold");
+ AddFont(Resources, @"Fonts/Inter/Inter-BoldItalic");
+
+ AddFont(Resources, @"Fonts/Noto/Noto-Basic");
+ AddFont(Resources, @"Fonts/Noto/Noto-Hangul");
+ AddFont(Resources, @"Fonts/Noto/Noto-CJK-Basic");
+ AddFont(Resources, @"Fonts/Noto/Noto-CJK-Compatibility");
+ AddFont(Resources, @"Fonts/Noto/Noto-Thai");
+
+ AddFont(Resources, @"Fonts/Venera/Venera-Light");
+ AddFont(Resources, @"Fonts/Venera/Venera-Bold");
+ AddFont(Resources, @"Fonts/Venera/Venera-Black");
+ }
+
private IDisposable blocking;
private void updateThreadStateChanged(ValueChangedEvent state)
diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs
index 412889d210..58abfab29c 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs
@@ -4,10 +4,13 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.MatrixExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Handlers.Tablet;
+using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
@@ -17,6 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
public class TabletAreaSelection : CompositeDrawable
{
+ public bool IsWithinBounds { get; private set; }
+
private readonly ITabletHandler handler;
private Container tabletContainer;
@@ -109,29 +114,30 @@ namespace osu.Game.Overlays.Settings.Sections.Input
areaOffset.BindTo(handler.AreaOffset);
areaOffset.BindValueChanged(val =>
{
- usableAreaContainer.MoveTo(val.NewValue, 100, Easing.OutQuint)
- .OnComplete(_ => checkBounds()); // required as we are using SSDQ.
+ usableAreaContainer.MoveTo(val.NewValue, 100, Easing.OutQuint);
+ checkBounds();
}, true);
areaSize.BindTo(handler.AreaSize);
areaSize.BindValueChanged(val =>
{
- usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint)
- .OnComplete(_ => checkBounds()); // required as we are using SSDQ.
+ usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint);
int x = (int)val.NewValue.X;
int y = (int)val.NewValue.Y;
int commonDivider = greatestCommonDivider(x, y);
usableAreaText.Text = $"{(float)x / commonDivider}:{(float)y / commonDivider}";
+ checkBounds();
}, true);
rotation.BindTo(handler.Rotation);
rotation.BindValueChanged(val =>
{
+ usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint);
tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint);
- usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint)
- .OnComplete(_ => checkBounds()); // required as we are using SSDQ.
+
+ checkBounds();
}, true);
tablet.BindTo(handler.Tablet);
@@ -169,12 +175,35 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (tablet.Value == null)
return;
- var usableSsdq = usableAreaContainer.ScreenSpaceDrawQuad;
+ // allow for some degree of floating point error, as we don't care about being perfect here.
+ const float lenience = 0.5f;
- bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft + new Vector2(1)) &&
- tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight - new Vector2(1));
+ var tabletArea = new Quad(-lenience, -lenience, tablet.Value.Size.X + lenience * 2, tablet.Value.Size.Y + lenience * 2);
- usableFill.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100);
+ var halfUsableArea = areaSize.Value / 2;
+ var offset = areaOffset.Value;
+
+ var usableAreaQuad = new Quad(
+ new Vector2(-halfUsableArea.X, -halfUsableArea.Y),
+ new Vector2(halfUsableArea.X, -halfUsableArea.Y),
+ new Vector2(-halfUsableArea.X, halfUsableArea.Y),
+ new Vector2(halfUsableArea.X, halfUsableArea.Y)
+ );
+
+ var matrix = Matrix3.Identity;
+
+ MatrixExtensions.TranslateFromLeft(ref matrix, offset);
+ MatrixExtensions.RotateFromLeft(ref matrix, MathUtils.DegreesToRadians(rotation.Value));
+
+ usableAreaQuad *= matrix;
+
+ IsWithinBounds =
+ tabletArea.Contains(usableAreaQuad.TopLeft) &&
+ tabletArea.Contains(usableAreaQuad.TopRight) &&
+ tabletArea.Contains(usableAreaQuad.BottomLeft) &&
+ tabletArea.Contains(usableAreaQuad.BottomRight);
+
+ usableFill.FadeColour(IsWithinBounds ? colour.Blue : colour.RedLight, 100);
}
protected override void Update()
diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
index b8b86d9069..8c60e81fb5 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
@@ -20,6 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
public class TabletSettings : SettingsSubsection
{
+ public TabletAreaSelection AreaSelection { get; private set; }
+
private readonly ITabletHandler tabletHandler;
private readonly Bindable enabled = new BindableBool(true);
@@ -121,7 +123,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
- new TabletAreaSelection(tabletHandler)
+ AreaSelection = new TabletAreaSelection(tabletHandler)
{
RelativeSizeAxes = Axes.X,
Height = 300,
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index ef9181c8a6..03434961ea 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -367,6 +367,11 @@ namespace osu.Game.Tests.Visual
Add(runner = new TestSceneTestRunner.TestRunner());
}
+ protected override void InitialiseFonts()
+ {
+ // skip fonts load as it's not required for testing purposes.
+ }
+
public void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test);
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index ebe3de6ea4..ae423bac8c 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -37,7 +37,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 1714bae53c..be737392e1 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -71,7 +71,7 @@
-
+