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