mouse.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package tui
  3. import (
  4. "fmt"
  5. "time"
  6. "kitty"
  7. "kitty/tools/config"
  8. "kitty/tools/tui/loop"
  9. "kitty/tools/utils"
  10. )
  11. var _ = fmt.Print
  12. type LinePos interface {
  13. LessThan(other LinePos) bool
  14. Equal(other LinePos) bool
  15. MinX() int
  16. MaxX() int
  17. }
  18. type SelectionBoundary struct {
  19. line LinePos
  20. x int
  21. in_first_half_of_cell bool
  22. }
  23. func (self *SelectionBoundary) LessThan(other *SelectionBoundary) bool {
  24. if self.line.LessThan(other.line) {
  25. return true
  26. }
  27. if !self.line.Equal(other.line) {
  28. return false
  29. }
  30. if self.x == other.x {
  31. return !self.in_first_half_of_cell && other.in_first_half_of_cell
  32. }
  33. return self.x < other.x
  34. }
  35. func (self *SelectionBoundary) Equal(other SelectionBoundary) bool {
  36. if self.x != other.x || self.in_first_half_of_cell != other.in_first_half_of_cell {
  37. return false
  38. }
  39. if self.line == nil {
  40. return other.line == nil
  41. }
  42. return self.line.Equal(other.line)
  43. }
  44. type MouseSelection struct {
  45. start, end SelectionBoundary
  46. is_active bool
  47. min_y, max_y int
  48. cell_width, cell_height int
  49. drag_scroll struct {
  50. timer_id loop.IdType
  51. pixel_gap int
  52. mouse_event loop.MouseEvent
  53. }
  54. }
  55. func (self *MouseSelection) IsEmpty() bool { return self.start.Equal(self.end) }
  56. func (self *MouseSelection) IsActive() bool { return self.is_active }
  57. func (self *MouseSelection) Finish() { self.is_active = false }
  58. func (self *MouseSelection) Clear() { *self = MouseSelection{} }
  59. func (ms *MouseSelection) StartNewSelection(ev *loop.MouseEvent, line LinePos, min_y, max_y, cell_width, cell_height int) {
  60. *ms = MouseSelection{cell_width: cell_width, cell_height: cell_height, min_y: min_y, max_y: max_y}
  61. ms.start.line = line
  62. ms.start.x = max(line.MinX(), min(ev.Cell.X, line.MaxX()))
  63. cell_start := cell_width * ev.Cell.X
  64. ms.start.in_first_half_of_cell = ev.Pixel.X <= cell_start+cell_width/2
  65. ms.end = ms.start
  66. ms.is_active = true
  67. }
  68. func (ms *MouseSelection) Update(ev *loop.MouseEvent, line LinePos) {
  69. ms.drag_scroll.timer_id = 0
  70. if ms.is_active {
  71. ms.end.x = max(line.MinX(), min(ev.Cell.X, line.MaxX()))
  72. cell_start := ms.cell_width * ms.end.x
  73. ms.end.in_first_half_of_cell = ev.Pixel.X <= cell_start+ms.cell_width/2
  74. ms.end.line = line
  75. }
  76. }
  77. func (ms *MouseSelection) LineBounds(line_pos LinePos) (start_x, end_x int) {
  78. if ms.IsEmpty() {
  79. return -1, -1
  80. }
  81. a, b := &ms.start, &ms.end
  82. if b.LessThan(a) {
  83. a, b = b, a
  84. }
  85. adjust_end := func(x int, b *SelectionBoundary) (int, int) {
  86. if b.in_first_half_of_cell {
  87. if b.x > x {
  88. return x, b.x - 1
  89. }
  90. return -1, -1
  91. }
  92. return x, b.x
  93. }
  94. adjust_start := func(a *SelectionBoundary, x int) (int, int) {
  95. if a.in_first_half_of_cell {
  96. return a.x, x
  97. }
  98. if x > a.x {
  99. return a.x + 1, x
  100. }
  101. return -1, -1
  102. }
  103. adjust_both := func(a, b *SelectionBoundary) (int, int) {
  104. if a.in_first_half_of_cell {
  105. return adjust_end(a.x, b)
  106. } else {
  107. if b.in_first_half_of_cell {
  108. s, e := a.x+1, b.x-1
  109. if e <= s {
  110. return -1, -1
  111. }
  112. return s, e
  113. } else {
  114. return adjust_start(a, b.x)
  115. }
  116. }
  117. }
  118. if a.line.LessThan(line_pos) {
  119. if line_pos.LessThan(b.line) {
  120. return line_pos.MinX(), line_pos.MaxX()
  121. } else if b.line.Equal(line_pos) {
  122. return adjust_end(line_pos.MinX(), b)
  123. }
  124. } else if a.line.Equal(line_pos) {
  125. if line_pos.LessThan(b.line) {
  126. return adjust_start(a, line_pos.MaxX())
  127. } else if b.line.Equal(line_pos) {
  128. return adjust_both(a, b)
  129. }
  130. }
  131. return -1, -1
  132. }
  133. func FormatPartOfLine(sgr string, start_x, end_x, y int) string { // uses zero based indices
  134. // DECCARA used to set formatting in specified region using zero based indexing
  135. return fmt.Sprintf("\x1b[%d;%d;%d;%d;%s$r", y+1, start_x+1, y+1, end_x+1, sgr)
  136. }
  137. func (ms *MouseSelection) LineFormatSuffix(line_pos LinePos, sgr string, y int) string {
  138. s, e := ms.LineBounds(line_pos)
  139. if s > -1 {
  140. return FormatPartOfLine(sgr, s, e, y)
  141. }
  142. return ""
  143. }
  144. func (ms *MouseSelection) StartLine() LinePos {
  145. return ms.start.line
  146. }
  147. func (ms *MouseSelection) EndLine() LinePos {
  148. return ms.end.line
  149. }
  150. func (ms *MouseSelection) OutOfVerticalBounds(ev *loop.MouseEvent) bool {
  151. return ev.Pixel.Y < ms.min_y*ms.cell_height || ev.Pixel.Y > (ms.max_y+1)*ms.cell_height
  152. }
  153. func (ms *MouseSelection) DragScrollTick(timer_id loop.IdType, lp *loop.Loop, callback loop.TimerCallback, do_scroll func(int, *loop.MouseEvent) error) error {
  154. if !ms.is_active || ms.drag_scroll.timer_id != timer_id || ms.drag_scroll.pixel_gap == 0 {
  155. return nil
  156. }
  157. amt := 1
  158. if ms.drag_scroll.pixel_gap < 0 {
  159. amt *= -1
  160. }
  161. err := do_scroll(amt, &ms.drag_scroll.mouse_event)
  162. if err == nil {
  163. ms.drag_scroll.timer_id, _ = lp.AddTimer(50*time.Millisecond, false, callback)
  164. }
  165. return err
  166. }
  167. func (ms *MouseSelection) DragScroll(ev *loop.MouseEvent, lp *loop.Loop, callback loop.TimerCallback) {
  168. if !ms.is_active {
  169. return
  170. }
  171. upper := ms.min_y * ms.cell_height
  172. lower := (ms.max_y + 1) * ms.cell_height
  173. if ev.Pixel.Y < upper {
  174. ms.drag_scroll.pixel_gap = ev.Pixel.Y - upper
  175. } else if ev.Pixel.Y > lower {
  176. ms.drag_scroll.pixel_gap = ev.Pixel.Y - lower
  177. }
  178. if ms.drag_scroll.timer_id == 0 && ms.drag_scroll.pixel_gap != 0 {
  179. ms.drag_scroll.timer_id, _ = lp.AddTimer(50*time.Millisecond, false, callback)
  180. }
  181. ms.drag_scroll.mouse_event = *ev
  182. }
  183. type CellRegion struct {
  184. TopLeft, BottomRight struct{ X, Y int }
  185. Id string
  186. OnClick []func(id string) error
  187. }
  188. func (c CellRegion) Contains(x, y int) bool { // 0-based
  189. if c.TopLeft.Y > y || c.BottomRight.Y < y {
  190. return false
  191. }
  192. return (y > c.TopLeft.Y || (y == c.TopLeft.Y && x >= c.TopLeft.X)) && (y < c.BottomRight.Y || (y == c.BottomRight.Y && x <= c.BottomRight.X))
  193. }
  194. type MouseState struct {
  195. Cell, Pixel struct{ X, Y int }
  196. Pressed struct{ Left, Right, Middle, Fourth, Fifth, Sixth, Seventh bool }
  197. regions []*CellRegion
  198. region_id_map map[string][]*CellRegion
  199. hovered_ids *utils.Set[string]
  200. default_url_style struct {
  201. value string
  202. loaded bool
  203. }
  204. }
  205. func (m *MouseState) AddCellRegion(id string, start_x, start_y, end_x, end_y int, on_click ...func(id string) error) *CellRegion {
  206. cr := CellRegion{TopLeft: struct{ X, Y int }{start_x, start_y}, BottomRight: struct{ X, Y int }{end_x, end_y}, Id: id, OnClick: on_click}
  207. m.regions = append(m.regions, &cr)
  208. if m.region_id_map == nil {
  209. m.region_id_map = make(map[string][]*CellRegion)
  210. }
  211. m.region_id_map[id] = append(m.region_id_map[id], &cr)
  212. return &cr
  213. }
  214. func (m *MouseState) ClearCellRegions() {
  215. m.regions = nil
  216. m.region_id_map = nil
  217. m.hovered_ids = nil
  218. }
  219. func (m *MouseState) UpdateHoveredIds() (changed bool) {
  220. h := utils.NewSet[string]()
  221. for _, r := range m.regions {
  222. if r.Contains(m.Cell.X, m.Cell.Y) {
  223. h.Add(r.Id)
  224. }
  225. }
  226. changed = !h.Equal(m.hovered_ids)
  227. m.hovered_ids = h
  228. return
  229. }
  230. func (m *MouseState) ApplyHoverStyles(lp *loop.Loop, style ...string) {
  231. if m.hovered_ids == nil {
  232. return
  233. }
  234. hs := ""
  235. if len(style) == 0 {
  236. if !m.default_url_style.loaded {
  237. m.default_url_style.loaded = true
  238. color, style := kitty.DefaultUrlColor, kitty.DefaultUrlStyle
  239. line_handler := func(key, val string) error {
  240. switch key {
  241. case "url_color":
  242. color = val
  243. case "url_style":
  244. style = val
  245. }
  246. return nil
  247. }
  248. config.ReadKittyConfig(line_handler)
  249. if style != "none" && style != "" {
  250. m.default_url_style.value = fmt.Sprintf("u=%s uc=%s", style, color)
  251. }
  252. }
  253. hs = m.default_url_style.value
  254. } else {
  255. hs = style[0]
  256. }
  257. is_hovered := false
  258. for id := range m.hovered_ids.Iterable() {
  259. for _, r := range m.region_id_map[id] {
  260. lp.StyleRegion(hs, r.TopLeft.X, r.TopLeft.Y, r.BottomRight.X, r.BottomRight.Y)
  261. is_hovered = true
  262. }
  263. }
  264. if is_hovered {
  265. if s, has := lp.CurrentPointerShape(); !has || s != loop.POINTER_POINTER {
  266. lp.PushPointerShape(loop.POINTER_POINTER)
  267. }
  268. } else {
  269. lp.ClearPointerShapes()
  270. }
  271. }
  272. func (m *MouseState) ClickHoveredRegions() error {
  273. seen := utils.NewSet[string]()
  274. for id := range m.hovered_ids.Iterable() {
  275. for _, r := range m.region_id_map[id] {
  276. if seen.Has(r.Id) {
  277. continue
  278. }
  279. seen.Add(r.Id)
  280. for _, f := range r.OnClick {
  281. if err := f(r.Id); err != nil {
  282. return err
  283. }
  284. }
  285. }
  286. }
  287. return nil
  288. }
  289. func (m *MouseState) UpdateState(ev *loop.MouseEvent) (hovered_ids_changed bool) {
  290. m.Cell = ev.Cell
  291. m.Pixel = ev.Pixel
  292. if ev.Event_type == loop.MOUSE_PRESS || ev.Event_type == loop.MOUSE_RELEASE {
  293. pressed := ev.Event_type == loop.MOUSE_PRESS
  294. if ev.Buttons&loop.LEFT_MOUSE_BUTTON != 0 {
  295. m.Pressed.Left = pressed
  296. }
  297. if ev.Buttons&loop.RIGHT_MOUSE_BUTTON != 0 {
  298. m.Pressed.Right = pressed
  299. }
  300. if ev.Buttons&loop.MIDDLE_MOUSE_BUTTON != 0 {
  301. m.Pressed.Middle = pressed
  302. }
  303. if ev.Buttons&loop.FOURTH_MOUSE_BUTTON != 0 {
  304. m.Pressed.Fourth = pressed
  305. }
  306. if ev.Buttons&loop.FIFTH_MOUSE_BUTTON != 0 {
  307. m.Pressed.Fifth = pressed
  308. }
  309. if ev.Buttons&loop.SIXTH_MOUSE_BUTTON != 0 {
  310. m.Pressed.Sixth = pressed
  311. }
  312. if ev.Buttons&loop.SEVENTH_MOUSE_BUTTON != 0 {
  313. m.Pressed.Seventh = pressed
  314. }
  315. }
  316. return m.UpdateHoveredIds()
  317. }