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

Emscripten mouse wheeling (was: GLFW Emscripten fixes) #6096

Closed
wants to merge 2 commits into from

Conversation

topolarity
Copy link

A couple of small fixes for targeting GLFW with Emscripten.

Please let me know if anything needs improving 🙂

…sonable

In my browser, I noticed that zooming seemed jumpy and unreliable.
Turns out that my fingers were moving horizontally on my trackpad,
and the x-scroll was causing the unexpected behavior.
ocornut pushed a commit that referenced this pull request Jan 20, 2023
…#6096)

This is used to populate the monitor array, which is only a requirement for multi-viewport support (which is not supported by Emscripten anyway).
@ocornut ocornut added the inputs label Jan 20, 2023
@ocornut
Copy link
Owner

ocornut commented Jan 20, 2023

Thank you for the PR!

I have reworded and pushed the first change as 6342275, feels simpler this way.

About the mis-caled horizontal event, would you be able to do some extra research on this?
Where is that coming from?
Does it happens on SDL backend?
I'm tagging @floooh (hope it's ok) as I suspect they are well acquainted with Emscripten inputs and may know an explanation for this.

The "flipped" component is a bit confusing and #4019 is still open.

@wolfpld
Copy link
Contributor

wolfpld commented Jan 20, 2023

About the mis-caled horizontal event, would you be able to do some extra research on this?
Where is that coming from?

If you go to https://tracy.nereid.pl/ and try to scroll horizontally using a laptop touchpad, you will see that the way input behaves makes this uncontrollable.

@ocornut
Copy link
Owner

ocornut commented Jan 20, 2023

Bartosz: I don't have access to one right now, it is the same issue (scrolling being way too fast)? If so, does the scaling suggested by the PR seems reasonable to your case.

Worth mentioning if this is on Mac or not? Mac drivers can emit simultaneous 2D events, which used to be problematic #3795 #4559 but 1.89.2 made changes to this, which may include side-effects. Could be unrelated and you may be referring 100% to the scaling issue.

As for the X direction: the crux of #4019 is we need to clarify/standardize who exactly is responsible for direction flipping. As you know Macs application eends to scroll the "opposite" way of Windows. Some low-level libraries (SDL/GLFW) tends to apply this flipping on Mac, our backends also tends to do it, and our scrolling functions also do it, and it's currently a bit inconsistent. This is the essence of #4019 to untangle this. What should we report to end-user wanting to use horizontal wheeling to alter values that are not scrolling? Probably something consistent across platforms

Add to the mix that I don't think an Emscripten target is considered an __APPLE__ build from the POV of the compiler, the build is same for both Apple and Windows and flipping may need to done based on a runtime configuration flag (io.ConfigMacOSXBehaviors) which currently we don't/can't set when run on a Mac with Emscripten. It'd be useful to find how we can detect being run on a Mac with an Emscripten build and set io.ConfigMacOSXBehaviors = true; there.

EDITED

@wolfpld
Copy link
Contributor

wolfpld commented Jan 20, 2023

I don't have access to one right now, it is the same issue (scrolling being way too fast)? If so, does the scaling suggested by the PR seems reasonable to your case.

This is upstreaming of a PR that OP originally submitted to Tracy: wolfpld/tracy#510

Worth mentioning if this is on Mac or not?

It seems to be OK on Windows, sorta-kinda OK-ish on Mac and unusable on Linux (with Wayland).

Add to the mix that I don't think an Emscripten target is considered an __APPLE__ build from the POV of the compiler, the build is same for both Apple and Windows and flipping may need to done based on a runtime configuration flag (io.ConfigMacOSXBehaviors) which currently we don't/can't set when run on a Mac with Emscripten. It'd be useful to find how we can detect being run on a Mac with an Emscripten build and set io.ConfigMacOSXBehaviors = true; there.

I do not think tying this with MacOS is the right solution. I use "natural" scroll direction on Linux, and #5167 suggests it's also an option on Windows.

FWIW, I had to reverse the scroll direction in my Wayland backed, for the scroll to be consistent with what should happen both when the "natural" scroll is enabled (view contents are moved in the same direction as finger movement, as if you were moving a paper sheet on a table) and when it is disabled (when the viewport itself, or an imaginary camera looking down at the contents, is following finger movement).

