Skip to content

Comments

QtCollider: AbstractStepValue widgets: improve wheelEvent scroll behavior + fix regression#7030

Merged
elgiano merged 12 commits intosupercollider:3.14from
elgiano:topic/wayland-scroll
Jul 13, 2025
Merged

QtCollider: AbstractStepValue widgets: improve wheelEvent scroll behavior + fix regression#7030
elgiano merged 12 commits intosupercollider:3.14from
elgiano:topic/wayland-scroll

Conversation

@elgiano
Copy link
Contributor

@elgiano elgiano commented Jul 3, 2025

Purpose and Motivation

Fixes #7008

When migrating from deprecated .delta() to .angleDelta()/.pixelDelta() (9d10e19)
there were some errors in scaling the delta values, and some probably typos turning vertical scaling to horizontal.

angleDelta() is a direct translation of the old deprecated delta(), so I think we could skip checking for pixelDelta() in widgets like QcSlider and just use angleDelta(), to get the old behavior back. I'm not sure if using pixelDelta gives any better precision/smoothness in this case. However, for now I've kept also pixelDelta, and changed it's scaling to a value that gives me the same feel as angleDelta (and as in 3.13).

Can someone on a mac try it and report how does scrolling feel between this PR, 3.13 and 3.14-rc1? For me on linux (X11/Wayland) it's now the same across all versions.

While at it, I also added support for wheelEvent in other widgets. Full list of affected widgets:

  • QcSlider: adjusted
  • QcNumberBox: adjusted
  • ScIDE AudioStatusBox (accepts scrolling for adjusting server volume): adjusted
  • QcSlider2D: added
  • QcKnob: added

Note: this is not about Qt6, as .angleDelta and .pixelDelta were introduced in Qt5, and .delta was deprecated already then

Types of changes

  • Bug fix

To-do list

  • Code is tested
  • All tests are passing
  • Updated documentation
  • This PR is ready for review

@elgiano elgiano changed the base branch from develop to 3.14 July 3, 2025 13:20
@elgiano elgiano added qt6 issues specific to version 6 of Qt and removed qt6 issues specific to version 6 of Qt labels Jul 3, 2025
@elgiano elgiano changed the title QtCollider: QWheelEvent fix regression for scroll delta scaling for Qt6 QtCollider: QWheelEvent fix regression for scroll delta scaling Jul 3, 2025
@dyfer
Copy link
Member

dyfer commented Jul 3, 2025

Here's are my tests on M1 macbook pro with internal touchpad:
In comparison to SC 3.13, this PR makes the scroll more sensitive.
Note that current develop has the scroll less sensitive than 3.13.
I think that with inertia (having the scroll continue after a quicker "flick" of the touchpad) this PR might be too sensitive? However, scrolling without inertia seems good to me.

Scroll without inertia (sliding across full vertical space of the touchpad):

scroll.-.no.inertia.mov

Scroll with inertia (trying to use similar speed on the touchpad on each of the sliders, but that's of course subjective)

scroll.-.with.inertia.mov

@dyfer
Copy link
Member

dyfer commented Jul 3, 2025

BTW I looked at Qt documentation for this, as I thought there should be a more direct translation from a distance on the touchpad to what's on the screen to get a natural feel. Qt docs suggest to use numPixels when available, and numDegrees otherwise:

void MyWidget::wheelEvent(QWheelEvent *event)
{
    QPoint numPixels = event->pixelDelta();
    QPoint numDegrees = event->angleDelta() / 8;

    if (!numPixels.isNull()) {
        scrollWithPixels(numPixels);
    } else if (!numDegrees.isNull()) {
        QPoint numSteps = numDegrees / 15;
        scrollWithDegrees(numSteps);
    }

    event->accept();
}

@elgiano
Copy link
Contributor Author

elgiano commented Jul 3, 2025 via email

@dyfer
Copy link
Member

dyfer commented Jul 3, 2025

After looking again at our code and this PR, I guess we are using pixelData only when available:

const double scrollSteps = e->pixelDelta().isNull() ? e->angleDelta().y() / 120.0 : e->pixelDelta().y() / 15.0;

However, for something like a slider, we should probably use pixelDelta and use it to change the value, but taking the slider dimensions into account, so that it feels natural...

EDIT On one hand I agree that this could be for a separate PR. However, using pixelData is preferable as it provides a smoother experience... I'm not sure what's better in the end.

@dyfer
Copy link
Member

dyfer commented Jul 3, 2025

More thoughts on this:

For something like a slider, it makes sense to use pixelData directly if we can move the slider by the actual num of pixels (we'd need to calculate that).
As for accurate scaling for non-slider objects, I think we'd need to poll screen pixel density for this - that'd give us a relation between the size of physical gesture and the amount of change for a number, possibly consistent across platforms/configurations. So in that case maybe it's better to stick to angleData at the cost of smoothness?

@elgiano
Copy link
Contributor Author

elgiano commented Jul 3, 2025

Sounds reasonable, but I'm not sure I would want to go that path, because QSlider has steps. It seems consequential to me to only use numSteps and thus angleDelta.

Also note that we only support vertical scrolling so far, even if the slider is horizontal. It seems like scrolling is intended as a way to step the value up and down (and you can also shift- or ctrl-scroll to use different step sizes).

If we wanted to go the pixelDelta route, we would have to:

  • get the numPixels
  • find out the proportion p = numPixels/sliderHeight (or width)
  • apply the same proportion of steps (that is p * stepSize/range) [not verified :D]

and this would make scrolling react differently to widgets of different sizes, which makes sense to me intuitively, but not so much if we think about it in terms of an interface for steps

@elgiano
Copy link
Contributor Author

elgiano commented Jul 3, 2025

Also note that there is already another interface for moving a slider an exact number of pixels: dragging it.
That said, I also think it would be nice if scrolling would become more intuitive visually, I'm just not sure if we should go for it now or later :D

@dyfer
Copy link
Member

dyfer commented Jul 3, 2025

I'm just not sure if we should go for it now or later :D

Let's do that later maybe. There are a few more things to consider, like handling inverted scrolling (in my tests I had to swipe down to make the slider go up, as I use the "natural scrolling" direction on my system).

Sounds reasonable, but I'm not sure I would want to go that path, because QSlider has steps. It seems consequential to me to only use numSteps and thus angleDelta.

I'm not sure if the steps are really in the way here. I believe that visual slider should move according to physical movement? As we have pixelStep, some of that calculation is already available.

Also, for non-slider objects, I think it makes sense to stick to angle data, I think this should also be smooth if the hardware/OS supports it.

@dyfer
Copy link
Member

dyfer commented Jul 3, 2025

Another point of information: I was trying to see how scroll to change value is implemented in other macOS audio apps... and it seems that "scroll to change value" is typically not implemented. I'm not saying we should do it one way or the other, but just to raise awareness of what seems to be the "typical" macOS look&feel.

@elgiano
Copy link
Contributor Author

elgiano commented Jul 3, 2025

Thanks, I haven't noticed pixelStep(), I'm pushing a new version with:

double step = qMax(_step, pixelStep());
modifyStep(&step);
const double scrollSteps = e->pixelDelta().isNull() ? e->angleDelta().y() / 120.0 : e->pixelDelta().y();
double dval = scrollSteps * step;
setValue(_value + dval);

That is, dval is pixelDelta() * pixelStep(). It works well on wayland, and I suppose also on mac but:

  • it becomes definitely too fast (unusable) when using default ctrlScale and shiftScale
  • on X11, at least on my system, it's too fast. Maybe I have to take into account pixelDensity, but I also see this note from Qt:

Note: On X11 this value is driver-specific and unreliable, use angleDelta() instead.

What do you think?

@dyfer
Copy link
Member

dyfer commented Jul 3, 2025

Note: On X11 this value is driver-specific and unreliable, use angleDelta() instead.

Ugh, in that case maybe an ifdef for X11? Or is there a Qt flag that tells us if we're in X11 at runtime?

@elgiano
Copy link
Contributor Author

elgiano commented Jul 3, 2025

It should be possible to check QGuiApplication::platformName == "xcb" to detect x11.
On the other hand, I don't think I would like to make ctrl_scale and shift_scale unusable when scrolling... have you tried pressing ctrl or shift while scrolling?

@dyfer
Copy link
Member

dyfer commented Jul 3, 2025

I tried the build 50ff352

I see two issues:

  • The scroll is a bit too sensitive now. I assume that it should take a similar space on trackpad to drag the slider (click + drag) and two finger scroll, right? The two finger scroll is about 2-3x faster/larger that dragging the handle...
  • mouse wheel scroll is waaay to fast. I think the issue is that step = qMax(_step, pixelStep()); should only be used if pixelDelta is used, but I assume that when mouse is used, then angleDelta is used. That issue was not introduced in this PR, but I think it should be addressed, what do you think?

@dyfer
Copy link
Member

dyfer commented Jul 3, 2025

Well, looking at the code agin again, I don't know if using pixelStep is wrong.

I guess the question is whether we set the scroll wheel sensitivity based on gui size, or not. Intuitively I'd think it makes sense to set it based on the number of revolutions of the scroll wheel, i.e. one full revolution = full range (or whatever?) of the slider, but maybe that's not a good look and feel? In that case we shouldn't use pixelStep from scroll wheel.
For the touchpad scroll I'd think it should take the same about of space on the touchpad to drag the slider vs two-finger scroll, but that assumption may be wrong.

EDIT: part of the issue is that the range of scrolling on the touchpad is different base on speed. For slow movements, I get a "movement too big" situation. For faster movements, it's closer to what I'd expect, which is that the scale on the touchpad reflects the size on screen.

For the mouse wheel, the jump size increases with speed. At the slow speed it seems fine, but at medium speed (what I'd consider a "regular" scroll speed) it moves too much.

@elgiano
Copy link
Contributor Author

elgiano commented Jul 3, 2025

Well, looking at the code agin

yeah, this code is agin' :D

I don't have a mouse wheel around to try, so I don't know if pixelDelta is available or not in that case. But theoretically: if angleDelta * 8 is the number of degrees, and we are calculating 15 degrees per step, we have 24 steps for a full rotation. Default step is 0.1, so it should take around a half rotation (150deg) to cover the full range. We can make that larger, for instance 36 deg/step, so that a full rotation maps to the full range.
Default step is pixelStep(), so I have to recalculate it :)

deltaDeg / 360 = deltaSteps / totalSteps
totalSteps = 1 / step

deltaSteps = deltaDeg / 360 / step

I tried another way to calculate the increment from pixelDelta:

deltaPixels / totalPixels = deltaSteps / totalSteps
totalSteps = 1 / step

deltaSteps = deltaPixels / totalPixels / step

Then I modify step (so that alt_scale and ctrl_scale still work), and get the increment as deltaSteps * modifiedStep.

This for me gives a scroll speed comparable with dragging, on wayland. It is faster than it was in 3.13, but feels more intuitive. On X11 on the other hand, is way too fast.

I'll sleep on it. We can also just revert everything to angleDelta so to reproduce the 3.13 behavior, and take this later, if it takes too long.

@elgiano elgiano added comp: Qt GUI sclang Qt components -- for IDE tickets, use "env: SCIDE" instead Work In Progress (WIP) - don't merge yet labels Jul 4, 2025
@dyfer
Copy link
Member

dyfer commented Jul 4, 2025

Touchpad scroll feels pretty good now!

On X11 on the other hand, is way too fast.

It seems too fast with the mousewheel on macOS as well.
Your code assumes one revolution of the mosewheel for the full range, correct? I think that may be too sensitive, but I couldn't find a definite document. Maybe 3-5 revolutions per full range would be a better fit?

I posted some print statements and curiously mousewheel events also come through as pixelData on my macOS.
Single mousewheel step has a value of 2 when scrolling slowly, but that value increases when scrolling fast - to 160-180. Maybe the value of pixelData should be clamped to some arbitrary value so that it doesn't get too big? That seems like a hack though.
When scrolling on the touchpad I get pixelData values in the 20s max (typically it's just 1/-1 for slow movement). Also I get some 0s from angleData, but that probably doesn't hurt anything?

This is touchpad slow scroll
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 3
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 3
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 3
QcSlider::wheelEvent: using angleDelta: 0
This is touchpad faster scroll
QcSlider::wheelEvent: using pixelDelta: -1
QcSlider::wheelEvent: using pixelDelta: 0
QcSlider::wheelEvent: using pixelDelta: 0
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 3
QcSlider::wheelEvent: using pixelDelta: 8
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 23
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 41
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 51
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 46
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 45
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 42
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 26
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 20
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 15
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 9
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 5
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 3
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using angleDelta: 0
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
QcSlider::wheelEvent: using pixelDelta: 1
This is mousewheel slow scroll
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 2
This is mousewheel faster scroll (a single gesture of the mousewheel, moved the slider all the way up)
QcSlider::wheelEvent: using pixelDelta: 2
QcSlider::wheelEvent: using pixelDelta: 16
QcSlider::wheelEvent: using pixelDelta: 65
QcSlider::wheelEvent: using pixelDelta: 99
QcSlider::wheelEvent: using pixelDelta: 116
QcSlider::wheelEvent: using pixelDelta: 128
QcSlider::wheelEvent: using pixelDelta: 139
QcSlider::wheelEvent: using pixelDelta: 149
QcSlider::wheelEvent: using pixelDelta: 169

printing code
void QcSlider::wheelEvent(QWheelEvent* e) {
    double step = qMax(_step, pixelStep());
    double scrollRatio;
    // Qt advises against using pixelDelta on X11 (xcb)
    if (e->pixelDelta().isNull() || QGuiApplication::platformName() == "xcb") {
        std::cout << "QcSlider::wheelEvent: using angleDelta: " << e->angleDelta().y() << std::endl;
        scrollRatio = e->angleDelta().y() / 8. / 360. / 16.;
    } else {
        std::cout << "QcSlider::wheelEvent: using pixelDelta: " << e->pixelDelta().y() << std::endl;
        scrollRatio = e->pixelDelta().y() * pixelStep();
    }
    const double numSteps = scrollRatio / step; // totSteps = 1.0 / step
    modifyStep(&step);
    setValue(_value + step * numSteps);
    Q_EMIT(action());
}

@elgiano
Copy link
Contributor Author

elgiano commented Jul 4, 2025

ah interesting thanks!
Could you try to force the use of angleDelta (and perhaps adjust the scale to something like 360 / 2) and see how it all feels?

EDIT: and while you're at it, could you also print e->deviceType()?

void QcSlider::wheelEvent(QWheelEvent* e) {
    double step = qMax(_step, pixelStep());
    double scrollRatio;
    // Qt advises against using pixelDelta on X11 (xcb)
    if (e->pixelDelta().isNull() || QGuiApplication::platformName() == "xcb" || true) {
        std::cout << "QcSlider::wheelEvent: device " << (int) e->deviceType() << " using angleDelta: " << e->angleDelta().y() << std::endl;
        scrollRatio = e->angleDelta().y() / 8. / 360. / 16.;
    } else {
        std::cout << "QcSlider::wheelEvent: using pixelDelta: " << e->pixelDelta().y() << std::endl;
        scrollRatio = e->pixelDelta().y() * pixelStep();
    }
    const double numSteps = scrollRatio / step; // totSteps = 1.0 / step
    modifyStep(&step);
    setValue(_value + step * numSteps);
    Q_EMIT(action());
}

@elgiano
Copy link
Contributor Author

elgiano commented Jul 5, 2025

I pushed a new version, that uses angleDelta for mouse devices (with same scaling as we had in 3.13), X11 (with a faster scaling to match pixelDelta), and otherwise uses pixelDelta.

It works fine for me on both X and Wayland, and the behavior makes sense to me at different widget sizes, with different steps, and scales. Shift scale is way too fast when widget is small, but I think it makes sense like this.

@dyfer (and everyone interested, maybe @bgola?) can you give it a try?

@elgiano elgiano force-pushed the topic/wayland-scroll branch 2 times, most recently from 4350d97 to 4d7f769 Compare July 5, 2025 14:36
@dyfer
Copy link
Member

dyfer commented Jul 5, 2025

@elgiano I will try but at a later time. Sorry for the delay. I'll report back once I try. Thanks for working on this!

@dyfer
Copy link
Member

dyfer commented Jul 5, 2025

In 4d7f769 the touchpad feels exactly right for the slider 👍.

For the mouse, I have two comments:

  • I feel the movement is generally too small; for a default size Slider (Slider().front) I need to make approx 12-14 finger movements to go through the full range; if the slider is taller, like screen height, it takes many more movements. const double degreesPerStep = isMouse ? 4. : 1.; seemed like a better value for the default-sized slider, where I needed ~4-5 gestures to move through the full range. However...
  • Do we want the mouse operation to be scaled to the screen discance, as it is currently, as opposed to slider's full range? With the current implementation the number of turns of the mousewheel always moves the slider by a set number of pixels on screen; i.e. larger slider takes more turns to move from the very top to bottom. Do you feel we should change it so that we always get the same number of turns for the full range of the slider, regardless of the size? In that case pixelStep should only be used for the touchpad input, I guess.

@dyfer
Copy link
Member

dyfer commented Jul 6, 2025

All right, after more testing, here are my thoughts on macOS:

  • pixelDelta emitted from the mousewheel includes speed-based scaling (faster rotation -> more jump). I think this is more consistent with how other UI elements work on macOS, as it felt more natural, so based on that I think it'd be beneficial to keep using pixelDelta when available
    • fast mousewheel movement is a bit too fast in that case, but I think it's okay; slow movement is small enough; and faster movement when pressing alt/option seems to be accurate enough, at least subjectively
  • I think that for using angleDelta, it would be better to scale the full range to the number of rotations. I think something like two rotations of the mousewheel equal about 5 finger gestures and that seems to feel good for me for the full range of the slider. I tested this by forcing the condution below with if (e->pixelDelta().isNull() || isX11 || true). Could be also set a bit smaller per step. What do you think?
void QcSlider::wheelEvent(QWheelEvent* e) {
    double step;
    double numSteps;
    const bool isX11 = QGuiApplication::platformName() == "xcb";

    // use angleDelta for X11 (advice from Qt docs)
    if (e->pixelDelta().isNull() || isX11) {
        step = qMax(_step, 1.0 / 360.0);
        numSteps = e->angleDelta().y() / 8; // I think between "/ 8" and "/ 16" works well with my mouse, taking 5-10 finger gestures for the full range
    } else {
        step = qMax(_step, pixelStep());
        // for pixelDelta, map full range to widget dimension (= 1/pixelStep)
        // scrollPx / totPx = scrollSteps / totSteps (totSteps = 1.0 / step)
        numSteps = e->pixelDelta().y() * pixelStep() / step;
    }

    modifyStep(&step);
    setValue(_value + step * numSteps);
    Q_EMIT(action());
}

@dyfer
Copy link
Member

dyfer commented Jul 9, 2025

Does Shift actually swap axes on your mac though?

It does in other apps, where the window can be scrolled both horizontally and vertically, but only for the mousewheel , i.e. mousewheel scrolls window up and down shift+mousewheel scrolls left/right.
Shift does not change the behavior of the touchpad of course, as touchpad has both horizontal and vertical scrolling.
I can see this behavior e.g. in Chrome, Safari, and SC IDE post window (with word wrap off).

So all in all, we shouldn't have the alt fixes active on macOS. I'd need to check on Windows though.

BTW I think I know what's happening with shift on macOS:
Currently the mousewheel movement of Slider, Knob etc stops when I press shift. I think that's because it changes the scroll axis to the horizontal one (it does not affect movements from the touchpad though; i.e. shift only speeds it up). I don't think there's a clean way to address this though, as both mouse and touchpad come through pixelDelta... unless we use a switch for the mouse device?

So for Slider2D, when I tested before the alt changes were pushed, the behavior with the mousewheel was: when pressing shift, the axis switched to horizontal; but we also increased the step, so the value jumped all the way to left/right (step too large to be useful).

@elgiano
Copy link
Contributor Author

elgiano commented Jul 9, 2025

Ok, great, it's starting to become clearer now. Now Alt doesn't affect macos anymore, we need to test it on Windows though... and I've abstracted a function to get scrollRatio, now it's all in one place.

As for the scales: I think it would make sense to make the default scale like 10 times smaller, so that shift and ctrl scales can be used for bigger movements and alt for very fine tunings, and it also would address @bgola's comment. On the other hand I think the pixelDelta() scaling is a quite intuitive criterion (1px scroll -> 1px step). What do you think?

@dyfer
Copy link
Member

dyfer commented Jul 9, 2025

Thanks! Notes for 105f650 on macOS:

Slider, Knob, behave fine with touchpad and mousewheel. Behavior with shift is still noop (more below).

Slider2D: with natural scrolling on, the horizontal axis on touchpad is now inverted! I think this should only happen when the controls are not inverted.

BTW the Volume control still scrolls only up, the event->inverted() here needs to be removed.

Regarding shift:
I propose that we disable increasing the step with shift (and ctrl? is that supposed to change the scrolling speed as well?). On macOS scrolling has built-in scaling based on the gesture speed, and additionally has inertia (for touchpad only). This makes it easy and intuitive to make larger gestures, so I don't think having shift/ctrl is needed. Additionally, shift already changes scroll direction for mousewheel (on the OS level?) so it prevents vertical widgets from scrolling when pressed (and I don't think we need to work around this). Disabling shift for step change would be useful to make Slider2D work with the mousewheel though.

One more note: it looks like the UI design on macOS these days generally discourages using scroll for changing values. None of the Apple apps I tested (Logic Pro etc) allows changing values with a scroll. I'm not saying we should disable it on macOS, but I think we only need to make it work "well enough" i.e. not be counterintuitive, not necessarily have all the same functionality as other platforms. In my mind that means that we can forego "shift" on macOS, for example. But that's just my personal opinion.

@elgiano
Copy link
Contributor Author

elgiano commented Jul 10, 2025

I have a stronger and stronger feeling that we're over-engineering a seldom used feature... and also that we shouldn't change the behavior when all we wanted to do was to fix a regression.

The "original" behavior is to use angleDelta, assume 15deg/step and apply the number of steps. When no step is defined, it defaults to pixelStep, and it feels very slow because the step is very small. I would accept this because it fits with the metaphor and also because it would be the same behavior as in 3.13.

I would restore this behavior and accept that it feels slow when no step is provided. Alt and Ctrl work on all platforms to use a bigger or smaller step (and I would accept that shift doesn't work on macos, since it seems the axes swap is done by system and not by Qt). So I would also keep the "Alt" and "inverted" countermeasures put so far.

@dyfer
Copy link
Member

dyfer commented Jul 10, 2025

I have a stronger and stronger feeling that we're over-engineering a seldom used feature...

Yeah, I think that too...

But also this seems to be really close to be done right, at least on some systems. How do you want to proceed?

@elgiano
Copy link
Contributor Author

elgiano commented Jul 11, 2025

I agree it feels close to right, but I see three problems:

  • way we are changing the default feeling too much, and it's not configurable
  • we are changing the relationship between scroll and steps (from "a fixed amount of scroll applies a step" to "a certain visual proportion applies the same proportional increment")
  • in dealing with angleDelta and pixelDelta we face a lot of platform-specific differences and implementation details (e.g. angleDelta = 1.2 * pixelDelta, but it looks like on a macos touchpad it should be a = 2 * p)

So, I would like to go back to the previous paradigm, and to use only angleDelta. After all these are not scrolling contents, but adjustable stepping controls.

I also thought it would make sense to have a visual paradigm when the step is not defined, but then I also feel it should be configurable somehow, and such a big change of the default feeling could disrupt some user interfaces. We could also scale down pixelDelta to be comparable more or less with angleDelta (say, angleDelta = 1.5 * pixelDelta) but I don't know how much we could rely on such conversion.

I hope I was clear, sorry for the long hairy discussion, I can also push a version soon if you want to see how it would feel

@dyfer
Copy link
Member

dyfer commented Jul 11, 2025

I agree it feels close to right, but I see three problems:

  • way we are changing the default feeling too much, and it's not configurable

Yeah, the non-configurable part is unfortunate.

  • we are changing the relationship between scroll and steps (from "a fixed amount of scroll applies a step" to "a certain visual proportion applies the same proportional increment")

For the slider, when steps were not defined, weren't we effectively using the visual proportion though?

  • in dealing with angleDelta and pixelDelta we face a lot of platform-specific differences and implementation details (e.g. angleDelta = 1.2 * pixelDelta, but it looks like on a macos touchpad it should be a = 2 * p)

Yeah, the platform-specific differences are fragile.

So, I would like to go back to the previous paradigm, and to use only angleDelta. After all these are not scrolling contents, but adjustable stepping controls.

Okay. This will possibly degrade experience on macOS (angleDelta doesn't have the speed-based scaling and inertia IIUC), but OTOH on macOS scrolling with touchpad seems not really present in the UI paradigm so maybe we don't need to care about it.

I also thought it would make sense to have a visual paradigm when the step is not defined

Again, isn't this what we had before?

We could also scale down pixelDelta to be comparable more or less with angleDelta (say, angleDelta = 1.5 * pixelDelta) but I don't know how much we could rely on such conversion.

If we use pixelDelta, the current scaling feels good, so I wouldn't change that... after all, pixelDelta should refer to the visual paradigm. But maybe it's okay to revert to angleDelta

More comments:

  • I'd keep the "inverted" additions, so that the movement works always in the correct direction
  • I'd keep the current implementation around somewhere (maybe a branch on the main repo, linked in the PR's description) for a possible future consideration. In particular, I think we could re-consider using pixelDelta for Slider2D - that one looks like a potentially expressive controller with a touchpad. OTOH, maybe one should use HID for such use case anyway...

@elgiano
Copy link
Contributor Author

elgiano commented Jul 12, 2025

For the slider, when steps were not defined, weren't we effectively using the visual proportion though?

Almost: we were mapping 15 degrees from angleDelta to a pixelStep. That is: a fixed amount of scroll to an increment that depends on widget size: degreesScrolled / 15 * pixelStep, i.e angleDelta / 120 / widgetSize. In this PR we were doing: pixelDelta / widgetSize. The perceived difference is that the second version is much faster, as it behaves more like dragging the handle.

If we find a stable way to convert pixelDelta to angleDelta, we could still use pixelDelta, to keep the macos extra features like inertia. On my system is a more or less 10px/step, I suspect it will be different on other platforms, but if 10px/step turns out to be a good-enough average, and we don't get extreme differences, I'm ok to keep it!

If we use pixelDelta, the current scaling feels good

I'm afraid it's quite a drastic change from 3.13, as @bgola said, around 20x, so I would still scale it down to match the old angleDelta scaling, at least at this stage.

I'd keep the "inverted" additions, so that the movement works always in the correct direction

I would keep all the collateral improvements: both the inverted and the Alt key things, and also the fractional step accumulation.

I'd keep the current implementation around somewhere

I copied this branch to a new branch on main repo: https://github.com/supercollider/supercollider/tree/topic/qt-scroll-widgets-visual

For this PR, as a last attempt to keep pixelDelta, we can try to map 10px to a step and see if it's acceptable on all OSes.

@elgiano
Copy link
Contributor Author

elgiano commented Jul 12, 2025

I applied the changes to restore 3.13 behavior, plus the pixelDelta scaling attempt.
I also left in a debug print, among the other things it prints an estimate of px/step: on my system it varies between 7.5 and 15, thus I chose 10 as a reasonable average. I wonder what it prints on macos

@dyfer
Copy link
Member

dyfer commented Jul 13, 2025

Hi @elgiano

Almost: we were mapping 15 degrees from angleDelta to a pixelStep. That is: a fixed amount of scroll to an increment that depends on widget size: degreesScrolled / 15 * pixelStep, i.e angleDelta / 120 / widgetSize.

Hm really? I thought that this line used pixelDelta if available? It's not really important, I'm just trying to understand the previous state.

I applied the changes to restore 3.13 behavior, plus the pixelDelta scaling attempt.

Great, thanks!

I also left in a debug print, among the other things it prints an estimate of px/step: on my system it varies between 7.5 and 15, thus I chose 10 as a reasonable average. I wonder what it prints on macos

Here's the output on macOS. However, it shows up inconsistently in the post window (doesn't post for a while, then posts a bunch of lines at once) so I'm not sure what movement it actually corresponded to.

Details
-> a Slider
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,-1) pxDelta(1,-8) px/step(60.000000) steps(0.100000,-0.800000)
scroll: angleDelta/12(0,-2) pxDelta(1,-12) px/step(60.000000) steps(0.100000,-1.200000)
scroll: angleDelta/12(0,-2) pxDelta(1,-9) px/step(60.000000) steps(0.100000,-0.900000)
scroll: angleDelta/12(0,-1) pxDelta(1,-8) px/step(60.000000) steps(0.100000,-0.800000)
scroll: angleDelta/12(0,-1) pxDelta(1,-7) px/step(60.000000) steps(0.100000,-0.700000)
scroll: angleDelta/12(0,-1) pxDelta(1,-6) px/step(60.000000) steps(0.100000,-0.600000)
scroll: angleDelta/12(0,-1) pxDelta(1,-5) px/step(60.000000) steps(0.100000,-0.500000)
scroll: angleDelta/12(0,-1) pxDelta(1,-5) px/step(60.000000) steps(0.100000,-0.500000)
scroll: angleDelta/12(0,-1) pxDelta(0,-4) px/step(60.000000) steps(0.000000,-0.400000)
scroll: angleDelta/12(0,-1) pxDelta(0,-4) px/step(60.000000) steps(0.000000,-0.400000)
scroll: angleDelta/12(0,-1) pxDelta(0,-3) px/step(60.000000) steps(0.000000,-0.300000)
scroll: angleDelta/12(0,-1) pxDelta(0,-3) px/step(60.000000) steps(0.000000,-0.300000)
scroll: angleDelta/12(0,-1) pxDelta(0,-3) px/step(60.000000) steps(0.000000,-0.300000)
scroll: angleDelta/12(0,-1) pxDelta(0,-3) px/step(60.000000) steps(0.000000,-0.300000)
scroll: angleDelta/12(0,-1) pxDelta(-1,-4) px/step(60.000000) steps(-0.100000,-0.400000)
scroll: angleDelta/12(0,-1) pxDelta(-1,-4) px/step(60.000000) steps(-0.100000,-0.400000)
scroll: angleDelta/12(0,-1) pxDelta(-1,-3) px/step(60.000000) steps(-0.100000,-0.300000)
scroll: angleDelta/12(0,-1) pxDelta(-1,-3) px/step(60.000000) steps(-0.100000,-0.300000)
scroll: angleDelta/12(0,-1) pxDelta(-1,-3) px/step(60.000000) steps(-0.100000,-0.300000)
scroll: angleDelta/12(0,-1) pxDelta(-1,-3) px/step(60.000000) steps(-0.100000,-0.300000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(-1,-1) px/step(60.000000) steps(-0.100000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(0,-2) px/step(60.000000) steps(0.000000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-2) px/step(60.000000) steps(-0.100000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(-1,-1) px/step(60.000000) steps(-0.100000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(-1,-1) px/step(60.000000) steps(-0.100000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(-1,-1) px/step(60.000000) steps(-0.100000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,1) px/step(60.000000) steps(0.000000,0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,1) px/step(60.000000) steps(0.000000,0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,1) px/step(60.000000) steps(0.000000,0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,1) px/step(60.000000) steps(0.000000,0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,1) px/step(60.000000) steps(0.000000,0.100000)
scroll: angleDelta/12(0,1) pxDelta(-1,3) px/step(60.000000) steps(-0.100000,0.300000)
scroll: angleDelta/12(0,2) pxDelta(-1,9) px/step(60.000000) steps(-0.100000,0.900000)
scroll: angleDelta/12(0,2) pxDelta(-1,13) px/step(60.000000) steps(-0.100000,1.300000)
scroll: angleDelta/12(0,2) pxDelta(-1,14) px/step(60.000000) steps(-0.100000,1.400000)
scroll: angleDelta/12(0,2) pxDelta(-1,10) px/step(60.000000) steps(-0.100000,1.000000)
scroll: angleDelta/12(0,1) pxDelta(0,8) px/step(60.000000) steps(0.000000,0.800000)
scroll: angleDelta/12(0,2) pxDelta(0,14) px/step(60.000000) steps(0.000000,1.400000)
scroll: angleDelta/12(0,2) pxDelta(0,14) px/step(60.000000) steps(0.000000,1.400000)
scroll: angleDelta/12(0,3) pxDelta(0,15) px/step(60.000000) steps(0.000000,1.500000)
scroll: angleDelta/12(0,3) pxDelta(0,15) px/step(60.000000) steps(0.000000,1.500000)
scroll: angleDelta/12(0,3) pxDelta(0,15) px/step(60.000000) steps(0.000000,1.500000)
scroll: angleDelta/12(0,2) pxDelta(0,14) px/step(60.000000) steps(0.000000,1.400000)
scroll: angleDelta/12(0,3) pxDelta(0,16) px/step(60.000000) steps(0.000000,1.600000)
scroll: angleDelta/12(0,3) pxDelta(0,16) px/step(60.000000) steps(0.000000,1.600000)
scroll: angleDelta/12(0,2) pxDelta(0,14) px/step(60.000000) steps(0.000000,1.400000)
scroll: angleDelta/12(0,2) pxDelta(0,13) px/step(60.000000) steps(0.000000,1.300000)
scroll: angleDelta/12(0,2) pxDelta(0,12) px/step(60.000000) steps(0.000000,1.200000)
scroll: angleDelta/12(0,2) pxDelta(0,11) px/step(60.000000) steps(0.000000,1.100000)
scroll: angleDelta/12(0,2) pxDelta(0,10) px/step(60.000000) steps(0.000000,1.000000)
scroll: angleDelta/12(0,2) pxDelta(0,9) px/step(60.000000) steps(0.000000,0.900000)
scroll: angleDelta/12(0,1) pxDelta(0,8) px/step(60.000000) steps(0.000000,0.800000)
scroll: angleDelta/12(0,1) pxDelta(0,8) px/step(60.000000) steps(0.000000,0.800000)
scroll: angleDelta/12(0,1) pxDelta(0,7) px/step(60.000000) steps(0.000000,0.700000)
scroll: angleDelta/12(0,1) pxDelta(0,6) px/step(60.000000) steps(0.000000,0.600000)
scroll: angleDelta/12(0,1) pxDelta(0,6) px/step(60.000000) steps(0.000000,0.600000)
scroll: angleDelta/12(0,1) pxDelta(0,5) px/step(60.000000) steps(0.000000,0.500000)
scroll: angleDelta/12(0,1) pxDelta(0,5) px/step(60.000000) steps(0.000000,0.500000)
scroll: angleDelta/12(0,1) pxDelta(0,5) px/step(60.000000) steps(0.000000,0.500000)
scroll: angleDelta/12(0,1) pxDelta(0,4) px/step(60.000000) steps(0.000000,0.400000)
scroll: angleDelta/12(0,1) pxDelta(0,4) px/step(60.000000) steps(0.000000,0.400000)
scroll: angleDelta/12(0,1) pxDelta(0,4) px/step(60.000000) steps(0.000000,0.400000)
scroll: angleDelta/12(0,1) pxDelta(0,3) px/step(60.000000) steps(0.000000,0.300000)
scroll: angleDelta/12(0,1) pxDelta(0,3) px/step(60.000000) steps(0.000000,0.300000)
scroll: angleDelta/12(0,1) pxDelta(0,3) px/step(60.000000) steps(0.000000,0.300000)
scroll: angleDelta/12(0,1) pxDelta(0,3) px/step(60.000000) steps(0.000000,0.300000)
scroll: angleDelta/12(0,0) pxDelta(0,2) px/step(60.000000) steps(0.000000,0.200000)
scroll: angleDelta/12(0,0) pxDelta(0,2) px/step(60.000000) steps(0.000000,0.200000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,-1) pxDelta(-1,-5) px/step(60.000000) steps(-0.100000,-0.500000)
scroll: angleDelta/12(0,-3) pxDelta(-1,-15) px/step(60.000000) steps(-0.100000,-1.500000)
scroll: angleDelta/12(0,-3) pxDelta(-2,-19) px/step(60.000000) steps(-0.200000,-1.900000)
scroll: angleDelta/12(0,-4) pxDelta(-2,-23) px/step(60.000000) steps(-0.200000,-2.300000)
scroll: angleDelta/12(0,-4) pxDelta(-2,-25) px/step(60.000000) steps(-0.200000,-2.500000)
scroll: angleDelta/12(0,-4) pxDelta(-2,-21) px/step(60.000000) steps(-0.200000,-2.100000)
scroll: angleDelta/12(0,-3) pxDelta(-2,-15) px/step(60.000000) steps(-0.200000,-1.500000)
scroll: angleDelta/12(0,-5) pxDelta(0,-30) px/step(60.000000) steps(0.000000,-3.000000)
scroll: angleDelta/12(0,-6) pxDelta(0,-33) px/step(60.000000) steps(0.000000,-3.300000)
scroll: angleDelta/12(0,-6) pxDelta(0,-34) px/step(60.000000) steps(0.000000,-3.400000)
scroll: angleDelta/12(0,-6) pxDelta(0,-34) px/step(60.000000) steps(0.000000,-3.400000)
scroll: angleDelta/12(0,-6) pxDelta(0,-34) px/step(60.000000) steps(0.000000,-3.400000)
scroll: angleDelta/12(0,-6) pxDelta(0,-33) px/step(60.000000) steps(0.000000,-3.300000)
scroll: angleDelta/12(0,-6) pxDelta(0,-36) px/step(60.000000) steps(0.000000,-3.600000)
scroll: angleDelta/12(0,-6) pxDelta(0,-35) px/step(60.000000) steps(0.000000,-3.500000)
scroll: angleDelta/12(0,-5) pxDelta(0,-32) px/step(60.000000) steps(0.000000,-3.200000)
scroll: angleDelta/12(0,-5) pxDelta(0,-29) px/step(60.000000) steps(0.000000,-2.900000)
scroll: angleDelta/12(0,-4) pxDelta(0,-26) px/step(60.000000) steps(0.000000,-2.600000)
scroll: angleDelta/12(0,-4) pxDelta(0,-24) px/step(60.000000) steps(0.000000,-2.400000)
scroll: angleDelta/12(0,-4) pxDelta(0,-22) px/step(60.000000) steps(0.000000,-2.200000)
scroll: angleDelta/12(0,-3) pxDelta(0,-20) px/step(60.000000) steps(0.000000,-2.000000)
scroll: angleDelta/12(0,-3) pxDelta(0,-19) px/step(60.000000) steps(0.000000,-1.900000)
scroll: angleDelta/12(0,-3) pxDelta(0,-17) px/step(60.000000) steps(0.000000,-1.700000)
scroll: angleDelta/12(0,-3) pxDelta(0,-16) px/step(60.000000) steps(0.000000,-1.600000)
scroll: angleDelta/12(0,-2) pxDelta(0,-14) px/step(60.000000) steps(0.000000,-1.400000)
scroll: angleDelta/12(0,-2) pxDelta(0,-13) px/step(60.000000) steps(0.000000,-1.300000)
scroll: angleDelta/12(0,-2) pxDelta(0,-12) px/step(60.000000) steps(0.000000,-1.200000)
scroll: angleDelta/12(0,-2) pxDelta(0,-11) px/step(60.000000) steps(0.000000,-1.100000)
scroll: angleDelta/12(0,-2) pxDelta(0,-10) px/step(60.000000) steps(0.000000,-1.000000)
scroll: angleDelta/12(0,-2) pxDelta(0,-9) px/step(60.000000) steps(0.000000,-0.900000)
scroll: angleDelta/12(0,-1) pxDelta(0,-8) px/step(60.000000) steps(0.000000,-0.800000)
scroll: angleDelta/12(0,-1) pxDelta(0,-8) px/step(60.000000) steps(0.000000,-0.800000)
scroll: angleDelta/12(0,-1) pxDelta(0,-7) px/step(60.000000) steps(0.000000,-0.700000)
scroll: angleDelta/12(0,-1) pxDelta(0,-7) px/step(60.000000) steps(0.000000,-0.700000)
scroll: angleDelta/12(0,-1) pxDelta(0,-6) px/step(60.000000) steps(0.000000,-0.600000)
scroll: angleDelta/12(0,-1) pxDelta(0,-5) px/step(60.000000) steps(0.000000,-0.500000)
scroll: angleDelta/12(0,-1) pxDelta(0,-5) px/step(60.000000) steps(0.000000,-0.500000)
scroll: angleDelta/12(0,-1) pxDelta(0,-5) px/step(60.000000) steps(0.000000,-0.500000)
scroll: angleDelta/12(0,-1) pxDelta(0,-5) px/step(60.000000) steps(0.000000,-0.500000)
scroll: angleDelta/12(0,-1) pxDelta(0,-4) px/step(60.000000) steps(0.000000,-0.400000)
scroll: angleDelta/12(0,-1) pxDelta(0,-4) px/step(60.000000) steps(0.000000,-0.400000)
scroll: angleDelta/12(0,-1) pxDelta(0,-4) px/step(60.000000) steps(0.000000,-0.400000)
scroll: angleDelta/12(0,-1) pxDelta(0,-3) px/step(60.000000) steps(0.000000,-0.300000)
scroll: angleDelta/12(0,-1) pxDelta(0,-3) px/step(60.000000) steps(0.000000,-0.300000)
scroll: angleDelta/12(0,-1) pxDelta(0,-3) px/step(60.000000) steps(0.000000,-0.300000)
scroll: angleDelta/12(0,0) pxDelta(0,-2) px/step(60.000000) steps(0.000000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(0,-2) px/step(60.000000) steps(0.000000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(0,-2) px/step(60.000000) steps(0.000000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(0,-2) px/step(60.000000) steps(0.000000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(0,-2) px/step(60.000000) steps(0.000000,-0.200000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,0) pxDelta(0,-1) px/step(60.000000) steps(0.000000,-0.100000)
scroll: angleDelta/12(0,1) pxDelta(0,5) px/step(60.000000) steps(0.000000,0.500000)
scroll: angleDelta/12(0,9) pxDelta(0,53) px/step(60.000000) steps(0.000000,5.300000)
scroll: angleDelta/12(0,38) pxDelta(1,230) px/step(60.000000) steps(0.100000,23.000000)
scroll: angleDelta/12(0,46) pxDelta(0,273) px/step(60.000000) steps(0.000000,27.300000)
scroll: angleDelta/12(0,42) pxDelta(0,250) px/step(60.000000) steps(0.000000,25.000000)
scroll: angleDelta/12(0,39) pxDelta(0,231) px/step(60.000000) steps(0.000000,23.100000)
scroll: angleDelta/12(0,36) pxDelta(0,216) px/step(60.000000) steps(0.000000,21.600000)
scroll: angleDelta/12(0,34) pxDelta(0,202) px/step(60.000000) steps(0.000000,20.200000)
scroll: angleDelta/12(0,32) pxDelta(0,190) px/step(60.000000) steps(0.000000,19.000000)
scroll: angleDelta/12(0,28) pxDelta(0,169) px/step(60.000000) steps(0.000000,16.900000)
scroll: angleDelta/12(0,27) pxDelta(0,159) px/step(60.000000) steps(0.000000,15.900000)
scroll: angleDelta/12(0,26) pxDelta(0,154) px/step(60.000000) steps(0.000000,15.400000)
scroll: angleDelta/12(0,25) pxDelta(0,149) px/step(60.000000) steps(0.000000,14.900000)
scroll: angleDelta/12(0,24) pxDelta(0,144) px/step(60.000000) steps(0.000000,14.400000)
scroll: angleDelta/12(0,23) pxDelta(0,139) px/step(60.000000) steps(0.000000,13.900000)
scroll: angleDelta/12(0,22) pxDelta(0,134) px/step(60.000000) steps(0.000000,13.400000)
scroll: angleDelta/12(0,22) pxDelta(0,129) px/step(60.000000) steps(0.000000,12.900000)
scroll: angleDelta/12(0,21) pxDelta(0,124) px/step(60.000000) steps(0.000000,12.400000)
scroll: angleDelta/12(0,20) pxDelta(0,119) px/step(60.000000) steps(0.000000,11.900000)
scroll: angleDelta/12(0,19) pxDelta(0,113) px/step(60.000000) steps(0.000000,11.300000)
scroll: angleDelta/12(0,18) pxDelta(0,109) px/step(60.000000) steps(0.000000,10.900000)
scroll: angleDelta/12(0,18) pxDelta(0,105) px/step(60.000000) steps(0.000000,10.500000)
scroll: angleDelta/12(0,17) pxDelta(0,100) px/step(60.000000) steps(0.000000,10.000000)
scroll: angleDelta/12(0,16) pxDelta(0,96) px/step(60.000000) steps(0.000000,9.600000)
scroll: angleDelta/12(0,15) pxDelta(0,90) px/step(60.000000) steps(0.000000,9.000000)
scroll: angleDelta/12(0,14) pxDelta(0,85) px/step(60.000000) steps(0.000000,8.500000)
scroll: angleDelta/12(0,14) pxDelta(0,81) px/step(60.000000) steps(0.000000,8.100000)
scroll: angleDelta/12(0,13) pxDelta(0,77) px/step(60.000000) steps(0.000000,7.700000)
scroll: angleDelta/12(0,12) pxDelta(0,72) px/step(60.000000) steps(0.000000,7.200000)
scroll: angleDelta/12(0,11) pxDelta(0,68) px/step(60.000000) steps(0.000000,6.800000)
scroll: angleDelta/12(0,10) pxDelta(0,62) px/step(60.000000) steps(0.000000,6.200000)
scroll: angleDelta/12(0,10) pxDelta(0,58) px/step(60.000000) steps(0.000000,5.800000)
scroll: angleDelta/12(0,9) pxDelta(0,55) px/step(60.000000) steps(0.000000,5.500000)
scroll: angleDelta/12(0,9) pxDelta(0,51) px/step(60.000000) steps(0.000000,5.100000)
scroll: angleDelta/12(0,8) pxDelta(0,47) px/step(60.000000) steps(0.000000,4.700000)
scroll: angleDelta/12(0,7) pxDelta(0,43) px/step(60.000000) steps(0.000000,4.300000)
scroll: angleDelta/12(0,7) pxDelta(0,41) px/step(60.000000) steps(0.000000,4.100000)
scroll: angleDelta/12(0,6) pxDelta(0,37) px/step(60.000000) steps(0.000000,3.700000)
scroll: angleDelta/12(0,6) pxDelta(0,34) px/step(60.000000) steps(0.000000,3.400000)
scroll: angleDelta/12(0,5) pxDelta(0,32) px/step(60.000000) steps(0.000000,3

I think the slower scroll on macOS with touchpad is fine, with inertia etc it's easy enough to change the value I think, even if it doesn't correspond to the physical distance on screen.

Also, we were hoping with @capital-G to release 3.14.0-rc2 in about 24h... (after 18:00 UTC on Monday), do you think this is good in the current form (minus the printing of course)? Do we need more testing on Linux - X11 or Waylwand?

Also, I tested this build on Windows 10. Scrolling is slow, but I think it's okay. Curiously, inverted scroll setting is not detected (i.e. when turned on, the widgets scroll in opposite direction). Nothing we can do about it, just FYI.

@elgiano
Copy link
Contributor Author

elgiano commented Jul 13, 2025

Ok, thanks! I didn't expect such a difference in px/step, but since you say it feels ok I would leave it as it is.
I'll clean up a bit and push the final version before tomorrow!

Hm really? I thought that this line used pixelDelta if available? It's not really important, I'm just trying to understand the previous state.

This line was changed in 9d10e19, which is the commit that replaced the deprecated delta(). Although that commit is from 2021, it was merged only in #6464, for 3.14. Here is what happened: 9d10e19#diff-480d87f75c40eaca8c16adeba1eace52c326c93034c1c8cda02e753eca4fee57L117
Before that change, in 3.13, we were only using angleDelta / 120. After, we were using either angleDelta / 8160 or pixelDelta / 120. Although I believe pixelDelta / 120 works ok on mac, this made scrolling on X and Wayland much much slower.

@dyfer
Copy link
Member

dyfer commented Jul 13, 2025

This line was changed in 9d10e19, which is the commit that replaced the deprecated delta(). Although that commit is from 2021, it was merged only in #6464, for 3.14.

Ah, I totally forgot about that. Thanks for pointing it out!

@dyfer
Copy link
Member

dyfer commented Jul 13, 2025

Ok, thanks! I didn't expect such a difference in px/step,

I think this happens when you "fling" fingers over touchpad to quickly scroll.
But again, because of the delayed posting (I wonder what's that about?) I couldn't confirm for sure.

@elgiano
Copy link
Contributor Author

elgiano commented Jul 13, 2025

I think this happens when you "fling" fingers over touchpad to quickly scroll.

I meant the difference in px/step = 60 (which never changes in your example), compared to the values I get on Wayland (~10) and X (120, but anyway we are not using pxDelta on X).

@dyfer
Copy link
Member

dyfer commented Jul 13, 2025

I meant the difference in px/step = 60 (which never changes in your example), compared to the values I get on Wayland (~10) and X (120, but anyway we are not using pxDelta on X).

Ah, got it.

Copy link
Member

@dyfer dyfer left a comment

Choose a reason for hiding this comment

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

Thanks!

I think this is a net improvement and can go in as is.

One point I want to clarify: sliders with larger steps are now scrolled "by step", i.e. a fixed amount of movement of the touchpad/wheel will always advance by one step. (Do we still use the fractional step accumulation?). This feels "too large" for large steps with the touchpad, but I think it's reasonable for mouse scroll wheel movement. The behavior is I think similar to 3.13, but scroll was so much slower in 3.13 that it's hard to tell.

@elgiano
Copy link
Contributor Author

elgiano commented Jul 13, 2025

I had a typo in the last commit (checking "x11" instead of "xcb").
Also, I didn't understand that scrolling became much faster on macos, it doesn't on wayland... and that's the difference between 10 px/step and 60 px/step. The behavior with defined steps it's also the same as in 3.13.

EDIT: we still use fractional step accumulation, which makes a big difference with how 3.13 worked. On 3.13 in wayland, I can't scroll a slider with step=0.5 at all, while with this PR i can

I think in the future if we want to use pixelDelta, we have to to switch to the visual paradigm, which also makes sense with high-res scrolling to make small or big adjustments. But I also don't want to keep going in circles :D so if this behavior is good enough for you, let's go with it and eventually change it in a later release.

Alternatively, we can still go back to using only angleDelta to match perfectly the 3.13 behavior.

@dyfer
Copy link
Member

dyfer commented Jul 13, 2025

@elgiano this is good for me as is, if you can vouch for behavior on Linux. I don't want to keep going in circles either.
I think we could consider the visual scrolling paradigm, but let's leave that for the future.
This PR makes scrolling operational - "good enough" - on all platforms IIUC (I haven't tested on Linux).

@elgiano elgiano merged commit a97e871 into supercollider:3.14 Jul 13, 2025
23 checks passed
@github-project-automation github-project-automation bot moved this from In progress to Done in 3.14.0 Jul 13, 2025
@capital-G
Copy link
Contributor

What a ride - thanks gianluca and marcin for tackling this <3

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

Labels

comp: Qt GUI sclang Qt components -- for IDE tickets, use "env: SCIDE" instead env: SCIDE squash on merge

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Scroll behavior in Slider GUIs changed from 3.13 to 3.14 (Linux/Sway/Wayland)

4 participants