simplify touchpad

This commit is contained in:
Vaclav Tvrdik
2026-09-08 21:26:39 +02:00
parent 68dbde957d
commit d7aebda782
3 changed files with 27 additions and 26 deletions

View File

@@ -1,6 +1,4 @@
""" SGR mouse reporting while the app tracks the mouse; otherwise sublime keeps drag_select """ """ SGR mouse reporting while the app tracks the mouse; otherwise sublime keeps drag_select """
import math
import sublime import sublime
import sublime_plugin import sublime_plugin
@@ -115,18 +113,17 @@ class VtermScrollCommand(sublime_plugin.TextCommand):
return True return True
def ensure_trackpad(terminal): def start_trackpad(terminal):
"""Start the poller that turns trackpad scrolling into wheel lines. """Start the poller that turns trackpad scrolling into wheel lines.
The trackpad never fires the mousemap scroll bindings on macOS The trackpad never fires the mousemap scroll bindings on macOS
(sublime_text#1838); its only trace is viewport movement. While the app (sublime_text#1838); its only trace is viewport movement. While the app
owns the wheel the mousemap keeps the physical wheel off the viewport, so owns the wheel the mousemap keeps the physical wheel off the viewport, so
any displacement from the follow anchor is trackpad scroll: harvest whole any displacement from the follow anchor is trackpad scroll: harvest whole
lines from it, send them like wheel lines, and snap the viewport back.""" lines from it, send them like wheel lines, and snap the viewport back.
if terminal._trackpad_running:
return The caller checks _trackpad_running; the first tick arms the baseline."""
terminal._trackpad_running = True terminal._trackpad_running = True
terminal._trackpad_acc = 0.0
terminal._trackpad_engaged = True terminal._trackpad_engaged = True
terminal._trackpad_baseline = None terminal._trackpad_baseline = None
sublime.set_timeout(lambda: _trackpad_tick(terminal), TRACKPAD_POLL_MS) sublime.set_timeout(lambda: _trackpad_tick(terminal), TRACKPAD_POLL_MS)
@@ -136,7 +133,7 @@ def _trackpad_tick(terminal):
"""One poll: harvest viewport displacement, reschedule while captured.""" """One poll: harvest viewport displacement, reschedule while captured."""
view = terminal.view view = terminal.view
if Terminal.from_id(view.id()) is not terminal or not terminal.alive \ if Terminal.from_id(view.id()) is not terminal or not terminal.alive \
or not terminal._wheel_captured: or not terminal._wheel_captured or not terminal.is_visible():
terminal._trackpad_running = False terminal._trackpad_running = False
return return
anchor = terminal.viewport_y anchor = terminal.viewport_y
@@ -146,13 +143,13 @@ def _trackpad_tick(terminal):
# around the new position instead of reading the move as a gesture # around the new position instead of reading the move as a gesture
terminal._trackpad_baseline = (anchor, extent) terminal._trackpad_baseline = (anchor, extent)
terminal._trackpad_acc = 0.0 terminal._trackpad_acc = 0.0
else: elif not terminal._trackpad_engaged:
dy = view.viewport_position()[1] - anchor
if not terminal._trackpad_engaged:
if following(view, terminal): if following(view, terminal):
terminal._trackpad_engaged = True terminal._trackpad_engaged = True
terminal._trackpad_acc = 0.0 terminal._trackpad_acc = 0.0
elif dy == 0: else:
dy = view.viewport_position()[1] - anchor
if dy == 0:
# at rest a stale fraction must not fire on the next gesture # at rest a stale fraction must not fire on the next gesture
terminal._trackpad_acc = 0.0 terminal._trackpad_acc = 0.0
elif abs(dy) >= extent: elif abs(dy) >= extent:
@@ -161,7 +158,7 @@ def _trackpad_tick(terminal):
terminal._trackpad_engaged = False terminal._trackpad_engaged = False
else: else:
terminal._trackpad_acc += dy / view.line_height() terminal._trackpad_acc += dy / view.line_height()
lines = math.trunc(terminal._trackpad_acc) lines = int(terminal._trackpad_acc)
terminal._trackpad_acc -= lines terminal._trackpad_acc -= lines
if lines: if lines:
send_scroll(terminal, up=lines < 0, lines=abs(lines)) send_scroll(terminal, up=lines < 0, lines=abs(lines))

View File

@@ -172,7 +172,7 @@ class VtermRenderCommand(sublime_plugin.TextCommand):
terminal.pending_clear_screen = False terminal.pending_clear_screen = False
self.clear_view(edit, terminal, wipe_screen=True) self.clear_view(edit, terminal, wipe_screen=True)
if view.size() == 0 and terminal.offset == 0: if terminal.offset == 0 and view.size() == 0:
# one blank scrollback row above a fresh screen: sublime has no # one blank scrollback row above a fresh screen: sublime has no
# layout above row 0, and the trackpad poller needs upward slack — # layout above row 0, and the trackpad poller needs upward slack —
# without it, trackpad-up is dead until a line retires, which on # without it, trackpad-up is dead until a line retires, which on
@@ -217,15 +217,15 @@ class VtermRenderCommand(sublime_plugin.TextCommand):
alt = terminal.screen.alternate_buffer_mode alt = terminal.screen.alternate_buffer_mode
if not alt and not terminal.applied_alt_screen: if not alt and not terminal.applied_alt_screen:
return return
entering = alt and not terminal.applied_alt_screen
terminal.applied_alt_screen = alt terminal.applied_alt_screen = alt
end = view.text_point(terminal.offset, 0) - 1 if alt and terminal.offset else 0 end = view.text_point(terminal.offset, 0) - 1 if alt and terminal.offset else 0
want = [(0, end)] if end > 0 else [] want = [(0, end)] if end > 0 else []
had = [(r.begin(), r.end()) for r in view.folded_regions()] if [(r.begin(), r.end()) for r in view.folded_regions()] == want:
if had == want:
return return
view.unfold(sublime.Region(0, view.size())) view.unfold(sublime.Region(0, view.size()))
if want and view.fold(sublime.Region(0, end)) and not had: if want and view.fold(sublime.Region(0, end)) and entering:
# a fresh fold collapses the layout; a stale viewport anchor would # entering collapses the layout; a stale viewport anchor would
# stop the follow-scroll gate from re-engaging. re-asserting a # stop the follow-scroll gate from re-engaging. re-asserting a
# disturbed fold keeps the anchor: apps repaint the boundary row # disturbed fold keeps the anchor: apps repaint the boundary row
# every scroll, and resetting per repair thrashes the poller # every scroll, and resetting per repair thrashes the poller
@@ -545,7 +545,7 @@ def scroll_to_cursor(view, terminal):
"""Follow the output: pin the viewport to the bottom of the live screen.""" """Follow the output: pin the viewport to the bottom of the live screen."""
terminal._scroll_pending = False terminal._scroll_pending = False
y = bottom_y(view, terminal) y = bottom_y(view, terminal)
if y == terminal.viewport_y and terminal._trackpad_running and terminal._trackpad_engaged: if y == terminal.viewport_y and terminal.trackpad_pinning():
# the anchor is unchanged and the trackpad poller owns the pin: # the anchor is unchanged and the trackpad poller owns the pin:
# re-pinning here would discard displacement it has not harvested yet, # re-pinning here would discard displacement it has not harvested yet,
# and apps repaint while scrolling, so this races every gesture # and apps repaint while scrolling, so this races every gesture

View File

@@ -109,7 +109,7 @@ class Terminal:
self._send_lock = threading.Lock() self._send_lock = threading.Lock()
self._wheel_captured = False self._wheel_captured = False
self._scroll_pending = False self._scroll_pending = False
# trackpad poller (mouse.ensure_trackpad): reschedule guard, fractional # trackpad poller (mouse.start_trackpad): reschedule guard, fractional
# line remainder, harvesting vs. browsing, last seen (anchor, extent) # line remainder, harvesting vs. browsing, last seen (anchor, extent)
self._trackpad_running = False self._trackpad_running = False
self._trackpad_acc = 0.0 self._trackpad_acc = 0.0
@@ -152,6 +152,10 @@ class Terminal:
self._visible = group >= 0 and window.active_view_in_group(group) == self.view self._visible = group >= 0 and window.active_view_in_group(group) == self.view
return self._visible return self._visible
def trackpad_pinning(self):
"""Whether the trackpad poller owns pinning the viewport to the anchor."""
return self._trackpad_running and self._trackpad_engaged
def is_render_due(self): def is_render_due(self):
"""Whether an unfocused terminal may draw again. """Whether an unfocused terminal may draw again.
@@ -398,11 +402,11 @@ class Terminal:
if captured != self._wheel_captured: if captured != self._wheel_captured:
self._wheel_captured = captured self._wheel_captured = captured
self.view.settings().set("vterm_view.wheel_captured", captured) self.view.settings().set("vterm_view.wheel_captured", captured)
if captured and not self._trackpad_running: if captured and not self._trackpad_running and self.is_visible():
# checked every renderer tick, so a poller that just stopped on a # checked every renderer tick, so a poller that stopped on a
# capture flap is restarted within a frame or two # capture flap or a hidden tab restarts within a frame or two
from .mouse import ensure_trackpad # module-level import would cycle from .mouse import start_trackpad # module-level import would cycle
ensure_trackpad(self) start_trackpad(self)
def send_focus(self, focused): def send_focus(self, focused):
"""Report focus gain or loss, when the app asked for it (?1004).""" """Report focus gain or loss, when the app asked for it (?1004)."""