https://github.com/wolfpld/tracy/blob/baaed68bcf7e2dd9ea6b39cb6e26142514089bb3/profiler/src/BackendWayland.cpp#L243-L254

More generally, I have been thinking in context of Wayland how ImGui represents the possible inputs space, and it seems to be lacking many things. For example, mouse click events and touch inputs should be handled separately, but currently both are mapped to mouse click events. Or how scroll events are emulating the digital way of mouse wheel event reporting, while with touchpad hardware you also get "scroll start" and "scroll end" events. Handling these last two events would, for example, enable things like fast-swipe-and-release, to do kinetic scrolling, as seen in mobile phone UIs.

@ocornut
Copy link
Owner

ocornut commented Jan 20, 2023

It would make sense at least to create a separate flag for "natural" scroll.
Regardless it would still be beneficial to be able to poll the correct default when running on Emscripten (or elsewhere).

mouse click events and touch inputs should be handled separately

Why? (Serious question). My assumption is that for touch events (#3453, #2650 etc) we'll probably tag existing mouse events with additional metadata, and let them be handled the same way in a majority of code paths. But it's good to know specifically how we can better cater to touch inputs.

My new Windows laptop has a touch-screen so I should be able to investigate this better.

I'm not sure I fully understand the purpose/need of scroll-start/scroll-end and what can be done. This probably ought to have its own issue but realistically we still need to make progress on better touch support.

@wolfpld
Copy link
Contributor

wolfpld commented Jan 20, 2023

Why? (Serious question).

Most basically, the touch and mouse events are not exclusive wrt each other. What should happen, when you have a touch event active, mapped to left mouse button, and at the same time you press the left mouse button on your mouse?

More specifically, with touch events the button ID has no special meaning. It's just an ID that you use to track the event. So, you can have touches 0 and 1, both scrolling contents in independent windows, and then touch 2 clicks on some button, which should execute the assigned LMB action. Consider that all these touches are active at the same time, and common hardware typically supports tracking of 10 touches. If this would be with a mouse, the button ID 2 would indicate the right mouse button, which may have completely different meaning, such as opening a context menu.

I'm not sure I fully understand the purpose/need of scroll-start/scroll-end and what can be done.

Please see the following video for visual explanation of the kinetic scrolling:

https://www.youtube.com/watch?v=b68mskFTHX0

In Tracy, I map mouse wheel events to zoom in / zoom out. Since wheel events are impulses, I can set the target view width to e.g. 90% of the current view and start a short-running lerp, for extra smoothness. Since wheel impulses arrive infrequently, due to the hardware design of scroll wheel encoder, this works fine.

However, if wheel events are coming from an analog touchpad, the stream of events may be continuous, in which case the discrete processing described above does not work as expected, either under- or over-shooting the actual finger movement. In this case a more natural solution would be to switch to a 1:1 mapping of finger movement to zoom, triggered when the scroll event starts, and then maybe add kinetic scroll when the event ends.

(And of course, you can have touchpad scrolling active and mouse wheel impulses arriving at the same time.)

This probably ought to have its own issue but realistically we still need to make progress on better touch support.

Yes, this is out-of-scope for this PR.

@ocornut
Copy link
Owner

ocornut commented Jan 20, 2023

More specifically, with touch events the button ID has no special meaning. It's just an ID that you use to track the event. So, you can have touches 0 and 1, both scrolling contents in independent windows, and then touch 2 clicks on some button, which should execute the assigned LMB action. Consider that all these touches are active at the same time, and common hardware typically supports tracking of 10 touches. If this would be with a mouse, the button ID 2 would indicate the right mouse button, which may have completely different meaning, such as opening a context menu.

Multi-touch is a whole different beast that simply "better support for basic touch".
I imagine our touch support would generally emulate left-mouse button and some interactions may be tweaked or altered knowing the input comes from a touch device, and knowing how those devices behave differently than a mouse.

But simultaneous and separate touch interactions (e.g. scrolling two windows at the same time) I cannot imagine happening with the current codebase and scope of Dear ImGui, unfortunately. It's way far off what would be reasonable with the codebase.
Of course we can process and provide that raw data to the user (multi-touch tracking data) but Dear ImGui widgets/windowing taking advantage of it would likely be too complicated.

Some of the most-important multi-touch handling (transforming drags of three fingers into a scrolling event) would already be handled by the OS or backend and transparent to Dear ImGui. We can even possibly make backends report more multi-touch-specific events (e.g. zooming gesture). I'm just saying: (A) touch should be handled by default by widget codes and this should report as mouse-click + metadata for optional custom processing (B) separate touch interactions are unlikely to be supported.

Please see the following video for visual explanation of the kinetic scrolling:
https://www.youtube.com/watch?v=b68mskFTHX0

Thanks for the reference.
It seems possible to implement that without too much trouble provided our mouse-clicks are tagged with data to let us know when come from a touch device (e.g. scrolling logic will treat a mouse-click-coming-from-touch as a scroll stopper but not a mouse-click-coming-from-mouse).

However, if wheel events are coming from an analog touchpad, the stream of events may be continuous, in which case the discrete processing described above does not work as expected, either under- or over-shooting the actual finger movement. In this case a more natural solution would be to switch to a 1:1 mapping of finger movement to zoom, triggered when the scroll event starts, and then maybe add kinetic scroll when the event ends.

Likewise if the mouse event is tagged you can have specific code to select a behavior.

(And of course, you can have touchpad scrolling active and mouse wheel impulses arriving at the same time.)

But for that: 🙈🙉

@floooh
Copy link
Contributor

floooh commented Jan 20, 2023

I don't have all that much experience with Emscripten's GLFW implementation unfortunately. Emscripten basically has its own GLFW emulation, so it's not guaranteed to behave like native GLFW and probably also doesn't keep track of bug fixes and new features in GLFW.

The Emscripten emulation layer is here: https://github.com/emscripten-core/emscripten/blob/main/src/library_glfw.js

...and specifically the mouse wheel event handler (which interestingly flips the scroll axis): https://github.com/emscripten-core/emscripten/blob/373b282c8fb785927032f90d4a105badb6ebb035/src/library_glfw.js#L518-L543

...the rest of the code (Browser.getMouseWheelDelta) is here, this takes care of the different 'scroll deltas' in browser wheel events:

https://github.com/emscripten-core/emscripten/blob/373b282c8fb785927032f90d4a105badb6ebb035/src/library_browser.js#L559-L602

For comparison this is my Emscripten mouse wheel handler in sokol_app.h:

https://github.com/floooh/sokol/blob/b3aecb21825f3191a9a150bc0918fc8c7df7855c/sokol_app.h#L5072-L5094

I'm also flipping the direction there, but this then passed as is to Dear ImGui, I don't understand why a 'double flip' is necessary with Emscripten's GLFW emulation.

I'm afraid that's all I can help with :) For a "proper application" I wouldn't use Emscripten's GLFW emulation, also since these emulation layers in Emscripten are only minimally maintained anymore (AFAIK!). I probably would still accept the PR since the changes are not all that "intrusive" :)

@wolfpld
Copy link
Contributor

wolfpld commented Jan 21, 2023

I fully understand your reluctance to make changes here, especially considering the target audience of ImGui. However, in my experience, going against the available APIs by inventing common abstractions creates more problems than it solves.

I imagine our touch support would generally emulate left-mouse button and some interactions may be tweaked or altered knowing the input comes from a touch device, and knowing how those devices behave differently than a mouse.

Right now, I see the following problems when using touch on emscripten (with GLFW):

  • Performing a touch action hides the mouse cursor. When I touch an element, let's say a window opens, and that's the action I expect. However, there may also be a tooltip associated with the item that opens the window. Touching the item moves the cursor there, even though it's not visible, and now I'm left with a lingering popup. This is very confusing and is a consequence of treating touch the same as the mouse.
  • With touch, it is almost impossible to get a touch down and touch up position to be the same, because the finger's touch position will move, due to the inherent imprecision of this mode of operation. Somewhere along the input path, something "fixes" this by reporting actions only on the touch-up event. This leads to completely anti-intuitive behavior. For example, let's say I want to move an ImGui window. I touch the title bar, but nothing happens. I do not notice this and move my finger to move the window. Nothing happens because the touch event has not been reported yet. I release the touch in frustration, and now something completely unexpected happens, because my finger moved, without my intention, over some other item that has an associated action.
  • For a proper fix, you need to set a threshold that switches the interaction from a stationary "click" to a "drag" motion. As mentioned before, touch is imprecise, so you will get some positional wiggle even if you intended only a click. This threshold should not be applied to events coming from the mouse.
  • Two-finger scrolling (which works fine in file explorer, for example) is overridden by the browser to perform a "pinch zoom".

