From 44bcbc48232a7fbdd0d1c87f05fcf8f5f159081b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 28 Sep 2021 20:12:55 +0530 Subject: [PATCH] Make it easier to handle simple click events --- kittens/mouse_demo/main.py | 6 ++++- kittens/tui/handler.py | 46 ++++++++++++++++++++++++++++++++++---- kittens/tui/loop.py | 8 +++---- kitty/typing.py | 3 +-- kitty/typing.pyi | 10 ++++----- 5 files changed, 56 insertions(+), 17 deletions(-) diff --git a/kittens/mouse_demo/main.py b/kittens/mouse_demo/main.py index 870433319..79580d0ae 100644 --- a/kittens/mouse_demo/main.py +++ b/kittens/mouse_demo/main.py @@ -24,7 +24,7 @@ class Mouse(Handler): def finalize(self) -> None: self.cmd.set_cursor_visible(True) - def on_mouse(self, ev: MouseEvent) -> None: + def on_mouse_event(self, ev: MouseEvent) -> None: self.current_mouse_event = ev self.draw_screen() @@ -43,6 +43,10 @@ class Mouse(Handler): if ev.mods: self.print(f'Modifiers: {format_mods(ev.mods)}') + def on_interrupt(self) -> None: + self.quit_loop(0) + on_eot = on_interrupt + def main(args: List[str]) -> None: loop = Loop() diff --git a/kittens/tui/handler.py b/kittens/tui/handler.py index d31678cb7..51ce42f7f 100644 --- a/kittens/tui/handler.py +++ b/kittens/tui/handler.py @@ -3,16 +3,19 @@ # License: GPL v3 Copyright: 2018, Kovid Goyal +from collections import deque +from time import monotonic from types import TracebackType from typing import ( - TYPE_CHECKING, Any, Callable, ContextManager, Dict, Optional, Sequence, - Type, Union, cast + TYPE_CHECKING, Any, Callable, ContextManager, Deque, Dict, NamedTuple, + Optional, Sequence, Type, Union, cast ) from kitty.types import DecoratedFunc, ParsedShortcut from kitty.typing import ( AbstractEventLoop, BossType, Debug, ImageManagerType, KeyActionType, - KeyEventType, LoopType, MouseEvent, ScreenSize, TermManagerType + KeyEventType, LoopType, MouseButton, MouseEvent, ScreenSize, + TermManagerType ) from .operations import MouseTracking, pending_update @@ -21,6 +24,20 @@ if TYPE_CHECKING: from kitty.file_transmission import FileTransmissionCommand +class ButtonEvent(NamedTuple): + mouse_event: MouseEvent + timestamp: float + + +def is_click(a: ButtonEvent, b: ButtonEvent) -> bool: + from .loop import EventType + if a.mouse_event.type is not EventType.PRESS or b.mouse_event.type is not EventType.RELEASE: + return False + x = a.mouse_event.cell_x - b.mouse_event.cell_x + y = a.mouse_event.cell_y - b.mouse_event.cell_y + return x*x + y*y <= 4 + + class Handler: image_manager_class: Optional[Type[ImageManagerType]] = None @@ -44,6 +61,7 @@ class Handler: self.debug = debug self.cmd = commander(self) self._image_manager = image_manager + self._button_events: Dict[MouseButton, Deque[ButtonEvent]] = {} @property def image_manager(self) -> ImageManagerType: @@ -106,7 +124,27 @@ class Handler: def on_key(self, key_event: KeyEventType) -> None: pass - def on_mouse(self, mouse_event: MouseEvent) -> None: + def on_mouse_event(self, mouse_event: MouseEvent) -> None: + from .loop import EventType + if mouse_event.type is EventType.MOVE: + self.on_mouse_move(mouse_event) + elif mouse_event.type is EventType.PRESS: + q = self._button_events.setdefault(mouse_event.buttons, deque()) + q.append(ButtonEvent(mouse_event, monotonic())) + if len(q) > 5: + q.popleft() + elif mouse_event.type is EventType.RELEASE: + q = self._button_events.setdefault(mouse_event.buttons, deque()) + q.append(ButtonEvent(mouse_event, monotonic())) + if len(q) > 5: + q.popleft() + if len(q) > 1 and is_click(q[-2], q[-1]): + self.on_click(mouse_event) + + def on_mouse_move(self, mouse_event: MouseEvent) -> None: + pass + + def on_click(self, mouse_event: MouseEvent) -> None: pass def on_interrupt(self) -> None: diff --git a/kittens/tui/loop.py b/kittens/tui/loop.py index 9b0c216a5..dff673602 100644 --- a/kittens/tui/loop.py +++ b/kittens/tui/loop.py @@ -109,7 +109,7 @@ class TermManager: class MouseButton(IntFlag): - LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 1, 2, 4, 8, 16 + NONE, LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 0, 1, 2, 4, 8, 16 bmap = {0: MouseButton.LEFT, 1: MouseButton.MIDDLE, 2: MouseButton.RIGHT} @@ -132,7 +132,7 @@ class MouseEvent(NamedTuple): pixel_x: int pixel_y: int type: EventType - buttons: int + buttons: MouseButton mods: int @@ -146,7 +146,7 @@ def decode_sgr_mouse(text: str, screen_size: ScreenSize) -> MouseEvent: m, y_ = y_[-1], y_[:-1] cb, x, y = map(int, (cb_, x_, y_)) typ = EventType.RELEASE if m == 'm' else (EventType.MOVE if cb & MOTION_INDICATOR else EventType.PRESS) - buttons = 0 + buttons: MouseButton = MouseButton.NONE cb3 = cb & 3 if cb3 != 3: if cb & EXTRA_BUTTON_INDICATOR: @@ -310,7 +310,7 @@ class Loop: except Exception: pass else: - self.handler.on_mouse(ev) + self.handler.on_mouse_event(ev) elif q in 'u~ABCDEHFPQRS': if csi == '200~': self.in_bracketed_paste = True diff --git a/kitty/typing.py b/kitty/typing.py index 62db96ba2..48fb9b3da 100644 --- a/kitty/typing.py +++ b/kitty/typing.py @@ -12,7 +12,7 @@ SessionTab = SessionType = LayoutType = SpecialWindowInstance = None MarkType = RemoteCommandType = CoreTextFont = FontConfigPattern = None KeyEventType = ImageManagerType = KittyCommonOpts = HandlerType = None GRT_t = GRT_a = GRT_d = GRT_f = GRT_m = GRT_o = GRT_C = None -ScreenSize = KittensKeyActionType = MouseEvent = AbstractEventLoop = None +ScreenSize = KittensKeyActionType = MouseEvent = MouseButton = AbstractEventLoop = None TermManagerType = LoopType = Debug = GraphicsCommandType = None CompletedProcess = Tuple @@ -21,5 +21,4 @@ EdgeLiteral = str PowerlineStyle = str MatchType = str Protocol = object -MouseEvent = dict OptionsProtocol = object diff --git a/kitty/typing.pyi b/kitty/typing.pyi index 19a20aa3d..3d2b763ff 100644 --- a/kitty/typing.pyi +++ b/kitty/typing.pyi @@ -9,8 +9,8 @@ from kittens.tui.images import ( GraphicsCommand as GraphicsCommandType, ImageManager as ImageManagerType ) from kittens.tui.loop import ( - Debug as Debug, Loop as LoopType, MouseEvent as MouseEvent, - TermManager as TermManagerType + Debug as Debug, Loop as LoopType, MouseButton as MouseButton, + MouseEvent as MouseEvent, TermManager as TermManagerType ) from .boss import Boss as BossType @@ -23,9 +23,7 @@ from .fast_data_types import ( ) from .key_encoding import KeyEvent as KeyEventType from .layout.base import Layout as LayoutType -from .options.utils import ( - KeyMap as KeyMap, SequenceMap as SequenceMap -) +from .options.utils import KeyMap as KeyMap, SequenceMap as SequenceMap from .rc.base import RemoteCommand as RemoteCommandType from .session import Session as SessionType, Tab as SessionTab from .tabs import ( @@ -56,7 +54,7 @@ __all__ = ( 'EdgeLiteral', 'MatchType', 'GRT_a', 'GRT_f', 'GRT_t', 'GRT_o', 'GRT_m', 'GRT_d', 'GraphicsCommandType', 'HandlerType', 'AbstractEventLoop', 'AddressFamily', 'Socket', 'CompletedProcess', 'PopenType', 'Protocol', 'TypedDict', 'MarkType', 'ImageManagerType', 'Debug', 'LoopType', 'MouseEvent', - 'TermManagerType', 'BossType', 'ChildType', 'BadLineType', + 'TermManagerType', 'BossType', 'ChildType', 'BadLineType', 'MouseButton', 'KeyActionType', 'KeyMap', 'KittyCommonOpts', 'SequenceMap', 'CoreTextFont', 'WindowSystemMouseEvent', 'FontConfigPattern', 'ScreenType', 'StartupCtx', 'KeyEventType', 'LayoutType', 'PowerlineStyle', 'RemoteCommandType', 'SessionType', 'SessionTab', 'SpecialWindowInstance', 'TabType', 'ScreenSize', 'WindowType'