window.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. package winman
  2. import (
  3. "fmt"
  4. "github.com/gdamore/tcell/v2"
  5. "github.com/rivo/tview"
  6. )
  7. // Window interface defines primitives that can be managed by the Window Manager
  8. type Window interface {
  9. tview.Primitive
  10. // IsModal defines this window as modal. When a window is modal, input cannot go to other windows
  11. IsModal() bool
  12. // IsMaximized returns true when the window is maximized and takes all the space available to the
  13. // Window Manager
  14. IsMaximized() bool
  15. // IsResizable returns true when the window can be resized by the user
  16. IsResizable() bool
  17. // IsDraggable returns true when the window can be moved by the user by dragging
  18. // the title bar
  19. IsDraggable() bool
  20. // IsVisible returns true when the window has to be drawn and can receive focus
  21. IsVisible() bool
  22. // HasBorder returns true if the window must have a border
  23. HasBorder() bool
  24. }
  25. // WindowBase defines a basic window
  26. type WindowBase struct {
  27. *tview.Box
  28. root tview.Primitive // The item contained in the window
  29. buttons []*Button // window buttons on the title bar
  30. border bool // whether to render a border
  31. restoreRect Rect // store previous coordinates after restoring from maximize
  32. maximized bool // whether the window is maximized to the entire window manager area
  33. Draggable bool //whether this window can be dragged around with the mouse
  34. Resizable bool // whether this window is user-resizable
  35. Modal bool // whether this window is modal
  36. Visible bool // whether this window is rendered
  37. }
  38. // NewWindow creates a new window
  39. func NewWindow() *WindowBase {
  40. window := &WindowBase{
  41. Box: tview.NewBox(), // initialize underlying box
  42. }
  43. window.restoreRect = NewRect(window.GetRect())
  44. window.SetBorder(true)
  45. return window
  46. }
  47. // SetRoot sets the main content of the window
  48. func (w *WindowBase) SetRoot(root tview.Primitive) *WindowBase {
  49. w.root = root
  50. return w
  51. }
  52. // GetRoot returns the primitive that represents the main content of the window
  53. func (w *WindowBase) GetRoot() tview.Primitive {
  54. return w.root
  55. }
  56. // SetModal makes this window modal. A modal window captures all input
  57. func (w *WindowBase) SetModal(modal bool) *WindowBase {
  58. w.Modal = modal
  59. return w
  60. }
  61. // IsModal returns true if this window is modal
  62. func (w *WindowBase) IsModal() bool {
  63. return w.Modal
  64. }
  65. // HasBorder returns true if this window has a border
  66. // windows without border cannot be resized or dragged by the user
  67. func (w *WindowBase) HasBorder() bool {
  68. return w.border
  69. }
  70. // SetBorder sets the flag indicating whether or not the box should have a
  71. // border.
  72. func (w *WindowBase) SetBorder(show bool) *WindowBase {
  73. w.border = show
  74. w.Box.SetBorder(show)
  75. return w
  76. }
  77. // IsDraggable returns true if this window can be dragged by the user
  78. func (w *WindowBase) IsDraggable() bool {
  79. return w.Draggable
  80. }
  81. // SetDraggable sets if this window can be dragged by the user
  82. func (w *WindowBase) SetDraggable(draggable bool) *WindowBase {
  83. w.Draggable = draggable
  84. return w
  85. }
  86. // IsResizable returns true if the user may resize this window
  87. func (w *WindowBase) IsResizable() bool {
  88. return w.Resizable
  89. }
  90. // SetResizable sets if this window can be resized
  91. func (w *WindowBase) SetResizable(resizable bool) *WindowBase {
  92. w.Resizable = resizable
  93. return w
  94. }
  95. // SetTitle sets the window title
  96. func (w *WindowBase) SetTitle(text string) *WindowBase {
  97. w.Box.SetTitle(text)
  98. return w
  99. }
  100. // IsVisible returns true if this window is rendered and may
  101. // get focus
  102. func (w *WindowBase) IsVisible() bool {
  103. return w.Visible
  104. }
  105. // Show makes the window visible
  106. func (w *WindowBase) Show() *WindowBase {
  107. w.Visible = true
  108. return w
  109. }
  110. // Hide hides this window
  111. func (w *WindowBase) Hide() *WindowBase {
  112. w.Visible = false
  113. return w
  114. }
  115. // Draw draws this primitive on to the screen
  116. func (w *WindowBase) Draw(screen tcell.Screen) {
  117. if w.HasFocus() { // if the window has focus, make sure the underlying box shows a thicker border
  118. w.Box.Focus(nil)
  119. } else {
  120. w.Box.Blur()
  121. }
  122. w.Box.Draw(screen) // draw the window frame
  123. // draw the underlying root primitive within the window bounds
  124. if w.root != nil {
  125. x, y, width, height := w.GetInnerRect()
  126. w.root.SetRect(x, y, width, height)
  127. w.root.Draw(NewClipRegion(screen, x, y, width, height))
  128. }
  129. // draw the window border
  130. if w.border {
  131. x, y, width, height := w.GetRect()
  132. screen = NewClipRegion(screen, x, y, width, height)
  133. for _, button := range w.buttons {
  134. buttonX, buttonY := button.offsetX+x, button.offsetY+y
  135. if button.offsetX < 0 {
  136. buttonX += width
  137. }
  138. if button.offsetY < 0 {
  139. buttonY += height
  140. }
  141. // render the window title buttons
  142. tview.Print(screen, tview.Escape(fmt.Sprintf("[%c]", button.Symbol)), buttonX-1, buttonY, 9, 0, tcell.ColorYellow)
  143. }
  144. }
  145. }
  146. // Maximize signals the window manager to resize this window to the maximum size available
  147. func (w *WindowBase) Maximize() *WindowBase {
  148. w.restoreRect = NewRect(w.GetRect())
  149. w.maximized = true
  150. return w
  151. }
  152. // IsMaximized returns true if this window is maximized
  153. func (w *WindowBase) IsMaximized() bool {
  154. return w.maximized
  155. }
  156. // Restore restores the window to the size it had before maximizing
  157. func (w *WindowBase) Restore() *WindowBase {
  158. w.SetRect(w.restoreRect.Rect())
  159. w.maximized = false
  160. return w
  161. }
  162. // Focus is called when this primitive receives focus.
  163. func (w *WindowBase) Focus(delegate func(p tview.Primitive)) {
  164. if w.root != nil {
  165. delegate(w.root)
  166. } else {
  167. delegate(w.Box)
  168. }
  169. w.Visible = true
  170. }
  171. // HasFocus returns whether or not this primitive has focus.
  172. func (w *WindowBase) HasFocus() bool {
  173. if !w.Visible {
  174. return false
  175. }
  176. if w.root != nil {
  177. return w.root.HasFocus()
  178. }
  179. return w.Box.HasFocus()
  180. }
  181. // MouseHandler returns a mouse handler for this primitive
  182. func (w *WindowBase) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
  183. return w.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
  184. if action == tview.MouseLeftClick {
  185. x, y := event.Position()
  186. wx, wy, width, _ := w.GetRect()
  187. // check if any window button was pressed
  188. // if the window does not have border, it cannot receive button events
  189. if y == wy && w.border {
  190. for _, button := range w.buttons {
  191. if button.offsetX >= 0 && x == wx+button.offsetX || button.offsetX < 0 && x == wx+width+button.offsetX {
  192. if button.OnClick != nil {
  193. button.OnClick()
  194. }
  195. return true, nil
  196. }
  197. }
  198. }
  199. }
  200. // pass on clicks to the root primitive, if any
  201. if w.root != nil {
  202. return w.root.MouseHandler()(action, event, setFocus)
  203. }
  204. return w.Box.MouseHandler()(action, event, setFocus)
  205. })
  206. }
  207. // InputHandler returns a handler which receives key events when it has focus.
  208. func (w *WindowBase) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
  209. if w.root != nil {
  210. return w.root.InputHandler()
  211. }
  212. return nil
  213. }
  214. // AddButton adds a new window button to the title bar
  215. func (w *WindowBase) AddButton(button *Button) *WindowBase {
  216. w.buttons = append(w.buttons, button)
  217. offsetLeft, offsetRight := 2, -3
  218. for _, button := range w.buttons {
  219. if button.Alignment == ButtonRight {
  220. button.offsetX = offsetRight
  221. offsetRight -= 3
  222. } else {
  223. button.offsetX = offsetLeft
  224. offsetLeft += 3
  225. }
  226. }
  227. return w
  228. }
  229. // GetButton returns the given button
  230. func (w *WindowBase) GetButton(i int) *Button {
  231. if i < 0 || i >= len(w.buttons) {
  232. return nil
  233. }
  234. return w.buttons[i]
  235. }
  236. // ButtonCount returns the number of buttons in the window title bar
  237. func (w *WindowBase) ButtonCount() int {
  238. return len(w.buttons)
  239. }