It may be easy to assume that touch input is a mobile thing, and therefore not something to worry about too much. Even if you have it available on a laptop, it's more or less a gimmick, since you also have the touchpad and the ability to connect a mouse, so why bother? It's easy to think that way. Then you realize that your laptop is a 2-in-1 and can be physically reconfigured to act as a tablet, and now all the input you have is just touch.

But for that: 🙈🙉

It is not something that I would want to support, but the design of the API does not preclude it from happening.

@topolarity
Copy link
Author

@ocornut Thanks for the revised commit and review so far

I'm happy to do more research if needed. Can you let me know what still needs looking into, based on the latest discussion?

@ocornut
Copy link
Owner

ocornut commented Jan 31, 2023

I don't disagree we should better support touch and those specific issues can be brought one by one in issues and improving.
What I am saying is that the codebase won't support two widgets being interacted with simultaneously, it's just too difficult as it breaks hundreds of assumptions in the codebase, and we have to work with that. That's the only thing I am pushing against, and it's merely because I see no other options. And I argue that "interacting with multiple widgets simultaneously" is not a very big loss, regardless of platform (mobile or laptop with touch). We can still in theory provide the multi-touch data and let common or custom behaviors use them (e.g. pinch becomes a zoom or scroll) but that's still within the realm of "only one widget is being interacted with".

Can you let me know what still needs looking into, based on the latest discussion?

I need to test/confirm values on Windows and OSX on my side for the scale and perhaps flip.
About the flip, before I know where to best put it, #4019 would need a better low-level records which I'll work on from Rokups's table. I think the values available from io.MouseWheel should follow the same direction on all systems and all configurations, and then scrolling code may interpret them differently, and we can provide a high-level per-axis "flip" factor for custom-widgets wanting to use that data for scrolling.

@ocornut ocornut changed the title Miscellaneous GLFW Emscripten fixes Emscripten mouse wheeling (was: GLFW Emscripten fixes) Jan 31, 2023
@ocornut
Copy link
Owner

ocornut commented Jan 31, 2023

"A couple of small fixes for targeting GLFW with Emscripten."

FYI no topic is too small. I am afraid even this mouse-wheeling issue may be a relatively large mess to untangle.
Let's keep this topic focused on the wheeling issue for now.

I ran dozens tests for raw value we are getting across backends and browsers. I added them to the existing thread about this (which already had a couple of infos)
#4019 (comment)
I yet have to add tests on my laptop PC which also has a touchpad.

I have also added a preliminary Emscripten+GLFW example but not committed it as currently it doesn't resize dislay properly, however for the purpose of testing mouse events I tested that along with Emscripten+SDL and found the wheel values were mostly identical.

About Wheel Magnitude:

  • The scale of values differs A LOTS depending on the 5 browsers/configs I have tried. On my Windows alone I get precise steps of 108, 350 or 175 depending on browser. vs ~1 natively.
  • Which leads me to think we need more info/data to be able to interpret those events reliably. I don't know where to source that info (JS/CSS layer ? Could Emscripten do a better job?).
  • GLFW+Emscripten gave me same results as SDL+Emscripten.
  • From the values I am getting I don't understand why your patch scales X but not Y axis. I see them equally wrong.
  • We can lazily clamp values to -1/+1 (app can parse and modify new events) on Emscripten but that will affect smooth scrolling and things may feel off.

About Horizontal Wheel Sign:

@wolfpld
Copy link
Contributor

wolfpld commented Jan 31, 2023

We can lazily clamp values to -1/+1 (app can parse and modify new events) on Emscripten but that will affect smooth scrolling and things may feel off.

Sorry, but this is completely wrong. Clamping this to unit values would completely destroy interaction through touchpad.

I don't know where to source that info (JS/CSS layer ? Could Emscripten do a better job?).

The emscripten html5.h interface is what all wrapper libraries have to rely on internally, for example here's how SDL does things:

https://github.com/libsdl-org/SDL/blob/22c69bccdf18d6c5c0990de9bc89e5e76d3a7c50/src/video/emscripten/SDL_emscriptenevents.c#L699-L719

(You gotta love that dy is negated there, but dx isn't.)

The API is explained in the emscripten's html5.h interface documentation:

https://emscripten.org/docs/api_reference/html5.h.html#wheel

The API is very simple to understand. You have the mouse wheel callback function, which delivers events:

typedef EM_BOOL (*em_wheel_callback_func)(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData);

The interesting data is passed in EmscriptenWheelEvent struct:

double deltaX
double deltaY
double deltaZ
unsigned long deltaMode

Now, deltaMode is one of "pixel", "line" or "page" modes of operation, which determine in which units the change along the axes is measured. The meaning of these modes is explained in the W3C specification:

https://w3c.github.io/uievents/#events-wheelevents

DOM_DELTA_PIXEL - The units of measurement for the delta MUST be pixels. This is the most typical case in most operating system and implementation configurations.
DOM_DELTA_LINE - The units of measurement for the delta MUST be individual lines of text. This is the case for many form controls.
DOM_DELTA_PAGE - The units of measurement for the delta MUST be pages, either defined as a single screen or as a demarcated page.

What does this mean exactly is defined in another castle. I'm not even sure if a pixel is a pixel, as Hi-DPI displays and UI scaling is a thing, and you have multiple possible coordinate spaces to consider.


The simplest way to inspect the raw data provided by the browser is to run the following program:

#include <emscripten.h>
#include <emscripten/html5.h>
#include <stdio.h>

int main()
{
    emscripten_set_wheel_callback( EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, []( int, const EmscriptenWheelEvent* ev, void* ) {
        printf( "mode: %lu dx: %g, dy: %g, dz: %g\n", ev->deltaMode, ev->deltaX, ev->deltaY, ev->deltaZ );
        return EM_TRUE;
    } );
    emscripten_set_main_loop( []{}, 0, false );
}

You can compile it using the emscripten SDK with em++ wheel.cpp -o wheel.html, and then serve with python -m http.server (you won't be able to just open the resulting html file from disk).

For your convenience I have placed this test program at https://nereid.pl/wheel/

@ocornut
Copy link
Owner

ocornut commented Feb 1, 2023

Sorry, but this is completely wrong

I understand it is wrong but it would currently be less wrong than interpreting GLFW/SDL mouse wheel inputs coming via Emscripten that have widely different magnitude. That /20 scaling factor seems like it means something different depending on each browser.

Thanks for the links and details. I’ll check the testbed tomorrow.

First I’m going to try fixing the sign inconsistencies. Obtaining usable Magnitudes will need more investigation on Emscripten side

ocornut added a commit that referenced this pull request Feb 1, 2023
…direction consistent accross backends/os. (#4019, #6096, #1463)

Documented assumptions.
@ocornut
Copy link
Owner

ocornut commented Feb 1, 2023

I have pushed 3617a96, see details and excel sheet in #4019 (comment)

I went through the data many times on many configuration and couldn't quite understand why you didn't flip nor scale the vertical wheel data. According to all my data it should be done just as well. I wondered if it was because Tracy mostly uses Horizontal scrollbars and not much Vertical ones.

About Magnitude

The magnitude of wheel inputs under Emscripten (with SDL or GLFW backends) is still a problem which I have merely used a dumb scale factor for. We should continue this specific discussion here.

Based on the data I have obtained ON MY DEAR IMGUI TESTBEDS I have scaled the magnitude down by 120.0f and not 20.0f (as 20.0f was still very unusable). However it is possible based on details posted above that this depends on the container HTML/JS. Someone who need to dig deeper to figure out what the right solution is (I'm sure there is one), perhaps replacing the container HTML/JS of imgui's example_emscripten_opengl3/ with one closer to yours we can validate this hypothesis.

I did run https://nereid.pl/wheel and recorded some values but since presently our aim is to support SDL and/or GLFW I can't do much with this, as I am not sure what value transforms are applied by the emulation layers for SDL and GLFW. This is however a good reference of what the raw data should be.

About Shift+Vertical Wheel on OSX

When running on Emscripten under OSX, Shift+WheelY with a mouse will be cancelled by our own handling of Shift+WheelY, this is because io.ConfigMacOSXBehaviors is not set. We should either disable Shift+WheelY support turning into WheelX when compiled for Emscripten, or find a way to detect an OSX client while running in Emscripten and set io.ConfigMacOSXBehaviors. If we can do the later it is preferable, if there is a consensus that we cannot we could disable our handling or provide a config flag to disable it.

** About Tracy being a savy used a horizontal scrolling **

We could consider a feature where vertical wheel could be redirect to horizontal scroll under some conditions. If interested please open a dedicated issue.

@ocornut
Copy link
Owner

ocornut commented Feb 1, 2023

Note that it is also perfectly possible that the "flipping" is part of a HTML/CSS/JS directive for scroll direction, which would explain the difference between your version and mine.

@ocornut
Copy link
Owner

ocornut commented Feb 1, 2023

Note that it is also perfectly possible that the "flipping" is part of a HTML/CSS/JS directive for scroll direction, which would explain the difference between your version and mine.

Effectively both my fix and your fix being so different suggest they are both wrong, so this issue is entirely Open, but hopefully now that backends are better standardized and we have the data in a sheet it should be easier to reason about the Emscripten parts without noise.

(I also now have a Mac at hands, a Windows with touchpad and a mouse with dual-wheel emulation.)

@wolfpld
Copy link
Contributor

wolfpld commented Feb 1, 2023

Emscripten apps may need to find a way to detect this and set io.ConfigMacOSXBehaviors manually (if you know a way let us know!)

Browser user-agent string.

https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent

@ocornut
Copy link
Owner

ocornut commented Feb 1, 2023

I would have no idea how to sanely surface this data into the C++ side, or at least massage it on JS side into a bool for C++ side, whichever makes more sense.

@wolfpld
Copy link
Contributor

wolfpld commented Feb 1, 2023

Refer to https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#call-compiled-c-c-code-directly-from-javascript for information on javascript string to char ptr conversion, and the immediately following section for information on executing javascript snippets from within C/C++ code.

Example code:

float Backend::GetDpiScale()
{
#ifdef __EMSCRIPTEN__
    return EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
#endif
    ...
}

@ocornut
Copy link
Owner

ocornut commented Feb 2, 2023

I have noticed that my Emscripten was not up to date, and updating it to latest I started getting mouse wheel values closer to 1.0f (with still values such as 3.75f on some browser).
I have therefore reverted the GLFW magnitude scale 0370856 and flipping 624c057 and removed the SDL clamping f822e07 but I still expect we should clarify how the HTML/JS shell alters those values and make it right for all users.

At some point emscripten repo changed from using master to main and I didn't notice, so I wasn't up to date.

I am currently working on refactoring all examples in the codebase to use a MainLoopStep() function, in order to make it possible to trivially make examples be changed to compile with Emscripten. This is so we can delete the dedicated "emscripten examples" folders. (While not all examples are expected to compile on Emscripten, we try to keep all examples as close as possible to each others to ease maintenance). Once this is done our GLFW example will also work with Emscripten (until now it didn't) and we can use shared HTML/JS templates.

ocornut added a commit that referenced this pull request Feb 2, 2023
… data for smooth scrolling + scaling for Emscripten. (#4019, #6096)

+ Missing changelog entries.
@ocornut
Copy link
Owner

ocornut commented Feb 2, 2023

FYI : I've pushed the example refactors mentioned as 96ab68e and ce6e6da making the SDL+GL and GLFW+GL examples also build and run with Emscripten, without the need for a dedicated Emscripten example. This is effectively adding the "missing" GLFW+Emscripten example that doesn't rely on WebGPU. Apologies for the yak-shaving/spam.

TL;DR;

  • With latest Emscripten I don't see a need for the Emscripten-specific flipping.
  • But please confirm on your side (make sure your copy of Emscripten points to main and not master if you have an old working copy).
  • The wheel values are still in need of some amount of rescaling. I suspect they are bugs in Emscripten emulation that needs to be addressed there.
  • We now have a GLFW+Emscripten example we can play with next to the SDL one.

SDL

  • Recent Emscripten added support for preciseX/preciseY fields so I am now using that.
  • Stepping mices X and Touchpad X values I am dividing by arbitrary 100.0f and that seems good-ish. I don't know if other factors would interact with it.
  • Stepping mices Y scroll values are more or less good but feels a bit jerky.
  • Touchpad swipe Y scroll values are more or less good but feels a bit jerky.
    I am not sure what SDL or GLFW does here, the smooth scrolling feels better here in native.

GLFW

  • Stepping mices X and Touchpad X values I am dividing by 100.0f and that seems good-ish.
  • Stepping mices Y scroll values seems good.
  • Touchpad swipe Y scroll values still accumulate to too much.
  • I don't know how to differentiate those with data we get from GLFW. In other words for GLFW things seems worse with latest than with the version I had before.

I think we should:

  • Get back to confirming raw values with your test-bed, ensuring it is built with latest Emscripten.
  • Depending what they are, either push for fixes on Emscripten API side, or fixes on Emscripten GLFW emulation side.
  • Either way we should get this raised on Emscripten side.

OR

  • We simply try to use emscripten.h in those backends.

@ocornut
Copy link
Owner

ocornut commented Feb 2, 2023

We simply try to use emscripten.h in those backends.

I'll work toward have our CPP backend simply use emscripten.h raw events, it seems like the sane way out.

Zooming affects the DOM_DELTA_PIXEL values in ways I can't comprehend right now so it's going to be potentially weird, but I suppose container HTML could restrict that.
We'll need a way to pass those values with semantic to imgui and let it convert them (so AddMouseWheelEvent() would accept more semantic via an enum).

@wolfpld
Copy link
Contributor

wolfpld commented Feb 2, 2023

I think the values the browser is producing are better explained here: https://www.w3.org/TR/uievents/#events-wheelevents

A user’s environment settings can be customized to interpret actual rotation/movement of a wheel device in different ways. One movement of a common "dented" mouse wheel can produce a measurement of 162 pixels (162 is just an example value, actual values can depend on the current screen dimensions of the user-agent). But a user can change their default environment settings to speed-up their mouse wheel, increasing this number. Furthermore, some mouse wheel software can support acceleration (the faster the wheel is rotated/moved, the greater the delta of each measurement) or even sub-pixel rotation measurements. Because of this, authors can not assume a given rotation amount in one user agent will produce the same delta value in all user agents.

The exact coordinate system of axis is also specified:

If a user agent scrolls as the default action of the wheel event then the sign of the delta SHOULD be given by a right-hand coordinate system where positive X, Y, and Z axes are directed towards the right-most edge, bottom-most edge, and farthest depth (away from the user) of the document, respectively.

Emscripten has code to fix this for browsers that are not compliant with the DOM Level 3 wheel event.

https://github.com/emscripten-core/emscripten/blob/c4f7a55347044af6ae29c73cec6e054cc3d83d97/src/library_html5.js#L639-L672

I wouldn't expect there to be any processing of the raw values returned by the browser, so I don't think it's an emscripten bug.

For the SDL implementation, refer to the source code I have linked before.

https://github.com/libsdl-org/SDL/blob/22c69bccdf18d6c5c0990de9bc89e5e76d3a7c50/src/video/emscripten/SDL_emscriptenevents.c#L699-L719

The adjustments there are somebody's random guess at what would feel fine, I guess. See emscripten-ports/SDL2#149 and the Rube Goldberg machine of referenced wheel issues.

As for GLFW, I was wondering where the implementation is, as there is no emscripten-specific code in the GLFW repo. It seems that emscripten has a pure javascript implementation of the GLFW API.

https://github.com/emscripten-core/emscripten/blob/main/src/library_glfw.js

I'll work toward have our CPP backend simply use emscripten.h raw events, it seems like the sane way out.

FWIW, emscripten's html5.h API is very nice to work with. You need to use OpenAL for sound and EGL to setup OpenGL, but you remove all that intermediate layer of SDL/GLFW code, which may be doing unexpected things, as is evident in this issue.

@ocornut
Copy link
Owner

ocornut commented Feb 2, 2023

Thanks for your help and patience with this, this is clearly not my comfort zone (both Emscripten and doing anything on a Mac).

I wired Emscripten raw calls into our GLFW backend, and could visualize the difference.

I think the bug in the GLFW emulation JS code is:

https://github.com/emscripten-core/emscripten/blob/main/src/library_glfw.js#L518

delta = (delta == 0) ? 0 : (delta > 0 ? Math.max(delta, 1) : Math.min(delta, -1)); // Quantize to integer so that minimum scroll is at least +/- 1.

This quantization is causing my issue that I can't get good values for both touchpad and stepping mouse, it makes small movement on the later end up having too much effect, as slow wheeling on a stepping mouse will emit series of -1/+1.

I think I can now workaround using emscripten_set_wheel_callback() and applying a factor of -100.0f for both axises.

@ocornut
Copy link
Owner

ocornut commented Feb 2, 2023

Here's a tentative fix for the magnitude GLFW backend:
4205f36

Could you cherry-pick (after updating) and see if things works for you?
(Please confirm if Win/OS and Stepping Mouse Wheel vs Smooth Mouse Wheel vs Touchpad)

@wolfpld
Copy link
Contributor

wolfpld commented Feb 2, 2023

Firefox on Linux + touchpad is ok.
Chromium on Linux + touchpad feels too fast, but is manageable. I don't use nor care about Chrome.
Firefox on Windows + touchpad feels too slow, but is in line with how touchpad seems to behave in other web-native places that can be scrolled. I don't use Windows with touchpad, so it's hard to say for me if that's to be expected.
Firefox on MacOS is simultaneously emitting horizontal and vertical scroll events, which makes interaction with Tracy unusable, as you are zooming when you want to pan. I don't care about MacOS.

I can't comment on how mouse wheel behaves, as I don't have handling of smooth vertical wheel input. Impulses (be it from the wheel, or the touchpad) work as expected.

The build I was testing on is available at https://tracy.nereid.pl/

@wolfpld
Copy link
Contributor

wolfpld commented Feb 3, 2023

Firefox on MacOS is simultaneously emitting horizontal and vertical scroll events, which makes interaction with Tracy unusable, as you are zooming when you want to pan.

To give more context for this, the reason is that I treat zoom in and zoom out events as impulses, as discussed earlier. I want the zoom to be animated so that the UI feels smooth. Touchpad control of the zoom requires mapping the touchpad finger motion directly to the zoom action, but this requires the touch start/end events.

Emitting events on both axes is still a bad idea from a UX point of view. There should be some dead zone to allow the user to perform vertical-only or horizontal-only scrolling. Windows and Linux get this, MacOS apparently doesn't.

@ocornut
Copy link
Owner

ocornut commented Feb 3, 2023

Mac OS emit dual-axis events for the first few events then seem to lock an axis. We use that and a heuristic to make our scrolling code select and lock a single axis. Are you polling wheel data or talking about scrolling here?
If polling wheel data, perhaps we could expose our "main axis" heuristic.

@wolfpld
Copy link
Contributor

wolfpld commented Feb 3, 2023

I handle dx and dy separately. I think both actions should be available to the user at the same time, if they want to, and this works reasonably well on Linux. On Mac you have to fight against this.

If polling wheel data, perhaps we could expose our "main axis" heuristic.

On the principle, I would be against fixing this in client code.

ocornut added a commit that referenced this pull request Feb 3, 2023
…et more accurate scrolling impulses on Emscripten. (#4019, #6096)

Namely, GLFW JS emulation seems to quantize values to a min of -1/+1 which breaks modern OSX/Windows emulating smoothness with stepping wheels (slow steps are sending sub-1.0 values)
+ Massage changelog.
@ocornut
Copy link
Owner

ocornut commented Feb 3, 2023

I have pushed the GLFW backend change as d0b1aaa.
AFAIK we can close this issue although there's lots of good info in this thread. We can refer to it in new possible topics (e.g. touch improvements, Emscripten improvements) but if thread gets too unwieldy it gets harder to make progress.

Most OSX apps are using "main axis" heuristic for scrolling. It is useful and sometimes required when you have nested view (e.g. child can scroll in one direction, parent in the other direction). This is what was addressed in #3795.

However we never alter io.MouseWheel data, we apply the heuristic in our scrolling code in UpdateMouseWheel(), and we could provide the result of our heuristic for some custom widgets to use. We record g.WheelingAxisAvg.x/y when there is a tie we defer the decision to next frame. In principle you may use that to initiate an axis lock to select zooming vs panning, but I don't know if the logic we use to reset this average will work with or against you as that logic is currently tied to scrolling.

@topolarity
Copy link
Author

Thanks for your thorough investigation @ocornut and @wolfpld

I believe the original issue is resolved, so I'm going to close this.

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

Successfully merging this pull request may close these issues.

4 participants