From 17ce474b796427816bd904d8ae2a05a0b11fde13 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Oct 2023 21:35:51 +0530 Subject: [PATCH] Use hand pointer when hovering over buttons in ask kitten --- kittens/ask/choices.go | 37 ++++++++++++++++++++++++--------- kitty/window.py | 3 ++- tools/tui/loop/api.go | 46 ++++++++++++++++++++++++++++++++++++++++++ tools/tui/loop/run.go | 5 +++++ 4 files changed, 81 insertions(+), 10 deletions(-) diff --git a/kittens/ask/choices.go b/kittens/ask/choices.go index 7cca7abb7..5f0c6116b 100644 --- a/kittens/ask/choices.go +++ b/kittens/ask/choices.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "kitty/tools/cli/markup" + "kitty/tools/tty" "kitty/tools/tui/loop" "kitty/tools/utils" "kitty/tools/utils/style" @@ -60,13 +61,16 @@ func extra_for(width, screen_width int) int { return max(0, screen_width-width)/2 + 1 } +var debugprintln = tty.DebugPrintln +var _ = debugprintln + func GetChoices(o *Options) (response string, err error) { response = "" lp, err := loop.New() if err != nil { return "", err } - lp.MouseTrackingMode(loop.BUTTONS_ONLY_MOUSE_TRACKING) + lp.MouseTrackingMode(loop.FULL_MOUSE_TRACKING) prefix_style_pat := regexp.MustCompile("^(?:\x1b\\[[^m]*?m)+") choice_order := make([]Choice, 0, len(o.Choices)) @@ -398,16 +402,31 @@ func GetChoices(o *Options) (response string, err error) { } lp.OnMouseEvent = func(ev *loop.MouseEvent) error { - if ev.Event_type == loop.MOUSE_CLICK { - for letter, ranges := range clickable_ranges { - for _, r := range ranges { - if r.has_point(ev.Cell.X, ev.Cell.Y) { - response = letter - lp.Quit(0) - return nil - } + on_letter := "" + for letter, ranges := range clickable_ranges { + for _, r := range ranges { + if r.has_point(ev.Cell.X, ev.Cell.Y) { + on_letter = letter + break } } + } + if on_letter != "" { + if s, has_shape := lp.CurrentPointerShape(); !has_shape && s != loop.POINTER_POINTER { + lp.PushPointerShape(loop.POINTER_POINTER) + } + } else { + if _, has_shape := lp.CurrentPointerShape(); has_shape { + lp.PopPointerShape() + } + } + + if ev.Event_type == loop.MOUSE_CLICK { + if on_letter != "" { + response = on_letter + lp.Quit(0) + return nil + } if hidden_text != "" && replacement_range.has_point(ev.Cell.X, ev.Cell.Y) { unhide() } diff --git a/kitty/window.py b/kitty/window.py index 74ffa58cb..fea360442 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -1741,7 +1741,8 @@ def set_pointer_shape(screen: Screen, value: str, os_window_id: int = 0) -> str: value = value[1:] if op in '=>': for v in value.split(','): - screen.change_pointer_shape(op, v) + if v: + screen.change_pointer_shape(op, v) if os_window_id and current_focused_os_window_id() == os_window_id: update_pointer_shape(os_window_id) elif op == '<': diff --git a/tools/tui/loop/api.go b/tools/tui/loop/api.go index 2ec8db8e3..fb8296c84 100644 --- a/tools/tui/loop/api.go +++ b/tools/tui/loop/api.go @@ -54,6 +54,7 @@ type Loop struct { style_cache map[string]func(...any) string style_ctx style.Context atomic_update_active bool + pointer_shapes []PointerShape // Suspend the loop restoring terminal state, and run the provided function. When it returns terminal state is // put back to what it was before suspending unless the function returns an error or an error occurs saving/restoring state. @@ -449,3 +450,48 @@ func (self *Loop) CopyTextToPrimarySelection(text string) { func (self *Loop) CopyTextToClipboard(text string) { self.copy_text_to(text, "c") } + +func (self *Loop) PushPointerShape(s PointerShape) { + self.pointer_shapes = append(self.pointer_shapes, s) + self.QueueWriteString("\x1b]22;" + s.String() + "\x1b\\") +} + +func (self *Loop) PopPointerShape() { + if len(self.pointer_shapes) > 0 { + self.pointer_shapes = self.pointer_shapes[:len(self.pointer_shapes)-1] + self.QueueWriteString("\x1b]22;<\x1b\\") + } +} + +func (self *Loop) ClearPointerShapes() (ans []PointerShape) { + ans = self.pointer_shapes + for i := len(self.pointer_shapes) - 1; i >= 0; i-- { + self.QueueWriteString("\x1b]22;<\x1b\\") + } + self.pointer_shapes = nil + return ans +} + +func (self *Loop) SetPointerShapes(ps []PointerShape) { + self.pointer_shapes = ps + if len(ps) > 0 { + s := strings.Builder{} + s.WriteString("\x1b]22;>") + for i, x := range ps { + s.WriteString(x.String()) + if i+1 < len(ps) { + s.WriteByte(',') + } + } + s.WriteString("\x1b\\") + self.QueueWriteString(s.String()) + } +} + +func (self *Loop) CurrentPointerShape() (ans PointerShape, has_shape bool) { + if len(self.pointer_shapes) > 0 { + has_shape = true + ans = self.pointer_shapes[len(self.pointer_shapes)-1] + } + return +} diff --git a/tools/tui/loop/run.go b/tools/tui/loop/run.go index 0e76e856a..096eb3e6f 100644 --- a/tools/tui/loop/run.go +++ b/tools/tui/loop/run.go @@ -346,6 +346,7 @@ func (self *Loop) run() (err error) { self.QueueWriteString(finalizer) } if needs_reset_escape_codes { + self.ClearPointerShapes() self.QueueWriteString(self.terminal_options.ResetStateEscapeCodes()) } // flush queued data and wait for it to be written for a timeout, then wait for writer to shutdown @@ -365,6 +366,7 @@ func (self *Loop) run() (err error) { } self.SuspendAndRun = func(run func() error) (err error) { + ps := self.ClearPointerShapes() write_id := self.QueueWriteString(self.terminal_options.ResetStateEscapeCodes()) needs_reset_escape_codes = false if err = self.wait_for_write_to_complete(write_id, self.tty_write_channel, write_done_channel, 2*time.Second); err != nil { @@ -386,11 +388,13 @@ func (self *Loop) run() (err error) { return err } write_id = self.QueueWriteString(self.terminal_options.SetStateEscapeCodes()) + self.SetPointerShapes(ps) needs_reset_escape_codes = true return self.wait_for_write_to_complete(write_id, self.tty_write_channel, write_done_channel, 2*time.Second) } self.on_SIGTSTP = func() error { + ps := self.ClearPointerShapes() write_id := self.QueueWriteString(self.terminal_options.ResetStateEscapeCodes()) needs_reset_escape_codes = false err := self.wait_for_write_to_complete(write_id, self.tty_write_channel, write_done_channel, 2*time.Second) @@ -406,6 +410,7 @@ func (self *Loop) run() (err error) { return err } write_id = self.QueueWriteString(self.terminal_options.SetStateEscapeCodes()) + self.SetPointerShapes(ps) needs_reset_escape_codes = true err = self.wait_for_write_to_complete(write_id, self.tty_write_channel, write_done_channel, 2*time.Second) if err != nil {