obs-studio icon indicating copy to clipboard operation
obs-studio copied to clipboard

frontend: Improve spin box UX

Open Warchamp7 opened this issue 5 months ago • 3 comments

Description

This PR introduces a number of changes to spinboxes and entering values into them to improve useability.

[!NOTE] These fixes are currently only applied to DoubleSpinBoxes. I still need to do the work for normal SpinBoxes as well. Either through moving most of this work onto a QAbstractSpinBox and inheriting from that, or by creating a SpinBox subclass. Still TBD which one makes more sense but I'd like this tested.

Motivation and Context

Currently typing in spin boxes is very headache inducing due to how Qt handles the restrictions on them. Typing after the decimal is blocked. Deleting a decimal digit automatically appends a 0 to the end of the string, thus still blocking a new value. Typing at the beginning is blocked, and much more. Effectively as soon as Qt is able to parse the text as a valid value, it will auto fill the box and screw up any further input.

Changes:

  • Numbers can be typed at the start of a DoubleSpinBox
  • Numbers can be typed at any decimal position
  • DoubleSpinBox value only updates when Enter is pressed or the DoubleSpinBox loses focus
    • This means Enter also no longer closes some dialogs when pressed with a DoubleSpinBox focused
  • The cursor is always automatically moved to after/before a prefix/suffix if one exists
    • This means hitting Home/End also moves the cursor to before/after the actual typeable text
    • Before this change the cursor would move to after a suffix and hitting backspace would not work
  • Since the SpinBox now allows typing in more situations, the max character length is restricted to 1 character more than the longest possible number based on minimum/maximum and decimal count
  • Excess trailing zeroes are trimmed when the box gets focus

https://github.com/user-attachments/assets/414b49fe-118b-40d4-b545-777ae538e203

How Has This Been Tested?

Updated the spinboxes in the properties-views and transform dialog to use these new spinboxes. I have thoroughly tested entering values and deleting from various cursor positions in the Filters window as well as with spinboxes containing suffixes in the transform dialog.

Types of changes

  • Tweak (non-breaking change to improve existing functionality)

Checklist:

  • [x] My code has been run through clang-format.
  • [x] I have read the contributing document.
  • [x] My code is not on the master branch.
  • [x] The code has been tested.
  • [x] All commit messages are properly formatted and commits squashed where appropriate.
  • [x] I have included updates to all appropriate documentation.

Warchamp7 avatar Jul 04 '25 19:07 Warchamp7

Moving this out of draft because DoubleSpinboxes are impacted by this weirdness far far more than normal spinboxes and I'd rather not hold up these improvements.

Warchamp7 avatar Aug 09 '25 16:08 Warchamp7

The only thing I found unfortunate from a UX point of view was the truncation of digits when an input field is focused.

I'd find it more natural if a value like 540.0 stays 540.0 and is not converted into 540.0000 only to be magically truncated to 540.0 when selecting, because if I want to increase it to 540.0009 I have to add those zeros back in myself.

And I'd be thinking to myself "those decimal places were just there, why are they gone just because I focused that field?" and be annoyed.

Alright I did some more cleanup and managed to accomplish this despite originally thinking it was going to be impossible or annoyingly difficult (Spoiler: It was the second one).

The spinbox will remember the number of decimals that have been input. If the widget has it's value updated programmatically, the displayed decimal precision will reset to what the widget was configured for.

If the user pressed Up/Down or PageUp/PageDown to perform a step change to the value, it will adjust the displayed decimal precision to the smaller of: the current value precision or the step values precision.

Ex.

  • Text is currently 20.0 and the user presses Up

    • If SingleStep() is set to 1
      • Text updates to 21.0
    • If SingleStep is set to 1.23
      • Text updates to 21.23
  • Text is currently 20.005 and the user presses Up

    • If SingleStep() is set to 1
      • Text updates to 21.005
    • If SingleStep is set to 1.23
      • Text updates to 21.235
  • Text is currently 20 and the user presses Up

    • If SingleStep() is set to 0.001
      • Text updates to 20.001

I also made a number of adjustments to properly accommodate different locales that have a different decimal separator.


I wonder if it wouldn't be simpler to take the current value of the box, remove the expected unit suffix, and then opportunistically attempt to convert the value into the required number format.

This is how Qt worked already, but unfortunately that process meant converting the text to a double and then putting that text into the box. This meant that after typing 2 it would convert to 2.000000, format to the specified precision decimals 2.00, and stick it into the box. That is the headache of the existing behaviour.

This PR effectively intercepts that behaviour and avoids actually updating the value while the input is focused.

If it succeeds, then the value was valid (-540.0, 10, 15.0001), if it fails (as an example --540.0 will throw an exception if used with stod) the string value was invalid and the original value is restored.

This is how the PR works right now.

In some cases - and this depends on the conversion technique - the converter might try to be as successful as it could be and ignores superfluous characters, so 540--0 becomes 540.0 and likewise 540..0 becomes 540.0 (both tested with libc++'s stod).

Qt more or less handles this already.

After that step you got the most "valid" numerical representation of the string value, which you can then clamp to the valid range and round to the maximum amount of decimals, and use a single output formatting step to pad the value with decimals as needed and that formatter could assume that every number it gets is "valid".

Because as I understand Qt's process here, the validation allows you to intercept the value and update the box with the "correct" one before the next actual paint event.

The core issue is that updating the value of a SpinBox implicitly updates the text as well, even while focused and likely still receiving input. Much of this PR is built around breaking that flow in this subclass.

Warchamp7 avatar Sep 27 '25 04:09 Warchamp7

I've been reviewing this today again and found a few bugs:

  • I can select the entire text back to the beginning when the cursor is at the end of the text, but not vice versa (select from beginning all the way to the end)
  • Sometimes the state of the spinbox gets entirely messed up because it doesn't show a selection, but somehow I cannot change values anymore
  • In general typing/overwriting with text selected behaves in quirky ways
  • When entering a number over the maximum or minimum of the spinbox, the value is clamped to the maximum/minimum instead of being rejected, though that might be less of a bug and more of an unfortunate design decision?

Also stupid question time: If the spinbox is configured to 2 decimal precision, why is it converted to 2.0000 first (as it would naturally be clamped/rounded to 2.00 per the spinbox configuration)?

I did some test runs in other apps, and the common behaviour was that one can type as much wants at the beginning or end of the string (the input field doesn't interfere with the input itself).

It is only when the value is submitted (either by pressing Enter or changing input focus) that the value is validated and either rejected (invalid characters, invalid value) or truncated/rounded to the configured value.

Most transform boxes in other apps seem to only accept a single decimal place so entering "1920.06" converted back to "1920" and "1920.65" was converted to "1920.6". So the decimal precision of the input element was the ultimate arbiter of the actual value and presented value.

How much does that align with your goals?

PatTheMav avatar Nov 03 '25 17:11 PatTheMav