frame.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. package tview
  2. import (
  3. "github.com/gdamore/tcell/v2"
  4. )
  5. // frameText holds information about a line of text shown in the frame.
  6. type frameText struct {
  7. Text string // The text to be displayed.
  8. Header bool // true = place in header, false = place in footer.
  9. Align int // One of the Align constants.
  10. Color tcell.Color // The text color.
  11. }
  12. // Frame is a wrapper which adds space around another primitive. In addition,
  13. // the top area (header) and the bottom area (footer) may also contain text.
  14. //
  15. // See https://github.com/rivo/tview/wiki/Frame for an example.
  16. type Frame struct {
  17. *Box
  18. // The contained primitive. May be nil.
  19. primitive Primitive
  20. // The lines of text to be displayed.
  21. text []*frameText
  22. // Border spacing.
  23. top, bottom, header, footer, left, right int
  24. // Keep a reference in case we need it when we change the primitive.
  25. setFocus func(p Primitive)
  26. }
  27. // NewFrame returns a new frame around the given primitive. The primitive's
  28. // size will be changed to fit within this frame. The primitive may be nil, in
  29. // which case no other primitive is embedded in the frame.
  30. func NewFrame(primitive Primitive) *Frame {
  31. box := NewBox()
  32. f := &Frame{
  33. Box: box,
  34. primitive: primitive,
  35. top: 1,
  36. bottom: 1,
  37. header: 1,
  38. footer: 1,
  39. left: 1,
  40. right: 1,
  41. }
  42. return f
  43. }
  44. // SetPrimitive replaces the contained primitive with the given one. To remove
  45. // a primitive, set it to nil.
  46. func (f *Frame) SetPrimitive(p Primitive) *Frame {
  47. var hasFocus bool
  48. if f.primitive != nil {
  49. hasFocus = f.primitive.HasFocus()
  50. }
  51. f.primitive = p
  52. if hasFocus && f.setFocus != nil {
  53. f.setFocus(p) // Restore focus.
  54. }
  55. return f
  56. }
  57. // GetPrimitive returns the primitive contained in this frame.
  58. func (f *Frame) GetPrimitive() Primitive {
  59. return f.primitive
  60. }
  61. // AddText adds text to the frame. Set "header" to true if the text is to appear
  62. // in the header, above the contained primitive. Set it to false for it to
  63. // appear in the footer, below the contained primitive. "align" must be one of
  64. // the Align constants. Rows in the header are printed top to bottom, rows in
  65. // the footer are printed bottom to top. Note that long text can overlap as
  66. // different alignments will be placed on the same row.
  67. func (f *Frame) AddText(text string, header bool, align int, color tcell.Color) *Frame {
  68. f.text = append(f.text, &frameText{
  69. Text: text,
  70. Header: header,
  71. Align: align,
  72. Color: color,
  73. })
  74. return f
  75. }
  76. // Clear removes all text from the frame.
  77. func (f *Frame) Clear() *Frame {
  78. f.text = nil
  79. return f
  80. }
  81. // SetBorders sets the width of the frame borders as well as "header" and
  82. // "footer", the vertical space between the header and footer text and the
  83. // contained primitive (does not apply if there is no text).
  84. func (f *Frame) SetBorders(top, bottom, header, footer, left, right int) *Frame {
  85. f.top, f.bottom, f.header, f.footer, f.left, f.right = top, bottom, header, footer, left, right
  86. return f
  87. }
  88. // Draw draws this primitive onto the screen.
  89. func (f *Frame) Draw(screen tcell.Screen) {
  90. f.Box.DrawForSubclass(screen, f)
  91. // Calculate start positions.
  92. x, top, width, height := f.GetInnerRect()
  93. bottom := top + height - 1
  94. x += f.left
  95. top += f.top
  96. bottom -= f.bottom
  97. width -= f.left + f.right
  98. if width <= 0 || top >= bottom {
  99. return // No space left.
  100. }
  101. // Draw text.
  102. var rows [6]int // top-left, top-center, top-right, bottom-left, bottom-center, bottom-right.
  103. topMax := top
  104. bottomMin := bottom
  105. for _, text := range f.text {
  106. // Where do we place this text?
  107. var y int
  108. if text.Header {
  109. y = top + rows[text.Align]
  110. rows[text.Align]++
  111. if y >= bottomMin {
  112. continue
  113. }
  114. if y+1 > topMax {
  115. topMax = y + 1
  116. }
  117. } else {
  118. y = bottom - rows[3+text.Align]
  119. rows[3+text.Align]++
  120. if y <= topMax {
  121. continue
  122. }
  123. if y-1 < bottomMin {
  124. bottomMin = y - 1
  125. }
  126. }
  127. // Draw text.
  128. Print(screen, text.Text, x, y, width, text.Align, text.Color)
  129. }
  130. // Set the size of the contained primitive.
  131. if f.primitive != nil {
  132. if topMax > top {
  133. top = topMax + f.header
  134. }
  135. if bottomMin < bottom {
  136. bottom = bottomMin - f.footer
  137. }
  138. if top > bottom {
  139. return // No space for the primitive.
  140. }
  141. f.primitive.SetRect(x, top, width, bottom+1-top)
  142. // Finally, draw the contained primitive.
  143. f.primitive.Draw(screen)
  144. }
  145. }
  146. // Focus is called when this primitive receives focus.
  147. func (f *Frame) Focus(delegate func(p Primitive)) {
  148. f.setFocus = delegate
  149. if f.primitive != nil {
  150. delegate(f.primitive)
  151. } else {
  152. f.Box.Focus(delegate)
  153. }
  154. }
  155. // HasFocus returns whether or not this primitive has focus.
  156. func (f *Frame) HasFocus() bool {
  157. if f.primitive == nil {
  158. return f.Box.HasFocus()
  159. }
  160. return f.primitive.HasFocus()
  161. }
  162. // MouseHandler returns the mouse handler for this primitive.
  163. func (f *Frame) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  164. return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  165. if !f.InRect(event.Position()) {
  166. return false, nil
  167. }
  168. // Pass mouse events on to contained primitive.
  169. if f.primitive != nil {
  170. consumed, capture = f.primitive.MouseHandler()(action, event, setFocus)
  171. if consumed {
  172. return true, capture
  173. }
  174. }
  175. // Clicking on the frame parts.
  176. if action == MouseLeftDown {
  177. setFocus(f)
  178. consumed = true
  179. }
  180. return
  181. })
  182. }
  183. // InputHandler returns the handler for this primitive.
  184. func (f *Frame) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
  185. return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
  186. if f.primitive == nil {
  187. return
  188. }
  189. if handler := f.primitive.InputHandler(); handler != nil {
  190. handler(event, setFocus)
  191. return
  192. }
  193. })
  194. }