Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow decimal degrees angle in rotation popover #29927

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,9 @@ private void load()
{
angleInput = new SliderWithTextBoxInput<float>("Angle (degrees):")
{
Current = new BindableNumber<float>
{
MinValue = -360,
MaxValue = 360,
Precision = 1
},
SliderPrecision = 1,
SliderMinValue = -360,
SliderMaxValue = 360,
Instantaneous = true
},
rotationOrigin = new EditorRadioButtonCollection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,38 @@ public void TestInstantaneousMode()
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
}

[Test]
public void TestMultiPrecision()
{
AddStep("set slider precision to 1", () => sliderWithTextBoxInput.SliderPrecision = 1f);

AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
AddStep("change text", () => textBox.Text = "3.4");
AddAssert("slider not moved", () => slider.Current.Value, () => Is.Zero);
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.Zero);

AddStep("commit text", () => InputManager.Key(Key.Enter));
AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3));
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3.4).Within(0.01));

AddStep("move mouse to nub", () => InputManager.MoveMouseTo(nub));
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
AddStep("move mouse to minimum", () => InputManager.MoveMouseTo(sliderWithTextBoxInput.ScreenSpaceDrawQuad.BottomLeft));
AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("3.4"));
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3.4).Within(0.01));

AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));

AddStep("move mouse to nub", () => InputManager.MoveMouseTo(nub));
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
AddStep("move mouse to 55%", () => InputManager.MoveMouseTo(sliderWithTextBoxInput.ScreenSpaceDrawQuad.BottomLeft * 0.45f + sliderWithTextBoxInput.ScreenSpaceDrawQuad.TopRight * 0.55f));

AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("1"));
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(1));
}
}
}
112 changes: 98 additions & 14 deletions osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Numerics;
using System.Globalization;
using osu.Framework.Bindables;
Expand All @@ -26,10 +27,83 @@ public float KeyboardStep
set => slider.KeyboardStep = value;
}

private readonly BindableNumberWithCurrent<T> current = new BindableNumberWithCurrent<T>();

public Bindable<T> Current
{
get => slider.Current;
set => slider.Current = value;
get => current;
set
{
current.Current = value;
slider.Current = value;
}
}

private T? sliderPrecision;

public T? SliderPrecision
{
get => sliderPrecision;
set
{
if (value.HasValue)
{
T multiple = value.Value / current.Precision;
if (!T.IsNaN(multiple) && !T.IsInfinity(multiple) && !T.IsZero(multiple % T.One))
throw new ArgumentException(@"Precision override must be a multiple of the current precision.");
}

sliderPrecision = value;
slider.Current = new BindableNumber<T>
{
MinValue = sliderMinValue ?? current.MinValue,
MaxValue = sliderMaxValue ?? current.MaxValue,
Default = current.Default,
Precision = value ?? current.Precision,
};
}
}

private T? sliderMinValue;

public T? SliderMinValue
{
get => sliderMinValue;
set
{
if (value.HasValue && value.Value < current.MinValue)
throw new ArgumentException(@"Minimum value override must be greater than or equal to the current minimum value.");

sliderMinValue = value;
slider.Current = new BindableNumber<T>
{
MinValue = value ?? current.MinValue,
MaxValue = sliderMaxValue ?? current.MaxValue,
Default = current.Default,
Precision = sliderPrecision ?? current.Precision,
};
}
}

private T? sliderMaxValue;

public T? SliderMaxValue
{
get => sliderMaxValue;
set
{
if (value.HasValue && value.Value > current.MaxValue)
throw new ArgumentException(@"Maximum value override must be less than or equal to the current maximum value.");

sliderMaxValue = value;
slider.Current = new BindableNumber<T>
{
MinValue = sliderMinValue ?? current.MinValue,
MaxValue = value ?? current.MaxValue,
Default = current.Default,
Precision = sliderPrecision ?? current.Precision,
};
}
}

private bool instantaneous;
Expand Down Expand Up @@ -82,37 +156,38 @@ public SliderWithTextBoxInput(LocalisableString labelText)
textBox.OnCommit += textCommitted;
textBox.Current.BindValueChanged(textChanged);

Current.BindValueChanged(updateTextBoxFromSlider, true);
slider.Current.BindValueChanged(updateCurrentFromSlider);
Current.BindValueChanged(updateTextBoxAndSliderFromCurrent, true);
}

public bool TakeFocus() => GetContainingFocusManager()?.ChangeFocus(textBox) == true;

public bool SelectAll() => textBox.SelectAll();

private bool updatingFromTextBox;
private bool updatingFromCurrent;

private void textChanged(ValueChangedEvent<string> change)
{
if (!instantaneous) return;

tryUpdateSliderFromTextBox();
tryUpdateCurrentFromTextBox();
}

private void textCommitted(TextBox t, bool isNew)
{
tryUpdateSliderFromTextBox();
tryUpdateCurrentFromTextBox();

// If the attempted update above failed, restore text box to match the slider.
Current.TriggerChange();
}

private void tryUpdateSliderFromTextBox()
private void tryUpdateCurrentFromTextBox()
{
updatingFromTextBox = true;
if (updatingFromCurrent) return;

try
{
switch (slider.Current)
switch (Current)
{
case Bindable<int> bindableInt:
bindableInt.Value = int.Parse(textBox.Current.Value);
Expand All @@ -123,7 +198,7 @@ private void tryUpdateSliderFromTextBox()
break;

default:
slider.Current.Parse(textBox.Current.Value, CultureInfo.CurrentCulture);
Current.Parse(textBox.Current.Value, CultureInfo.CurrentCulture);
break;
}
}
Expand All @@ -132,16 +207,25 @@ private void tryUpdateSliderFromTextBox()
// ignore parsing failures.
// sane state will eventually be restored by a commit (either explicit, or implicit via focus loss).
}
}

updatingFromTextBox = false;
private void updateCurrentFromSlider(ValueChangedEvent<T> _)
{
if (updatingFromCurrent) return;

Current.Value = slider.Current.Value;
}

private void updateTextBoxFromSlider(ValueChangedEvent<T> _)
private void updateTextBoxAndSliderFromCurrent(ValueChangedEvent<T> _)
{
if (updatingFromTextBox) return;
updatingFromCurrent = true;

decimal decimalValue = decimal.CreateTruncating(slider.Current.Value);
slider.Current.Value = Current.Value;

decimal decimalValue = decimal.CreateTruncating(Current.Value);
textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}");

updatingFromCurrent = false;
}
}
}
Loading