rootview.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. local core = require "core"
  2. local common = require "core.common"
  3. local style = require "core.style"
  4. local keymap = require "core.keymap"
  5. local Object = require "core.object"
  6. local View = require "core.view"
  7. local DocView = require "core.docview"
  8. local EmptyView = View:extend()
  9. function EmptyView:draw()
  10. self:draw_background(style.background)
  11. local pos = self.position
  12. local x, y, w, h = pos.x, pos.y, self.size.x, self.size.y
  13. local _, y = common.draw_text(style.big_font, style.dim, "empty", "center", x, y, w, h)
  14. local lines = {
  15. { fmt = "%s to run a command", cmd = "core:do-command" },
  16. { fmt = "%s to open a file from the project", cmd = "core:open-project-file" },
  17. }
  18. local th = style.font:get_height()
  19. for _, line in ipairs(lines) do
  20. local text = string.format(line.fmt, keymap.get_binding(line.cmd))
  21. y = y + style.padding.y
  22. common.draw_text(style.font, style.dim, text, "center", x, y, w, th)
  23. y = y + th
  24. end
  25. end
  26. local Node = Object:extend()
  27. function Node:new(type)
  28. self.type = type or "leaf"
  29. self.position = { x = 0, y = 0 }
  30. self.size = { x = 0, y = 0 }
  31. self.views = {}
  32. self.divider = 0.5
  33. if self.type == "leaf" then
  34. self:add_view(EmptyView())
  35. end
  36. end
  37. function Node:propagate(fn, ...)
  38. self.a[fn](self.a, ...)
  39. self.b[fn](self.b, ...)
  40. end
  41. function Node:on_mouse_moved(x, y, ...)
  42. self.hovered_tab = self:get_tab_overlapping_point(x, y)
  43. if self.type == "leaf" then
  44. self.active_view:on_mouse_moved(x, y, ...)
  45. else
  46. self:propagate("on_mouse_moved", x, y, ...)
  47. end
  48. end
  49. function Node:on_mouse_released(...)
  50. if self.type == "leaf" then
  51. self.active_view:on_mouse_released(...)
  52. else
  53. self:propagate("on_mouse_released", ...)
  54. end
  55. end
  56. function Node:consume(node)
  57. for k, _ in pairs(self) do self[k] = nil end
  58. for k, v in pairs(node) do self[k] = v end
  59. end
  60. local type_map = { up="vsplit", down="vsplit", left="hsplit", right="hsplit" }
  61. function Node:split(dir, view, locked)
  62. assert(self.type == "leaf", "tried to split non-leaf node")
  63. local type = assert(type_map[dir], "invalid direction")
  64. local child = Node()
  65. child:consume(self)
  66. self:consume(Node(type))
  67. self.a = child
  68. self.b = Node()
  69. self.b.locked = locked
  70. if view then self.b:add_view(view) end
  71. if not self.b.active_view.focusable then
  72. self.a:set_active_view(self.a.active_view)
  73. end
  74. if dir == "up" or dir == "left" then
  75. self.a, self.b = self.b, self.a
  76. end
  77. return child
  78. end
  79. function Node:close_active_view(root)
  80. local do_close = function()
  81. if #self.views > 1 then
  82. local idx = self:get_view_idx(self.active_view)
  83. table.remove(self.views, idx)
  84. self:set_active_view(self.views[idx] or self.views[#self.views])
  85. else
  86. local parent = self:get_parent_node(root)
  87. local other = parent[parent.a == self and "b" or "a"]
  88. if other:get_locked_size() then
  89. self.views = {}
  90. self:add_view(EmptyView())
  91. else
  92. parent:consume(other)
  93. parent:set_active_view(parent.active_view)
  94. end
  95. end
  96. end
  97. self.active_view:try_close(do_close)
  98. end
  99. function Node:add_view(view)
  100. assert(self.type == "leaf", "tried to add view to non-leaf node")
  101. if self.views[1] and self.views[1]:is(EmptyView) then
  102. table.remove(self.views)
  103. end
  104. table.insert(self.views, view)
  105. self:set_active_view(view)
  106. end
  107. function Node:set_active_view(view)
  108. self.active_view = view
  109. core.active_view = view
  110. end
  111. function Node:get_view_idx(view)
  112. for i, v in ipairs(self.views) do
  113. if v == view then return i end
  114. end
  115. end
  116. function Node:get_node_for_view(view)
  117. for _, v in ipairs(self.views) do
  118. if v == view then return self end
  119. end
  120. if self.type ~= "leaf" then
  121. return self.a:get_node_for_view(view) or self.b:get_node_for_view(view)
  122. end
  123. end
  124. function Node:get_parent_node(root)
  125. if root.a == self or root.b == self then
  126. return root
  127. elseif root.type ~= "leaf" then
  128. return self:get_parent_node(root.a) or self:get_parent_node(root.b)
  129. end
  130. end
  131. function Node:get_children(t)
  132. t = t or {}
  133. for _, view in ipairs(self.views) do
  134. table.insert(t, view)
  135. end
  136. if self.a then self.a:get_children(t) end
  137. if self.b then self.b:get_children(t) end
  138. return t
  139. end
  140. function Node:get_divider_overlapping_point(px, py)
  141. if self.type ~= "leaf" then
  142. local p = 6
  143. local x, y, w, h = self:get_divider_rect()
  144. x, y = x - p, y - p
  145. w, h = w + p * 2, h + p * 2
  146. if px > x and py > y and px < x + w and py < y + h then
  147. return self
  148. end
  149. return self.a:get_divider_overlapping_point(px, py)
  150. or self.b:get_divider_overlapping_point(px, py)
  151. end
  152. end
  153. function Node:get_tab_overlapping_point(px, py)
  154. if #self.views == 1 then return nil end
  155. local x, y, w, h = self:get_tab_rect(1)
  156. if px >= x and py >= y and px < x + w * #self.views and py < y + h then
  157. return math.floor((px - x) / w) + 1
  158. end
  159. end
  160. function Node:get_child_overlapping_point(x, y)
  161. local child
  162. if self.type == "leaf" then
  163. return self
  164. elseif self.type == "hsplit" then
  165. child = (x < self.b.position.x) and self.a or self.b
  166. elseif self.type == "vsplit" then
  167. child = (y < self.b.position.y) and self.a or self.b
  168. end
  169. return child:get_child_overlapping_point(x, y)
  170. end
  171. function Node:get_tab_rect(idx)
  172. local tw = math.min(style.tab_width, math.ceil(self.size.x / #self.views))
  173. local h = style.font:get_height() + style.padding.y * 2
  174. return self.position.x + (idx-1) * tw, self.position.y, tw, h
  175. end
  176. function Node:get_divider_rect()
  177. local x, y = self.position.x, self.position.y
  178. if self.type == "hsplit" then
  179. return x + self.a.size.x, y, style.divider_size, self.size.y
  180. elseif self.type == "vsplit" then
  181. return x, y + self.a.size.y, self.size.x, style.divider_size
  182. end
  183. end
  184. function Node:get_locked_size()
  185. if self.type == "leaf" then
  186. if self.locked then
  187. local size = self.active_view.size
  188. return size.x, size.y
  189. end
  190. else
  191. local x1, y1 = self.a:get_locked_size()
  192. local x2, y2 = self.b:get_locked_size()
  193. if x1 and x2 then
  194. return x1 + x2 + style.divider_size, y1 + y2 + style.divider_size
  195. end
  196. end
  197. end
  198. local function copy_position_and_size(dst, src)
  199. dst.position.x, dst.position.y = src.position.x, src.position.y
  200. dst.size.x, dst.size.y = src.size.x, src.size.y
  201. end
  202. -- calculating the sizes is the same for hsplits and vsplits, except the x/y
  203. -- axis are swapped; this function lets us use the same code for both
  204. local function calc_split_sizes(self, x, y, x1, x2)
  205. local n
  206. local ds = (x1 == 0 or x2 == 0) and 0 or style.divider_size
  207. if x1 then
  208. n = math.floor(x1 + ds)
  209. elseif x2 then
  210. n = math.floor(self.size[x] - x2)
  211. else
  212. n = math.floor(self.size[x] * self.divider)
  213. end
  214. self.a.position[x] = self.position[x]
  215. self.a.position[y] = self.position[y]
  216. self.a.size[x] = n - ds
  217. self.a.size[y] = self.size[y]
  218. self.b.position[x] = self.position[x] + n
  219. self.b.position[y] = self.position[y]
  220. self.b.size[x] = self.size[x] - n
  221. self.b.size[y] = self.size[y]
  222. end
  223. function Node:update_layout()
  224. if self.type == "leaf" then
  225. local av = self.active_view
  226. if #self.views > 1 then
  227. local _, _, _, th = self:get_tab_rect(1)
  228. av.position.x, av.position.y = self.position.x, self.position.y + th
  229. av.size.x, av.size.y = self.size.x, self.size.y - th
  230. else
  231. copy_position_and_size(av, self)
  232. end
  233. else
  234. local x1, y1 = self.a:get_locked_size()
  235. local x2, y2 = self.b:get_locked_size()
  236. if self.type == "hsplit" then
  237. calc_split_sizes(self, "x", "y", x1, x2)
  238. elseif self.type == "vsplit" then
  239. calc_split_sizes(self, "y", "x", y1, y2)
  240. end
  241. self.a:update_layout()
  242. self.b:update_layout()
  243. end
  244. end
  245. function Node:update()
  246. if self.type == "leaf" then
  247. for _, view in ipairs(self.views) do
  248. view:update()
  249. end
  250. else
  251. self.a:update()
  252. self.b:update()
  253. end
  254. end
  255. function Node:draw_tabs()
  256. local x, y, _, h = self:get_tab_rect(1)
  257. local ds = style.divider_size
  258. core.push_clip_rect(x, y, self.size.x, h)
  259. renderer.draw_rect(x, y, self.size.x, h, style.background2)
  260. renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
  261. for i, view in ipairs(self.views) do
  262. local x, y, w, h = self:get_tab_rect(i)
  263. local text = view:get_name()
  264. local color = style.dim
  265. if view == self.active_view then
  266. color = style.text
  267. renderer.draw_rect(x, y, w, h, style.background)
  268. renderer.draw_rect(x + w, y, ds, h, style.divider)
  269. renderer.draw_rect(x - ds, y, ds, h, style.divider)
  270. end
  271. if i == self.hovered_tab then
  272. color = style.text
  273. end
  274. core.push_clip_rect(x, y, w, h)
  275. common.draw_text(style.font, color, text, "center", x, y, w, h)
  276. core.pop_clip_rect()
  277. end
  278. core.pop_clip_rect()
  279. end
  280. function Node:draw()
  281. if self.type == "leaf" then
  282. if #self.views > 1 then
  283. self:draw_tabs()
  284. end
  285. local pos, size = self.active_view.position, self.active_view.size
  286. core.push_clip_rect(pos.x, pos.y, size.x, size.y)
  287. self.active_view:draw()
  288. core.pop_clip_rect()
  289. else
  290. local x, y, w, h = self:get_divider_rect()
  291. renderer.draw_rect(x, y, w, h, style.divider)
  292. self:propagate("draw")
  293. end
  294. end
  295. local RootView = View:extend()
  296. function RootView:new()
  297. RootView.super.new(self)
  298. self.root_node = Node()
  299. self.deferred_draws = {}
  300. self.mouse = { x = 0, y = 0 }
  301. end
  302. function RootView:defer_draw(fn, ...)
  303. table.insert(self.deferred_draws, 1, { fn = fn, ... })
  304. end
  305. function RootView:get_active_node()
  306. local node = self.root_node:get_node_for_view(core.active_view)
  307. return node or self.root_node.a
  308. end
  309. function RootView:open_doc(doc)
  310. local node = self:get_active_node()
  311. assert(not node.locked, "Cannot open doc on locked node")
  312. for i, view in ipairs(node.views) do
  313. if view.doc == doc then
  314. node:set_active_view(node.views[i])
  315. return view
  316. end
  317. end
  318. local view = DocView(doc)
  319. node:add_view(view)
  320. self.root_node:update_layout()
  321. view:scroll_to_line(view.doc:get_selection(), true, true)
  322. return view
  323. end
  324. function RootView:on_mouse_pressed(button, x, y, clicks)
  325. local div = self.root_node:get_divider_overlapping_point(x, y)
  326. if div then
  327. self.dragged_divider = div
  328. return
  329. end
  330. local node = self.root_node:get_child_overlapping_point(x, y)
  331. local idx = node:get_tab_overlapping_point(x, y)
  332. if idx then
  333. node:set_active_view(node.views[idx])
  334. if button == "middle" then
  335. node:close_active_view(self.root_node)
  336. end
  337. else
  338. if node.active_view.focusable then
  339. core.active_view = node.active_view
  340. end
  341. node.active_view:on_mouse_pressed(button, x, y, clicks)
  342. end
  343. end
  344. function RootView:on_mouse_released(...)
  345. if self.dragged_divider then
  346. self.dragged_divider = nil
  347. end
  348. self.root_node:on_mouse_released(...)
  349. end
  350. function RootView:on_mouse_moved(x, y, dx, dy)
  351. if self.dragged_divider then
  352. local div = self.dragged_divider
  353. if div.type == "hsplit" then
  354. div.divider = div.divider + dx / div.size.x
  355. else
  356. div.divider = div.divider + dy / div.size.y
  357. end
  358. div.divider = common.clamp(div.divider, 0.01, 0.99)
  359. return
  360. end
  361. self.mouse.x, self.mouse.y = x, y
  362. self.root_node:on_mouse_moved(x, y, dx, dy)
  363. local node = self.root_node:get_child_overlapping_point(x, y)
  364. local div = self.root_node:get_divider_overlapping_point(x, y)
  365. if div then
  366. system.set_cursor(div.type == "hsplit" and "sizeh" or "sizev")
  367. elseif node:get_tab_overlapping_point(x, y) then
  368. system.set_cursor("arrow")
  369. else
  370. system.set_cursor(node.active_view.cursor)
  371. end
  372. end
  373. function RootView:on_mouse_wheel(...)
  374. local x, y = self.mouse.x, self.mouse.y
  375. local node = self.root_node:get_child_overlapping_point(x, y)
  376. node.active_view:on_mouse_wheel(...)
  377. end
  378. function RootView:on_text_input(...)
  379. core.active_view:on_text_input(...)
  380. end
  381. function RootView:update()
  382. copy_position_and_size(self.root_node, self)
  383. self.root_node:update()
  384. self.root_node:update_layout()
  385. -- do `on_mouse_moved` if the scroll of the hovered view has changed
  386. local x, y = self.mouse.x, self.mouse.y
  387. local s = self.root_node:get_child_overlapping_point(x, y).active_view.scroll
  388. if self.last_scroll_x ~= s.x or self.last_scroll_y ~= s.y then
  389. self:on_mouse_moved(self.mouse.x, self.mouse.y, 0, 0)
  390. self.last_scroll_x, self.last_scroll_y = s.x, s.y
  391. end
  392. end
  393. function RootView:draw()
  394. self.root_node:draw()
  395. while #self.deferred_draws > 0 do
  396. local t = table.remove(self.deferred_draws)
  397. t.fn(table.unpack(t))
  398. end
  399. end
  400. return RootView