From bb3501a4f9cba0d634c7f9e5908fbc605b3e0370 Mon Sep 17 00:00:00 2001 From: Amirhosein Akhlaghpoor Date: Sat, 28 Feb 2026 02:56:25 +0000 Subject: [PATCH] ui: scale wheel lines on Windows/Linux to Mac (#14395) * input: accelerate wheel bursts on Windows->Mac - boost fast wheel bursts without affecting single-step scrolls\n- use dominant-axis smooth detection and velocity gate\n- reset wheel timestamp on enter/leave\n- enforce single-axis scrolling\n- extract/tune Sciter wheel accel thresholds Signed-off-by: Amirhossein Akhlaghpour * input: clarify wheel burst tuning - add comments on acceleration rules and units\n- apply burst accel on Windows/Linux to macOS\n- reset wheel timing on enter/leave Signed-off-by: Amirhossein Akhlaghpour * input: align wheel burst velocity thresholds - match Flutter velocity gate with Sciter Signed-off-by: Amirhossein Akhlaghpour * input: restore flutter wheel velocity threshold - keep burst threshold at 0.002 delta/us Signed-off-by: Amirhossein Akhlaghpour --------- Signed-off-by: Amirhossein Akhlaghpour --- flutter/lib/models/input_model.dart | 50 +++++++++++++++++++++++++---- src/ui/remote.tis | 40 ++++++++++++++++++----- 2 files changed, 76 insertions(+), 14 deletions(-) diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index 134b21107..628b27fb2 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -365,6 +365,16 @@ class InputModel { final isPhysicalMouse = false.obs; int _lastButtons = 0; Offset lastMousePos = Offset.zero; + int _lastWheelTsUs = 0; + + // Wheel acceleration thresholds. + static const int _wheelAccelFastThresholdUs = 40000; // 40ms + static const int _wheelAccelMediumThresholdUs = 80000; // 80ms + static const double _wheelBurstVelocityThreshold = + 0.002; // delta units per microsecond + // Wheel burst acceleration (empirical tuning). + // Applies only to fast, non-smooth bursts to preserve single-step scrolling. + // Flutter uses microseconds for dt, so velocity is in delta/us. // Relative mouse mode (for games/3D apps). final relativeMouseMode = false.obs; @@ -964,6 +974,7 @@ class InputModel { toReleaseRawKeys.release(handleRawKeyEvent); _pointerMovedAfterEnter = false; _pointerInsideImage = enter; + _lastWheelTsUs = 0; // Fix status if (!enter) { @@ -1407,17 +1418,44 @@ class InputModel { if (isViewOnly) return; if (isViewCamera) return; if (e is PointerScrollEvent) { - var dx = e.scrollDelta.dx.toInt(); - var dy = e.scrollDelta.dy.toInt(); + final rawDx = e.scrollDelta.dx; + final rawDy = e.scrollDelta.dy; + final dominantDelta = rawDx.abs() > rawDy.abs() ? rawDx.abs() : rawDy.abs(); + final isSmooth = dominantDelta < 1; + final nowUs = DateTime.now().microsecondsSinceEpoch; + final dtUs = _lastWheelTsUs == 0 ? 0 : nowUs - _lastWheelTsUs; + _lastWheelTsUs = nowUs; + int accel = 1; + if (!isSmooth && + dtUs > 0 && + dtUs <= _wheelAccelMediumThresholdUs && + (isWindows || isLinux) && + peerPlatform == kPeerPlatformMacOS) { + final velocity = dominantDelta / dtUs; + if (velocity >= _wheelBurstVelocityThreshold) { + if (dtUs < _wheelAccelFastThresholdUs) { + accel = 3; + } else { + accel = 2; + } + } + } + var dx = rawDx.toInt(); + var dy = rawDy.toInt(); + if (rawDx.abs() > rawDy.abs()) { + dy = 0; + } else { + dx = 0; + } if (dx > 0) { - dx = -1; + dx = -accel; } else if (dx < 0) { - dx = 1; + dx = accel; } if (dy > 0) { - dy = -1; + dy = -accel; } else if (dy < 0) { - dy = 1; + dy = accel; } bind.sessionSendMouse( sessionId: sessionId, diff --git a/src/ui/remote.tis b/src/ui/remote.tis index 0dd574af7..7602432fe 100644 --- a/src/ui/remote.tis +++ b/src/ui/remote.tis @@ -142,6 +142,14 @@ function resetWheel() { } var INERTIA_ACCELERATION = 30; +var WHEEL_ACCEL_VELOCITY_THRESHOLD = 5000; +var WHEEL_ACCEL_DT_FAST = 0.04; +var WHEEL_ACCEL_DT_MEDIUM = 0.08; +var WHEEL_ACCEL_VALUE_FAST = 3; +var WHEEL_ACCEL_VALUE_MEDIUM = 2; +// Wheel burst acceleration (empirical tuning). +// Applies only on fast, non-smooth wheel bursts to keep single-step scroll unchanged. +// Sciter uses seconds for dt, so velocity is in delta/sec. // not good, precision not enough to simulate acceleration effect, // seems have to use pixel based rather line based delta @@ -237,12 +245,28 @@ function handler.onMouse(evt) // mouseWheelDistance = 8 * [currentUserDefs floatForKey:@"com.apple.scrollwheel.scaling"]; mask = 3; { - var (dx, dy) = evt.wheelDeltas; - if (dx > 0) dx = 1; - else if (dx < 0) dx = -1; - if (dy > 0) dy = 1; - else if (dy < 0) dy = -1; - if (Math.abs(dx) > Math.abs(dy)) { + var now = getTime(); + var dt = last_wheel_time > 0 ? (now - last_wheel_time) / 1000 : 0; + var (raw_dx, raw_dy) = evt.wheelDeltas; + var dx = 0; + var dy = 0; + var abs_dx = Math.abs(raw_dx); + var abs_dy = Math.abs(raw_dy); + var dominant = abs_dx > abs_dy ? abs_dx : abs_dy; + var is_smooth = dominant < 1; + var accel = 1; + if (!is_smooth && dt > 0 && (is_win || is_linux) && get_peer_platform() == "Mac OS") { + var velocity = dominant / dt; + if (velocity >= WHEEL_ACCEL_VELOCITY_THRESHOLD) { + if (dt < WHEEL_ACCEL_DT_FAST) accel = WHEEL_ACCEL_VALUE_FAST; + else if (dt < WHEEL_ACCEL_DT_MEDIUM) accel = WHEEL_ACCEL_VALUE_MEDIUM; + } + } + if (raw_dx > 0) dx = accel; + else if (raw_dx < 0) dx = -accel; + if (raw_dy > 0) dy = accel; + else if (raw_dy < 0) dy = -accel; + if (abs_dx > abs_dy) { dy = 0; } else { dx = 0; @@ -253,8 +277,6 @@ function handler.onMouse(evt) wheel_delta_y = acc_wheel_delta_y.toInteger(); acc_wheel_delta_x -= wheel_delta_x; acc_wheel_delta_y -= wheel_delta_y; - var now = getTime(); - var dt = last_wheel_time > 0 ? (now - last_wheel_time) / 1000 : 0; if (dt > 0) { var vx = dx / dt; var vy = dy / dt; @@ -297,11 +319,13 @@ function handler.onMouse(evt) entered = true; stdout.println("enter"); handler.enter(handler.get_keyboard_mode()); + last_wheel_time = 0; return keyboard_enabled; case Event.MOUSE_LEAVE: entered = false; stdout.println("leave"); handler.leave(handler.get_keyboard_mode()); + last_wheel_time = 0; if (is_left_down && get_peer_platform() == "Android") { is_left_down = false; handler.send_mouse((1 << 3) | 2, 0, 0, evt.altKey,