feat(input): wire native mouse-wheel scroll on Wayland + Win32, normalize all backends to ±1/detent (#32)
Window::onMouseScroll now fires on every backend and speaks one unit: signed whole detents packed in the uint32 payload, +1 = wheel down (DOM deltaY sign). - Wayland: implement the previously-stubbed PointerListenerHandleAxis — vertical axis only, wl_fixed_to_double / 15 (libinput's units-per- detent), sub-detent remainder accumulated and reset on pointer leave. - Win32: add the missing WM_MOUSEWHEEL case — -GET_WHEEL_DELTA_WPARAM / WHEEL_DELTA with a remainder accumulator for free-spinning wheels. - DOM: dom-env.js normalizes WheelEvent.deltaY per deltaMode (100px / 3 lines / 1 page per detent) with the same remainder scheme, so wasm delivers the identical unit instead of raw browser-specific deltas. - Document the contract on Window::onMouseScroll. - tests/MouseScroll: compositor-free regression test driving the Wayland axis handler directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c608d75a36
commit
419730f46b
8 changed files with 240 additions and 8 deletions
|
|
@ -269,10 +269,31 @@ void Device::PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, std:
|
|||
void Device::PointerListenerHandleLeave(void* data, wl_pointer*, std::uint32_t, wl_surface*) {
|
||||
Device::focusedWindow->onMouseLeave.Invoke();
|
||||
focusedWindow = nullptr;
|
||||
// Drop any sub-detent scroll remainder so it can't leak into whatever
|
||||
// window the pointer enters next.
|
||||
scrollDetentRemainder = 0.0;
|
||||
}
|
||||
|
||||
void Device::PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t, std::uint32_t, wl_fixed_t value) {
|
||||
|
||||
void Device::PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t, std::uint32_t axis, wl_fixed_t value) {
|
||||
// Vertical wheel only — Window has no horizontal-scroll event (yet).
|
||||
if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL || focusedWindow == nullptr) {
|
||||
return;
|
||||
}
|
||||
// libinput convention (followed by wlroots, Mutter and KWin): one wheel
|
||||
// detent = 15 axis units, positive = wheel down / toward the user — the
|
||||
// same sign as DOM deltaY. Window::onMouseScroll speaks whole detents
|
||||
// (±1 per notch on every backend), so divide and accumulate the
|
||||
// remainder: smooth-scroll devices (touchpads) deliver many sub-detent
|
||||
// events that must add up instead of each truncating to zero.
|
||||
constexpr double axisUnitsPerDetent = 15.0;
|
||||
scrollDetentRemainder += wl_fixed_to_double(value) / axisUnitsPerDetent;
|
||||
const double whole = std::trunc(scrollDetentRemainder);
|
||||
if (whole != 0.0) {
|
||||
scrollDetentRemainder -= whole;
|
||||
// Event payload is uint32 — preserve sign via two's complement.
|
||||
focusedWindow->onMouseScroll.Invoke(
|
||||
static_cast<std::uint32_t>(static_cast<std::int32_t>(whole)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -258,8 +258,9 @@ void Map::Attach(Window& w) {
|
|||
scrollListener = std::make_unique<EventListener<std::uint32_t>>(
|
||||
&w.onMouseScroll,
|
||||
[this](std::uint32_t delta) {
|
||||
// Wayland and Win32 both deliver scroll as a signed delta
|
||||
// packed into a uint32; we reinterpret to preserve sign.
|
||||
// Every backend (DOM, Wayland, Win32) delivers scroll as
|
||||
// signed whole detents (+1 = wheel down) packed into a
|
||||
// uint32; we reinterpret to preserve sign.
|
||||
scrollAccumulator += (std::int32_t)delta;
|
||||
});
|
||||
lastMousePos.reset();
|
||||
|
|
|
|||
|
|
@ -257,6 +257,25 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
|||
break;
|
||||
}
|
||||
|
||||
case WM_MOUSEWHEEL: {
|
||||
// High word of wParam is the signed wheel movement, WHEEL_DELTA
|
||||
// (120) per detent, positive = wheel away from the user (scroll
|
||||
// up). Window::onMouseScroll speaks whole detents with +1 =
|
||||
// wheel down (the DOM deltaY / Wayland axis sign), so negate.
|
||||
// Free-spinning wheels report sub-WHEEL_DELTA steps — accumulate
|
||||
// so they add up instead of each truncating to zero.
|
||||
static int wheelRemainder = 0;
|
||||
wheelRemainder += -static_cast<int>(GET_WHEEL_DELTA_WPARAM(wParam));
|
||||
int detents = wheelRemainder / WHEEL_DELTA; // trunc toward zero
|
||||
if (detents != 0) {
|
||||
wheelRemainder -= detents * WHEEL_DELTA;
|
||||
// Event payload is uint32 — preserve sign via two's complement.
|
||||
window->onMouseScroll.Invoke(
|
||||
static_cast<std::uint32_t>(static_cast<std::int32_t>(detents)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_SETCURSOR: {
|
||||
if (LOWORD(lParam) == HTCLIENT && window->cursorHandle) {
|
||||
SetCursor(window->cursorHandle);
|
||||
|
|
@ -1458,10 +1477,12 @@ extern "C" {
|
|||
}
|
||||
|
||||
__attribute__((export_name("__crafterDom_wheel")))
|
||||
void __crafterDom_wheel(std::int32_t /*handle*/, double deltaY) {
|
||||
void __crafterDom_wheel(std::int32_t /*handle*/, double detents) {
|
||||
if (!g_domWindow) return;
|
||||
// dom-env.js has already normalized WheelEvent deltas to whole
|
||||
// detents (+1 = wheel down) — see __windowListeners.wheel.
|
||||
// Window::onMouseScroll is uint32 — preserve sign via two's complement.
|
||||
g_domWindow->onMouseScroll.Invoke(static_cast<std::uint32_t>(static_cast<std::int32_t>(deltaY)));
|
||||
g_domWindow->onMouseScroll.Invoke(static_cast<std::uint32_t>(static_cast<std::int32_t>(detents)));
|
||||
}
|
||||
|
||||
__attribute__((export_name("__crafterDom_keyDown")))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue