mouse.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package tui
  3. import (
  4. "fmt"
  5. "time"
  6. "kitty/tools/tui/loop"
  7. )
  8. var _ = fmt.Print
  9. type LinePos interface {
  10. LessThan(other LinePos) bool
  11. Equal(other LinePos) bool
  12. MinX() int
  13. MaxX() int
  14. }
  15. type SelectionBoundary struct {
  16. line LinePos
  17. x int
  18. in_first_half_of_cell bool
  19. }
  20. func (self *SelectionBoundary) LessThan(other *SelectionBoundary) bool {
  21. if self.line.LessThan(other.line) {
  22. return true
  23. }
  24. if !self.line.Equal(other.line) {
  25. return false
  26. }
  27. if self.x == other.x {
  28. return !self.in_first_half_of_cell && other.in_first_half_of_cell
  29. }
  30. return self.x < other.x
  31. }
  32. func (self *SelectionBoundary) Equal(other SelectionBoundary) bool {
  33. if self.x != other.x || self.in_first_half_of_cell != other.in_first_half_of_cell {
  34. return false
  35. }
  36. if self.line == nil {
  37. return other.line == nil
  38. }
  39. return self.line.Equal(other.line)
  40. }
  41. type MouseSelection struct {
  42. start, end SelectionBoundary
  43. is_active bool
  44. min_y, max_y int
  45. cell_width, cell_height int
  46. drag_scroll struct {
  47. timer_id loop.IdType
  48. pixel_gap int
  49. mouse_event loop.MouseEvent
  50. }
  51. }
  52. func (self *MouseSelection) IsEmpty() bool { return self.start.Equal(self.end) }
  53. func (self *MouseSelection) IsActive() bool { return self.is_active }
  54. func (self *MouseSelection) Finish() { self.is_active = false }
  55. func (self *MouseSelection) Clear() { *self = MouseSelection{} }
  56. func (ms *MouseSelection) StartNewSelection(ev *loop.MouseEvent, line LinePos, min_y, max_y, cell_width, cell_height int) {
  57. *ms = MouseSelection{cell_width: cell_width, cell_height: cell_height, min_y: min_y, max_y: max_y}
  58. ms.start.line = line
  59. ms.start.x = max(line.MinX(), min(ev.Cell.X, line.MaxX()))
  60. cell_start := cell_width * ev.Cell.X
  61. ms.start.in_first_half_of_cell = ev.Pixel.X <= cell_start+cell_width/2
  62. ms.end = ms.start
  63. ms.is_active = true
  64. }
  65. func (ms *MouseSelection) Update(ev *loop.MouseEvent, line LinePos) {
  66. ms.drag_scroll.timer_id = 0
  67. if ms.is_active {
  68. ms.end.x = max(line.MinX(), min(ev.Cell.X, line.MaxX()))
  69. cell_start := ms.cell_width * ms.end.x
  70. ms.end.in_first_half_of_cell = ev.Pixel.X <= cell_start+ms.cell_width/2
  71. ms.end.line = line
  72. }
  73. }
  74. func (ms *MouseSelection) LineBounds(line_pos LinePos) (start_x, end_x int) {
  75. if ms.IsEmpty() {
  76. return -1, -1
  77. }
  78. a, b := &ms.start, &ms.end
  79. if b.LessThan(a) {
  80. a, b = b, a
  81. }
  82. adjust_end := func(x int, b *SelectionBoundary) (int, int) {
  83. if b.in_first_half_of_cell {
  84. if b.x > x {
  85. return x, b.x - 1
  86. }
  87. return -1, -1
  88. }
  89. return x, b.x
  90. }
  91. adjust_start := func(a *SelectionBoundary, x int) (int, int) {
  92. if a.in_first_half_of_cell {
  93. return a.x, x
  94. }
  95. if x > a.x {
  96. return a.x + 1, x
  97. }
  98. return -1, -1
  99. }
  100. adjust_both := func(a, b *SelectionBoundary) (int, int) {
  101. if a.in_first_half_of_cell {
  102. return adjust_end(a.x, b)
  103. } else {
  104. if b.in_first_half_of_cell {
  105. s, e := a.x+1, b.x-1
  106. if e <= s {
  107. return -1, -1
  108. }
  109. return s, e
  110. } else {
  111. return adjust_start(a, b.x)
  112. }
  113. }
  114. }
  115. if a.line.LessThan(line_pos) {
  116. if line_pos.LessThan(b.line) {
  117. return line_pos.MinX(), line_pos.MaxX()
  118. } else if b.line.Equal(line_pos) {
  119. return adjust_end(line_pos.MinX(), b)
  120. }
  121. } else if a.line.Equal(line_pos) {
  122. if line_pos.LessThan(b.line) {
  123. return adjust_start(a, line_pos.MaxX())
  124. } else if b.line.Equal(line_pos) {
  125. return adjust_both(a, b)
  126. }
  127. }
  128. return -1, -1
  129. }
  130. func FormatPartOfLine(sgr string, start_x, end_x, y int) string { // uses zero based indices
  131. // DECCARA used to set formatting in specified region using zero based indexing
  132. return fmt.Sprintf("\x1b[%d;%d;%d;%d;%s$r", y+1, start_x+1, y+1, end_x+1, sgr)
  133. }
  134. func (ms *MouseSelection) LineFormatSuffix(line_pos LinePos, sgr string, y int) string {
  135. s, e := ms.LineBounds(line_pos)
  136. if s > -1 {
  137. return FormatPartOfLine(sgr, s, e, y)
  138. }
  139. return ""
  140. }
  141. func (ms *MouseSelection) StartLine() LinePos {
  142. return ms.start.line
  143. }
  144. func (ms *MouseSelection) EndLine() LinePos {
  145. return ms.end.line
  146. }
  147. func (ms *MouseSelection) OutOfVerticalBounds(ev *loop.MouseEvent) bool {
  148. return ev.Pixel.Y < ms.min_y*ms.cell_height || ev.Pixel.Y > (ms.max_y+1)*ms.cell_height
  149. }
  150. func (ms *MouseSelection) DragScrollTick(timer_id loop.IdType, lp *loop.Loop, callback loop.TimerCallback, do_scroll func(int, *loop.MouseEvent) error) error {
  151. if !ms.is_active || ms.drag_scroll.timer_id != timer_id || ms.drag_scroll.pixel_gap == 0 {
  152. return nil
  153. }
  154. amt := 1
  155. if ms.drag_scroll.pixel_gap < 0 {
  156. amt *= -1
  157. }
  158. err := do_scroll(amt, &ms.drag_scroll.mouse_event)
  159. if err == nil {
  160. ms.drag_scroll.timer_id, _ = lp.AddTimer(50*time.Millisecond, false, callback)
  161. }
  162. return err
  163. }
  164. func (ms *MouseSelection) DragScroll(ev *loop.MouseEvent, lp *loop.Loop, callback loop.TimerCallback) {
  165. if !ms.is_active {
  166. return
  167. }
  168. upper := ms.min_y * ms.cell_height
  169. lower := (ms.max_y + 1) * ms.cell_height
  170. if ev.Pixel.Y < upper {
  171. ms.drag_scroll.pixel_gap = ev.Pixel.Y - upper
  172. } else if ev.Pixel.Y > lower {
  173. ms.drag_scroll.pixel_gap = ev.Pixel.Y - lower
  174. }
  175. if ms.drag_scroll.timer_id == 0 && ms.drag_scroll.pixel_gap != 0 {
  176. ms.drag_scroll.timer_id, _ = lp.AddTimer(50*time.Millisecond, false, callback)
  177. }
  178. ms.drag_scroll.mouse_event = *ev
  179. }