Skip to content

Conversation

@Warchamp7
Copy link
Member

@Warchamp7 Warchamp7 commented Jul 4, 2025

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
obs64_e36dRmUhOq.mp4

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:

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

@WizardCM WizardCM added Enhancement Improvement to existing functionality UI/UX Anything to do with changes or additions to UI/UX elements. labels Jul 5, 2025
@Warchamp7 Warchamp7 force-pushed the input-typing-changes branch 3 times, most recently from 3bace4a to 478aeca Compare July 7, 2025 18:41
@Warchamp7 Warchamp7 added this to the OBS Studio 32.0 milestone Jul 14, 2025
@Warchamp7 Warchamp7 marked this pull request as ready for review August 9, 2025 16:09
@Warchamp7
Copy link
Member Author

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.

Copy link
Member

@PatTheMav PatTheMav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

@PatTheMav PatTheMav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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.

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).

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.

@Warchamp7
Copy link
Member Author

Warchamp7 commented Sep 27, 2025

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 Warchamp7 force-pushed the input-typing-changes branch from c724019 to e86a35b Compare November 1, 2025 04:34
@PatTheMav
Copy link
Member

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Improvement to existing functionality UI/UX Anything to do with changes or additions to UI/UX elements.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants