From 00d4841304e3bdf85274cd7267fba7f644c82a9f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 28 Mar 2023 11:48:22 +0530 Subject: [PATCH] Make the mouse selection code re-useable --- kittens/diff/mouse.go | 79 +++++++++--------------------------- kittens/diff/ui.go | 17 +++++++- tools/tui/mouse.go | 93 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 62 deletions(-) create mode 100644 tools/tui/mouse.go diff --git a/kittens/diff/mouse.go b/kittens/diff/mouse.go index ac561c2d4..add52aa8e 100644 --- a/kittens/diff/mouse.go +++ b/kittens/diff/mouse.go @@ -4,30 +4,17 @@ package diff import ( "fmt" + "path/filepath" + "strconv" + "kitty" "kitty/tools/config" "kitty/tools/tui/loop" "kitty/tools/utils" - "path/filepath" - "strconv" ) var _ = fmt.Print -type SelectionBoundary struct { - line ScrollPos - x int - in_first_half_of_cell bool -} - -type MouseSelection struct { - start, end SelectionBoundary - is_active bool - min_x, max_x int -} - -func (self *MouseSelection) IsEmpty() bool { return self.start == self.end } - type KittyOpts struct { Wheel_scroll_multiplier int } @@ -62,8 +49,6 @@ func (self *Handler) handle_wheel_event(up bool) { } func (self *Handler) start_mouse_selection(ev *loop.MouseEvent) { - self.mouse_selection = MouseSelection{} - ms := &self.mouse_selection available_cols := self.logical_lines.columns / 2 if ev.Cell.Y >= self.screen_size.num_lines || ev.Cell.X < self.logical_lines.margin_size || (ev.Cell.X >= available_cols && ev.Cell.X < available_cols+self.logical_lines.margin_size) { return @@ -74,51 +59,38 @@ func (self *Handler) start_mouse_selection(ev *loop.MouseEvent) { if ll.line_type == EMPTY_LINE || ll.line_type == IMAGE_LINE { return } - ms.start.line = pos - ms.start.x = ev.Cell.X - ms.min_x = self.logical_lines.margin_size - ms.max_x = available_cols - 1 - if ms.start.x >= available_cols { - ms.min_x += available_cols - ms.max_x += available_cols + min_x := self.logical_lines.margin_size + max_x := available_cols - 1 + if ev.Cell.X >= available_cols { + min_x += available_cols + max_x += available_cols } - ms.start.x = utils.Max(ms.min_x, utils.Min(ms.start.x, ms.max_x)) - cell_start := self.screen_size.cell_width * ev.Cell.X - ms.start.in_first_half_of_cell = ev.Pixel.X <= cell_start+self.screen_size.cell_width/2 - - ms.end = ms.start - ms.is_active = true + self.mouse_selection.StartNewSelection(ev, &pos, min_x, max_x, 0, self.screen_size.num_lines-1, self.screen_size.cell_width, self.screen_size.cell_height) } func (self *Handler) update_mouse_selection(ev *loop.MouseEvent) { - ms := &self.mouse_selection - if !self.mouse_selection.is_active { + if !self.mouse_selection.IsActive() { return } pos := self.scroll_pos y := ev.Cell.Y y = utils.Max(0, utils.Min(y, self.screen_size.num_lines-1)) self.logical_lines.IncrementScrollPosBy(&pos, y) - ms.end.x = ev.Cell.X - ms.end.x = utils.Max(ms.min_x, utils.Min(ms.end.x, ms.max_x)) - cell_start := self.screen_size.cell_width * ms.end.x - ms.end.in_first_half_of_cell = ev.Pixel.X <= cell_start+self.screen_size.cell_width/2 - ms.end.line = pos + self.mouse_selection.Update(ev, &pos) self.draw_screen() } func (self *Handler) clear_mouse_selection() { - self.mouse_selection = MouseSelection{} + self.mouse_selection.Clear() } func (self *Handler) finish_mouse_selection(ev *loop.MouseEvent) { - self.update_mouse_selection(ev) - ms := &self.mouse_selection - if !self.mouse_selection.is_active { + if !self.mouse_selection.IsActive() { return } - ms.is_active = false + self.update_mouse_selection(ev) + self.mouse_selection.Finish() } func format_part_of_line(sgr string, start_x, end_x, y int) string { @@ -131,24 +103,9 @@ func (self *Handler) add_mouse_selection_to_line(line string, line_pos ScrollPos if ms.IsEmpty() { return line } - a, b := ms.start.line, ms.end.line - ax, bx := ms.start.x, ms.end.x - if b.Less(a) { - a, b = b, a - ax, bx = bx, ax - } - if a.Less(line_pos) { - if line_pos.Less(b) { - line += format_part_of_line(selection_sgr, 0, ms.max_x, y) - } else if b == line_pos { - line += format_part_of_line(selection_sgr, 0, bx, y) - } - } else if a == line_pos { - if line_pos.Less(b) { - line += format_part_of_line(selection_sgr, ax, ms.max_x, y) - } else if b == line_pos { - line += format_part_of_line(selection_sgr, ax, bx, y) - } + x_start, x_end := self.mouse_selection.LineBounds(&line_pos) + if x_start > -1 { + line += format_part_of_line(selection_sgr, x_start, x_end, y) } return line } diff --git a/kittens/diff/ui.go b/kittens/diff/ui.go index cd75654a8..eb098079a 100644 --- a/kittens/diff/ui.go +++ b/kittens/diff/ui.go @@ -10,6 +10,7 @@ import ( "kitty/tools/config" "kitty/tools/tty" + "kitty/tools/tui" "kitty/tools/tui/graphics" "kitty/tools/tui/loop" "kitty/tools/tui/readline" @@ -37,6 +38,20 @@ func (self ScrollPos) Less(other ScrollPos) bool { return self.logical_line < other.logical_line || (self.logical_line == other.logical_line && self.screen_line < other.screen_line) } +func (self *ScrollPos) Equal(other tui.LinePos) bool { + if o, ok := other.(*ScrollPos); ok { + return *self == *o + } + return false +} + +func (self *ScrollPos) LessThan(other tui.LinePos) bool { + if o, ok := other.(*ScrollPos); ok { + return self.Less(*o) + } + return false +} + func (self ScrollPos) Add(other ScrollPos) ScrollPos { return ScrollPos{self.logical_line + other.logical_line, self.screen_line + other.screen_line} } @@ -54,7 +69,7 @@ var image_collection *graphics.ImageCollection type screen_size struct{ rows, columns, num_lines, cell_width, cell_height int } type Handler struct { async_results chan AsyncResult - mouse_selection MouseSelection + mouse_selection tui.MouseSelection shortcut_tracker config.ShortcutTracker left, right string collection *Collection diff --git a/tools/tui/mouse.go b/tools/tui/mouse.go new file mode 100644 index 000000000..848e71589 --- /dev/null +++ b/tools/tui/mouse.go @@ -0,0 +1,93 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +package tui + +import ( + "fmt" + "kitty/tools/tty" + "kitty/tools/tui/loop" + "kitty/tools/utils" +) + +var _ = fmt.Print + +type LinePos interface { + LessThan(other LinePos) bool + Equal(other LinePos) bool +} + +type SelectionBoundary struct { + line LinePos + x int + in_first_half_of_cell bool +} + +func (self *SelectionBoundary) Equal(other SelectionBoundary) bool { + if self.x != other.x || self.in_first_half_of_cell != other.in_first_half_of_cell { + return false + } + if self.line == nil { + return other.line == nil + } + return self.line.Equal(other.line) +} + +type MouseSelection struct { + start, end SelectionBoundary + is_active bool + min_x, max_x int + min_y, max_y int + cell_width, cell_height int +} + +func (self *MouseSelection) IsEmpty() bool { return self.start.Equal(self.end) } +func (self *MouseSelection) IsActive() bool { return self.is_active } +func (self *MouseSelection) Finish() { self.is_active = false } +func (self *MouseSelection) Clear() { *self = MouseSelection{} } + +func (ms *MouseSelection) StartNewSelection(ev *loop.MouseEvent, line LinePos, min_x, max_x, min_y, max_y, cell_width, cell_height int) { + *ms = MouseSelection{min_x: min_x, max_x: max_x, cell_width: cell_width, cell_height: cell_height, min_y: min_y, max_y: max_y} + ms.start.line = line + ms.start.x = utils.Max(ms.min_x, utils.Min(ev.Cell.X, ms.max_x)) + cell_start := cell_width * ev.Cell.X + ms.start.in_first_half_of_cell = ev.Pixel.X <= cell_start+cell_width/2 + ms.end = ms.start + ms.is_active = true +} + +func (ms *MouseSelection) Update(ev *loop.MouseEvent, line LinePos) { + if ms.is_active { + ms.end.x = utils.Max(ms.min_x, utils.Min(ev.Cell.X, ms.max_x)) + cell_start := ms.cell_width * ms.end.x + ms.end.in_first_half_of_cell = ev.Pixel.X <= cell_start+ms.cell_width/2 + ms.end.line = line + } +} + +var DebugPrintln = tty.DebugPrintln + +func (ms *MouseSelection) LineBounds(line_pos LinePos) (start_x, end_x int) { + if ms.IsEmpty() { + return -1, -1 + } + a, b := ms.start.line, ms.end.line + ax, bx := ms.start.x, ms.end.x + if b.LessThan(a) { + a, b = b, a + ax, bx = bx, ax + } + if a.LessThan(line_pos) { + if line_pos.LessThan(b) { + return ms.min_x, ms.max_x + } else if b.Equal(line_pos) { + return ms.min_x, bx + } + } else if a.Equal(line_pos) { + if line_pos.LessThan(b) { + return ax, ms.max_x + } else if b.Equal(line_pos) { + return ax, bx + } + } + return -1, -1 +}