manager.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. package winman
  2. import (
  3. "sync"
  4. "github.com/gdamore/tcell/v2"
  5. "github.com/rivo/tview"
  6. )
  7. // WindowEdge enumerates the different window edges and corners
  8. type WindowEdge int16
  9. // Different window edges
  10. const (
  11. EdgeNone WindowEdge = iota
  12. EdgeTop
  13. EdgeRight
  14. EdgeBottom
  15. EdgeLeft
  16. EdgeBottomRight
  17. EdgeBottomLeft
  18. )
  19. // WindowZTop is used with SetZ to move a window to the top
  20. const WindowZTop = -1
  21. // WindowZBottom is used with SetZ to move a window to the bottom
  22. const WindowZBottom = 0
  23. // MinWindowWidth sets the minimum width a window can have as part of a window manager
  24. var MinWindowWidth = 3
  25. // MinWindowHeight sets the minimum height a window can have as part of a window manager
  26. var MinWindowHeight = 3
  27. // inRect returns true if the given coordinates are within the window
  28. func inRect(wnd Window, x, y int) bool {
  29. return NewRect(wnd.GetRect()).Contains(x, y)
  30. }
  31. // Manager represents a Window Manager primitive
  32. type Manager struct {
  33. *tview.Box
  34. // The windows to be positioned.
  35. windows Stack
  36. dragOffsetX, dragOffsetY int
  37. draggedWindow Window
  38. draggedEdge WindowEdge
  39. sync.Mutex
  40. }
  41. // NewWindowManager returns a ready to use window manager
  42. func NewWindowManager() *Manager {
  43. wm := &Manager{
  44. Box: tview.NewBox(),
  45. }
  46. return wm
  47. }
  48. // NewWindow creates a new (hidden) window and adds it to this window manager
  49. func (wm *Manager) NewWindow() *WindowBase {
  50. wnd := NewWindow()
  51. wm.AddWindow(wnd)
  52. return wnd
  53. }
  54. // AddWindow adds the given window to the window manager
  55. func (wm *Manager) AddWindow(window Window) *Manager {
  56. wm.Lock()
  57. defer wm.Unlock()
  58. wm.windows.Push(window)
  59. return wm
  60. }
  61. // RemoveWindow removes the given window from this window manager
  62. func (wm *Manager) RemoveWindow(window Window) *Manager {
  63. wm.Lock()
  64. defer wm.Unlock()
  65. wm.windows.Remove(window)
  66. return wm
  67. }
  68. // Center centers the given window relative to the window manager
  69. func (wm *Manager) Center(window Window) *Manager {
  70. mx, my, mw, mh := wm.GetInnerRect()
  71. _, _, width, height := window.GetRect()
  72. x := mx + (mw-width)/2
  73. y := my + (mh-height)/2
  74. window.SetRect(x, y, width, height)
  75. return wm
  76. }
  77. // WindowCount returns the number of windows managed by this window manager
  78. func (wm *Manager) WindowCount() int {
  79. wm.Lock()
  80. defer wm.Unlock()
  81. return len(wm.windows)
  82. }
  83. // Window returns the window at the given z index
  84. func (wm *Manager) Window(z int) Window {
  85. wm.Lock()
  86. defer wm.Unlock()
  87. wnd, _ := wm.windows.Item(z).(Window)
  88. return wnd
  89. }
  90. func (wm *Manager) getZ(window Window) int {
  91. return wm.windows.IndexOf(window)
  92. }
  93. // GetZ returns the z index of the given window
  94. // returns -1 if the given window is not part of this manager
  95. func (wm *Manager) GetZ(window Window) int {
  96. wm.Lock()
  97. defer wm.Unlock()
  98. return wm.getZ(window)
  99. }
  100. func (wm *Manager) setZ(window Window, newZ int) {
  101. wm.windows.Move(window, newZ)
  102. }
  103. // SetZ moves the given window to the given z index
  104. // The special constants WindowZTop and WindowZBottom can be used
  105. func (wm *Manager) SetZ(window Window, newZ int) *Manager {
  106. wm.Lock()
  107. defer wm.Unlock()
  108. wm.setZ(window, newZ)
  109. return wm
  110. }
  111. // Focus is called when this primitive receives focus
  112. // implements tview.Primitive.Focus
  113. func (wm *Manager) Focus(delegate func(p tview.Primitive)) {
  114. wm.Lock()
  115. window, _ := wm.windows.Find(func(wi interface{}) bool {
  116. return wi.(Window).IsVisible()
  117. }).(Window)
  118. if window != nil {
  119. wm.Unlock()
  120. window.Focus(delegate)
  121. return
  122. }
  123. wm.Unlock()
  124. }
  125. // HasFocus returns whether or not this primitive has focus.
  126. // implements tview.Focusable
  127. func (wm *Manager) HasFocus() bool {
  128. wm.Lock()
  129. defer wm.Unlock()
  130. // iterate over all windows. If any has focus, then the
  131. // this window manager has focus.
  132. return nil != wm.windows.Find(func(wi interface{}) bool {
  133. return wi.(Window).HasFocus()
  134. })
  135. }
  136. // Draw draws this primitive onto the screen.
  137. // implements tview.Primitive.Draw
  138. func (wm *Manager) Draw(screen tcell.Screen) {
  139. wm.Box.Draw(screen)
  140. wm.Lock()
  141. defer wm.Unlock()
  142. // Ensure that the window with focus has the highest Z-index:
  143. topWindowIndex := len(wm.windows) - 1
  144. for i := topWindowIndex; i >= 0; i-- {
  145. window := wm.windows[i].(Window)
  146. if window.IsVisible() && window.HasFocus() {
  147. if i < topWindowIndex {
  148. wm.setZ(window, WindowZTop) // move focused window on top
  149. }
  150. break
  151. }
  152. }
  153. // make sure windows are not out of bounds, too small,
  154. // or too big to fit within the window manager:
  155. for _, wndItem := range wm.windows {
  156. window := wndItem.(Window)
  157. if !window.IsVisible() {
  158. continue
  159. }
  160. mx, my, mw, mh := wm.GetInnerRect()
  161. x, y, w, h := window.GetRect()
  162. // Avoid window overflowing on the left:
  163. if x < mx {
  164. x = mx
  165. }
  166. // Avoid window overflowing on the top:
  167. if y < my {
  168. y = my
  169. }
  170. // Fix window that is too narrow:
  171. if w < MinWindowWidth {
  172. w = MinWindowWidth
  173. }
  174. // Fix window that is too short:
  175. if h < MinWindowHeight {
  176. h = MinWindowHeight
  177. }
  178. // reduce windows that are too wide,
  179. // or fix size if the window is maximized
  180. if w > mw || window.IsMaximized() {
  181. w = mw
  182. x = mx
  183. }
  184. // reduce windows that are too tall,
  185. // or fix size if the window is maximized
  186. if h > mh || window.IsMaximized() {
  187. h = mh
  188. y = my
  189. }
  190. // Avoid window overflowing the right edge
  191. if x+w > mx+mw {
  192. x = mx + mw - w
  193. }
  194. // Avoid window overflowing the bottom edge
  195. if y+h > my+mh {
  196. y = my + mh - h
  197. }
  198. // reposition window to the new coordinates:
  199. window.SetRect(x, y, w, h)
  200. // now we can draw it
  201. window.Draw(screen)
  202. }
  203. }
  204. // MouseHandler returns the mouse handler for this primitive.
  205. // implements tview.Primitive.MouseHandler
  206. func (wm *Manager) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
  207. return wm.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
  208. // ignore mouse events out of the bounds of the window manager
  209. if !wm.InRect(event.Position()) {
  210. return false, nil
  211. }
  212. wm.Lock()
  213. // check if there is an active drag operation:
  214. if wm.draggedWindow != nil {
  215. switch action {
  216. case tview.MouseLeftUp:
  217. wm.draggedWindow = nil // if the button is released, stop the drag operation
  218. case tview.MouseMove:
  219. x, y := event.Position()
  220. wx, wy, ww, wh := wm.draggedWindow.GetRect()
  221. // depending if the drag operation is on the top or edges, either move the window or resize
  222. if wm.draggedEdge == EdgeTop && wm.draggedWindow.IsDraggable() {
  223. wm.draggedWindow.SetRect(x-wm.dragOffsetX, y-wm.dragOffsetY, ww, wh) // move window
  224. } else {
  225. // resize window pulling from the corresponding edge
  226. if wm.draggedWindow.IsResizable() {
  227. switch wm.draggedEdge {
  228. case EdgeRight:
  229. wm.draggedWindow.SetRect(wx, wy, x-wx+1, wh)
  230. case EdgeBottom:
  231. wm.draggedWindow.SetRect(wx, wy, ww, y-wy+1)
  232. case EdgeLeft:
  233. wm.draggedWindow.SetRect(x, wy, ww+wx-x, wh)
  234. case EdgeBottomRight:
  235. wm.draggedWindow.SetRect(wx, wy, x-wx+1, y-wy+1)
  236. case EdgeBottomLeft:
  237. wm.draggedWindow.SetRect(x, wy, ww+wx-x, y-wy+1)
  238. }
  239. }
  240. }
  241. wm.Unlock()
  242. return true, nil
  243. }
  244. }
  245. lastModal := false
  246. // Pass mouse events along to the window with highest Z
  247. // that is hit by the mouse
  248. // Stop if the last window was a modal.
  249. for i := len(wm.windows) - 1; i >= 0 && !lastModal; i-- {
  250. window := wm.windows[i].(Window)
  251. if !window.IsVisible() { // skip hidden windows
  252. continue
  253. }
  254. // if this is a modal window, then don't give a chance for
  255. // other windows to get mouse events
  256. lastModal = window.IsModal() // if true, will exit loop on the next iteration
  257. x, y := event.Position()
  258. if !inRect(window, x, y) {
  259. // skip this window since it is not hit
  260. continue
  261. }
  262. if action == tview.MouseLeftDown && window.HasBorder() {
  263. // initiate a drag operation
  264. if !window.HasFocus() {
  265. setFocus(window)
  266. }
  267. wx, wy, ww, wh := window.GetRect()
  268. wm.draggedEdge = EdgeNone
  269. switch {
  270. case y == wy+wh-1:
  271. switch {
  272. case x == wx:
  273. wm.draggedEdge = EdgeBottomLeft
  274. case x == wx+ww-1:
  275. wm.draggedEdge = EdgeBottomRight
  276. default:
  277. wm.draggedEdge = EdgeBottom
  278. }
  279. case x == wx:
  280. wm.draggedEdge = EdgeLeft
  281. case x == wx+ww-1:
  282. wm.draggedEdge = EdgeRight
  283. case y == wy:
  284. wm.draggedEdge = EdgeTop
  285. }
  286. if wm.draggedEdge != EdgeNone {
  287. // drag detected. Remember where the drag operation started
  288. wm.draggedWindow = window
  289. wm.dragOffsetX = x - wx
  290. wm.dragOffsetY = y - wy
  291. wm.Unlock()
  292. return true, nil
  293. }
  294. }
  295. wm.Unlock()
  296. // no drag operation detected.
  297. // pass the mouse events to the window itself.
  298. return window.MouseHandler()(action, event, setFocus)
  299. }
  300. wm.Unlock()
  301. return
  302. })
  303. }
  304. // InputHandler returns a handler which receives key events when it has focus.
  305. func (wm *Manager) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
  306. return wm.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
  307. wm.Lock()
  308. // Pass key events along to the window with highest Z that is visible and has focus
  309. var window Window
  310. for i := len(wm.windows) - 1; i >= 0; i-- {
  311. window = wm.windows[i].(Window)
  312. if window.HasFocus() {
  313. break
  314. }
  315. window = nil
  316. }
  317. wm.Unlock()
  318. if window != nil {
  319. inputHandler := window.InputHandler()
  320. if inputHandler != nil {
  321. inputHandler(event, setFocus)
  322. }
  323. }
  324. })
  325. }