box.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. package tview
  2. import (
  3. "github.com/gdamore/tcell/v2"
  4. )
  5. // Box implements the Primitive interface with an empty background and optional
  6. // elements such as a border and a title. Box itself does not hold any content
  7. // but serves as the superclass of all other primitives. Subclasses add their
  8. // own content, typically (but not necessarily) keeping their content within the
  9. // box's rectangle.
  10. //
  11. // Box provides a number of utility functions available to all primitives.
  12. //
  13. // See https://github.com/rivo/tview/wiki/Box for an example.
  14. type Box struct {
  15. // The position of the rect.
  16. x, y, width, height int
  17. // The inner rect reserved for the box's content.
  18. innerX, innerY, innerWidth, innerHeight int
  19. // Border padding.
  20. paddingTop, paddingBottom, paddingLeft, paddingRight int
  21. // The box's background color.
  22. backgroundColor tcell.Color
  23. // If set to true, the background of this box is not cleared while drawing.
  24. dontClear bool
  25. // Whether or not a border is drawn, reducing the box's space for content by
  26. // two in width and height.
  27. border bool
  28. // The border style.
  29. borderStyle tcell.Style
  30. // The title. Only visible if there is a border, too.
  31. title string
  32. // The color of the title.
  33. titleColor tcell.Color
  34. // The alignment of the title.
  35. titleAlign int
  36. // Whether or not this box has focus. This is typically ignored for
  37. // container primitives (e.g. Flex, Grid, Pages), as they will delegate
  38. // focus to their children.
  39. hasFocus bool
  40. // Optional callback functions invoked when the primitive receives or loses
  41. // focus.
  42. focus, blur func()
  43. // An optional capture function which receives a key event and returns the
  44. // event to be forwarded to the primitive's default input handler (nil if
  45. // nothing should be forwarded).
  46. inputCapture func(event *tcell.EventKey) *tcell.EventKey
  47. // An optional function which is called before the box is drawn.
  48. draw func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)
  49. // An optional capture function which receives a mouse event and returns the
  50. // event to be forwarded to the primitive's default mouse event handler (at
  51. // least one nil if nothing should be forwarded).
  52. mouseCapture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)
  53. }
  54. // NewBox returns a Box without a border.
  55. func NewBox() *Box {
  56. b := &Box{
  57. width: 15,
  58. height: 10,
  59. innerX: -1, // Mark as uninitialized.
  60. backgroundColor: Styles.PrimitiveBackgroundColor,
  61. borderStyle: tcell.StyleDefault.Foreground(Styles.BorderColor).Background(Styles.PrimitiveBackgroundColor),
  62. titleColor: Styles.TitleColor,
  63. titleAlign: AlignCenter,
  64. }
  65. return b
  66. }
  67. // SetBorderPadding sets the size of the borders around the box content.
  68. func (b *Box) SetBorderPadding(top, bottom, left, right int) *Box {
  69. b.paddingTop, b.paddingBottom, b.paddingLeft, b.paddingRight = top, bottom, left, right
  70. return b
  71. }
  72. // GetRect returns the current position of the rectangle, x, y, width, and
  73. // height.
  74. func (b *Box) GetRect() (int, int, int, int) {
  75. return b.x, b.y, b.width, b.height
  76. }
  77. // GetInnerRect returns the position of the inner rectangle (x, y, width,
  78. // height), without the border and without any padding. Width and height values
  79. // will clamp to 0 and thus never be negative.
  80. func (b *Box) GetInnerRect() (int, int, int, int) {
  81. if b.innerX >= 0 {
  82. return b.innerX, b.innerY, b.innerWidth, b.innerHeight
  83. }
  84. x, y, width, height := b.GetRect()
  85. if b.border {
  86. x++
  87. y++
  88. width -= 2
  89. height -= 2
  90. }
  91. x, y, width, height = x+b.paddingLeft,
  92. y+b.paddingTop,
  93. width-b.paddingLeft-b.paddingRight,
  94. height-b.paddingTop-b.paddingBottom
  95. if width < 0 {
  96. width = 0
  97. }
  98. if height < 0 {
  99. height = 0
  100. }
  101. return x, y, width, height
  102. }
  103. // SetRect sets a new position of the primitive. Note that this has no effect
  104. // if this primitive is part of a layout (e.g. Flex, Grid) or if it was added
  105. // like this:
  106. //
  107. // application.SetRoot(p, true)
  108. func (b *Box) SetRect(x, y, width, height int) {
  109. b.x = x
  110. b.y = y
  111. b.width = width
  112. b.height = height
  113. b.innerX = -1 // Mark inner rect as uninitialized.
  114. }
  115. // SetDrawFunc sets a callback function which is invoked after the box primitive
  116. // has been drawn. This allows you to add a more individual style to the box
  117. // (and all primitives which extend it).
  118. //
  119. // The function is provided with the box's dimensions (set via SetRect()). It
  120. // must return the box's inner dimensions (x, y, width, height) which will be
  121. // returned by GetInnerRect(), used by descendent primitives to draw their own
  122. // content.
  123. func (b *Box) SetDrawFunc(handler func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)) *Box {
  124. b.draw = handler
  125. return b
  126. }
  127. // GetDrawFunc returns the callback function which was installed with
  128. // SetDrawFunc() or nil if no such function has been installed.
  129. func (b *Box) GetDrawFunc() func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) {
  130. return b.draw
  131. }
  132. // WrapInputHandler wraps an input handler (see InputHandler()) with the
  133. // functionality to capture input (see SetInputCapture()) before passing it
  134. // on to the provided (default) input handler.
  135. //
  136. // This is only meant to be used by subclassing primitives.
  137. func (b *Box) WrapInputHandler(inputHandler func(*tcell.EventKey, func(p Primitive))) func(*tcell.EventKey, func(p Primitive)) {
  138. return func(event *tcell.EventKey, setFocus func(p Primitive)) {
  139. if b.inputCapture != nil {
  140. event = b.inputCapture(event)
  141. }
  142. if event != nil && inputHandler != nil {
  143. inputHandler(event, setFocus)
  144. }
  145. }
  146. }
  147. // InputHandler returns nil.
  148. func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
  149. return b.WrapInputHandler(nil)
  150. }
  151. // SetInputCapture installs a function which captures key events before they are
  152. // forwarded to the primitive's default key event handler. This function can
  153. // then choose to forward that key event (or a different one) to the default
  154. // handler by returning it. If nil is returned, the default handler will not
  155. // be called.
  156. //
  157. // Providing a nil handler will remove a previously existing handler.
  158. //
  159. // This function can also be used on container primitives (like Flex, Grid, or
  160. // Form) as keyboard events will be handed down until they are handled.
  161. func (b *Box) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Box {
  162. b.inputCapture = capture
  163. return b
  164. }
  165. // GetInputCapture returns the function installed with SetInputCapture() or nil
  166. // if no such function has been installed.
  167. func (b *Box) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
  168. return b.inputCapture
  169. }
  170. // WrapMouseHandler wraps a mouse event handler (see MouseHandler()) with the
  171. // functionality to capture mouse events (see SetMouseCapture()) before passing
  172. // them on to the provided (default) event handler.
  173. //
  174. // This is only meant to be used by subclassing primitives.
  175. func (b *Box) WrapMouseHandler(mouseHandler func(MouseAction, *tcell.EventMouse, func(p Primitive)) (bool, Primitive)) func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  176. return func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  177. if b.mouseCapture != nil {
  178. action, event = b.mouseCapture(action, event)
  179. }
  180. if event != nil && mouseHandler != nil {
  181. consumed, capture = mouseHandler(action, event, setFocus)
  182. }
  183. return
  184. }
  185. }
  186. // MouseHandler returns nil.
  187. func (b *Box) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  188. return b.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  189. if action == MouseLeftDown && b.InRect(event.Position()) {
  190. setFocus(b)
  191. consumed = true
  192. }
  193. return
  194. })
  195. }
  196. // SetMouseCapture sets a function which captures mouse events (consisting of
  197. // the original tcell mouse event and the semantic mouse action) before they are
  198. // forwarded to the primitive's default mouse event handler. This function can
  199. // then choose to forward that event (or a different one) by returning it or
  200. // returning a nil mouse event, in which case the default handler will not be
  201. // called.
  202. //
  203. // Providing a nil handler will remove a previously existing handler.
  204. func (b *Box) SetMouseCapture(capture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)) *Box {
  205. b.mouseCapture = capture
  206. return b
  207. }
  208. // InRect returns true if the given coordinate is within the bounds of the box's
  209. // rectangle.
  210. func (b *Box) InRect(x, y int) bool {
  211. rectX, rectY, width, height := b.GetRect()
  212. return x >= rectX && x < rectX+width && y >= rectY && y < rectY+height
  213. }
  214. // GetMouseCapture returns the function installed with SetMouseCapture() or nil
  215. // if no such function has been installed.
  216. func (b *Box) GetMouseCapture() func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse) {
  217. return b.mouseCapture
  218. }
  219. // SetBackgroundColor sets the box's background color.
  220. func (b *Box) SetBackgroundColor(color tcell.Color) *Box {
  221. b.backgroundColor = color
  222. b.borderStyle = b.borderStyle.Background(color)
  223. return b
  224. }
  225. // SetBorder sets the flag indicating whether or not the box should have a
  226. // border.
  227. func (b *Box) SetBorder(show bool) *Box {
  228. b.border = show
  229. return b
  230. }
  231. // SetBorderStyle sets the box's border style.
  232. func (b *Box) SetBorderStyle(style tcell.Style) *Box {
  233. b.borderStyle = style
  234. return b
  235. }
  236. // SetBorderColor sets the box's border color.
  237. func (b *Box) SetBorderColor(color tcell.Color) *Box {
  238. b.borderStyle = b.borderStyle.Foreground(color)
  239. return b
  240. }
  241. // SetBorderAttributes sets the border's style attributes. You can combine
  242. // different attributes using bitmask operations:
  243. //
  244. // box.SetBorderAttributes(tcell.AttrUnderline | tcell.AttrBold)
  245. func (b *Box) SetBorderAttributes(attr tcell.AttrMask) *Box {
  246. b.borderStyle = b.borderStyle.Attributes(attr)
  247. return b
  248. }
  249. // GetBorderAttributes returns the border's style attributes.
  250. func (b *Box) GetBorderAttributes() tcell.AttrMask {
  251. _, _, attr := b.borderStyle.Decompose()
  252. return attr
  253. }
  254. // GetBorderColor returns the box's border color.
  255. func (b *Box) GetBorderColor() tcell.Color {
  256. color, _, _ := b.borderStyle.Decompose()
  257. return color
  258. }
  259. // GetBackgroundColor returns the box's background color.
  260. func (b *Box) GetBackgroundColor() tcell.Color {
  261. return b.backgroundColor
  262. }
  263. // SetTitle sets the box's title.
  264. func (b *Box) SetTitle(title string) *Box {
  265. b.title = title
  266. return b
  267. }
  268. // GetTitle returns the box's current title.
  269. func (b *Box) GetTitle() string {
  270. return b.title
  271. }
  272. // SetTitleColor sets the box's title color.
  273. func (b *Box) SetTitleColor(color tcell.Color) *Box {
  274. b.titleColor = color
  275. return b
  276. }
  277. // SetTitleAlign sets the alignment of the title, one of AlignLeft, AlignCenter,
  278. // or AlignRight.
  279. func (b *Box) SetTitleAlign(align int) *Box {
  280. b.titleAlign = align
  281. return b
  282. }
  283. // Draw draws this primitive onto the screen.
  284. func (b *Box) Draw(screen tcell.Screen) {
  285. b.DrawForSubclass(screen, b)
  286. }
  287. // DrawForSubclass draws this box under the assumption that primitive p is a
  288. // subclass of this box. This is needed e.g. to draw proper box frames which
  289. // depend on the subclass's focus.
  290. //
  291. // Only call this function from your own custom primitives. It is not needed in
  292. // applications that have no custom primitives.
  293. func (b *Box) DrawForSubclass(screen tcell.Screen, p Primitive) {
  294. // Don't draw anything if there is no space.
  295. if b.width <= 0 || b.height <= 0 {
  296. return
  297. }
  298. // Fill background.
  299. background := tcell.StyleDefault.Background(b.backgroundColor)
  300. if !b.dontClear {
  301. for y := b.y; y < b.y+b.height; y++ {
  302. for x := b.x; x < b.x+b.width; x++ {
  303. screen.SetContent(x, y, ' ', nil, background)
  304. }
  305. }
  306. }
  307. // Draw border.
  308. if b.border && b.width >= 2 && b.height >= 2 {
  309. var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
  310. if p.HasFocus() {
  311. horizontal = Borders.HorizontalFocus
  312. vertical = Borders.VerticalFocus
  313. topLeft = Borders.TopLeftFocus
  314. topRight = Borders.TopRightFocus
  315. bottomLeft = Borders.BottomLeftFocus
  316. bottomRight = Borders.BottomRightFocus
  317. } else {
  318. horizontal = Borders.Horizontal
  319. vertical = Borders.Vertical
  320. topLeft = Borders.TopLeft
  321. topRight = Borders.TopRight
  322. bottomLeft = Borders.BottomLeft
  323. bottomRight = Borders.BottomRight
  324. }
  325. for x := b.x + 1; x < b.x+b.width-1; x++ {
  326. screen.SetContent(x, b.y, horizontal, nil, b.borderStyle)
  327. screen.SetContent(x, b.y+b.height-1, horizontal, nil, b.borderStyle)
  328. }
  329. for y := b.y + 1; y < b.y+b.height-1; y++ {
  330. screen.SetContent(b.x, y, vertical, nil, b.borderStyle)
  331. screen.SetContent(b.x+b.width-1, y, vertical, nil, b.borderStyle)
  332. }
  333. screen.SetContent(b.x, b.y, topLeft, nil, b.borderStyle)
  334. screen.SetContent(b.x+b.width-1, b.y, topRight, nil, b.borderStyle)
  335. screen.SetContent(b.x, b.y+b.height-1, bottomLeft, nil, b.borderStyle)
  336. screen.SetContent(b.x+b.width-1, b.y+b.height-1, bottomRight, nil, b.borderStyle)
  337. // Draw title.
  338. if b.title != "" && b.width >= 4 {
  339. printed, _ := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
  340. if len(b.title)-printed > 0 && printed > 0 {
  341. _, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
  342. fg, _, _ := style.Decompose()
  343. Print(screen, string(SemigraphicsHorizontalEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
  344. }
  345. }
  346. }
  347. // Call custom draw function.
  348. if b.draw != nil {
  349. b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.draw(screen, b.x, b.y, b.width, b.height)
  350. } else {
  351. // Remember the inner rect.
  352. b.innerX = -1
  353. b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.GetInnerRect()
  354. }
  355. }
  356. // SetFocusFunc sets a callback function which is invoked when this primitive
  357. // receives focus. Container primitives such as Flex or Grid may not be notified
  358. // if one of their descendents receive focus directly.
  359. //
  360. // Set to nil to remove the callback function.
  361. func (b *Box) SetFocusFunc(callback func()) *Box {
  362. b.focus = callback
  363. return b
  364. }
  365. // SetBlurFunc sets a callback function which is invoked when this primitive
  366. // loses focus. This does not apply to container primitives such as Flex or
  367. // Grid.
  368. //
  369. // Set to nil to remove the callback function.
  370. func (b *Box) SetBlurFunc(callback func()) *Box {
  371. b.blur = callback
  372. return b
  373. }
  374. // Focus is called when this primitive receives focus.
  375. func (b *Box) Focus(delegate func(p Primitive)) {
  376. b.hasFocus = true
  377. if b.focus != nil {
  378. b.focus()
  379. }
  380. }
  381. // Blur is called when this primitive loses focus.
  382. func (b *Box) Blur() {
  383. if b.blur != nil {
  384. b.blur()
  385. }
  386. b.hasFocus = false
  387. }
  388. // HasFocus returns whether or not this primitive has focus.
  389. func (b *Box) HasFocus() bool {
  390. return b.hasFocus
  391. }