123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 |
- package tview
- import (
- "github.com/gdamore/tcell"
- )
- // Box implements the Primitive interface with an empty background and optional
- // elements such as a border and a title. Box itself does not hold any content
- // but serves as the superclass of all other primitives. Subclasses add their
- // own content, typically (but not necessarily) keeping their content within the
- // box's rectangle.
- //
- // Box provides a number of utility functions available to all primitives.
- //
- // See https://github.com/rivo/tview/wiki/Box for an example.
- type Box struct {
- // The position of the rect.
- x, y, width, height int
- // The inner rect reserved for the box's content.
- innerX, innerY, innerWidth, innerHeight int
- // Border padding.
- paddingTop, paddingBottom, paddingLeft, paddingRight int
- // The box's background color.
- backgroundColor tcell.Color
- // Whether or not a border is drawn, reducing the box's space for content by
- // two in width and height.
- border bool
- // The color of the border.
- borderColor tcell.Color
- // The style attributes of the border.
- borderAttributes tcell.AttrMask
- // The title. Only visible if there is a border, too.
- title string
- // The color of the title.
- titleColor tcell.Color
- // The alignment of the title.
- titleAlign int
- // Provides a way to find out if this box has focus. We always go through
- // this interface because it may be overridden by implementing classes.
- focus Focusable
- // Whether or not this box has focus.
- hasFocus bool
- // An optional capture function which receives a key event and returns the
- // event to be forwarded to the primitive's default input handler (nil if
- // nothing should be forwarded).
- inputCapture func(event *tcell.EventKey) *tcell.EventKey
- // An optional function which is called before the box is drawn.
- draw func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)
- // An optional capture function which receives a mouse event and returns the
- // event to be forwarded to the primitive's default mouse event handler (at
- // least one nil if nothing should be forwarded).
- mouseCapture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)
- }
- // NewBox returns a Box without a border.
- func NewBox() *Box {
- b := &Box{
- width: 15,
- height: 10,
- innerX: -1, // Mark as uninitialized.
- backgroundColor: Styles.PrimitiveBackgroundColor,
- borderColor: Styles.BorderColor,
- titleColor: Styles.TitleColor,
- titleAlign: AlignCenter,
- }
- b.focus = b
- return b
- }
- // SetBorderPadding sets the size of the borders around the box content.
- func (b *Box) SetBorderPadding(top, bottom, left, right int) *Box {
- b.paddingTop, b.paddingBottom, b.paddingLeft, b.paddingRight = top, bottom, left, right
- return b
- }
- // GetRect returns the current position of the rectangle, x, y, width, and
- // height.
- func (b *Box) GetRect() (int, int, int, int) {
- return b.x, b.y, b.width, b.height
- }
- // GetInnerRect returns the position of the inner rectangle (x, y, width,
- // height), without the border and without any padding. Width and height values
- // will clamp to 0 and thus never be negative.
- func (b *Box) GetInnerRect() (int, int, int, int) {
- if b.innerX >= 0 {
- return b.innerX, b.innerY, b.innerWidth, b.innerHeight
- }
- x, y, width, height := b.GetRect()
- if b.border {
- x++
- y++
- width -= 2
- height -= 2
- }
- x, y, width, height = x+b.paddingLeft,
- y+b.paddingTop,
- width-b.paddingLeft-b.paddingRight,
- height-b.paddingTop-b.paddingBottom
- if width < 0 {
- width = 0
- }
- if height < 0 {
- height = 0
- }
- return x, y, width, height
- }
- // SetRect sets a new position of the primitive. Note that this has no effect
- // if this primitive is part of a layout (e.g. Flex, Grid) or if it was added
- // like this:
- //
- // application.SetRoot(b, true)
- func (b *Box) SetRect(x, y, width, height int) {
- b.x = x
- b.y = y
- b.width = width
- b.height = height
- b.innerX = -1 // Mark inner rect as uninitialized.
- }
- // SetDrawFunc sets a callback function which is invoked after the box primitive
- // has been drawn. This allows you to add a more individual style to the box
- // (and all primitives which extend it).
- //
- // The function is provided with the box's dimensions (set via SetRect()). It
- // must return the box's inner dimensions (x, y, width, height) which will be
- // returned by GetInnerRect(), used by descendent primitives to draw their own
- // content.
- func (b *Box) SetDrawFunc(handler func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)) *Box {
- b.draw = handler
- return b
- }
- // GetDrawFunc returns the callback function which was installed with
- // SetDrawFunc() or nil if no such function has been installed.
- func (b *Box) GetDrawFunc() func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) {
- return b.draw
- }
- // WrapInputHandler wraps an input handler (see InputHandler()) with the
- // functionality to capture input (see SetInputCapture()) before passing it
- // on to the provided (default) input handler.
- //
- // This is only meant to be used by subclassing primitives.
- func (b *Box) WrapInputHandler(inputHandler func(*tcell.EventKey, func(p Primitive))) func(*tcell.EventKey, func(p Primitive)) {
- return func(event *tcell.EventKey, setFocus func(p Primitive)) {
- if b.inputCapture != nil {
- event = b.inputCapture(event)
- }
- if event != nil && inputHandler != nil {
- inputHandler(event, setFocus)
- }
- }
- }
- // InputHandler returns nil.
- func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
- return b.WrapInputHandler(nil)
- }
- // SetInputCapture installs a function which captures key events before they are
- // forwarded to the primitive's default key event handler. This function can
- // then choose to forward that key event (or a different one) to the default
- // handler by returning it. If nil is returned, the default handler will not
- // be called.
- //
- // Providing a nil handler will remove a previously existing handler.
- //
- // Note that this function will not have an effect on primitives composed of
- // other primitives, such as Form, Flex, or Grid. Key events are only captured
- // by the primitives that have focus (e.g. InputField) and only one primitive
- // can have focus at a time. Composing primitives such as Form pass the focus on
- // to their contained primitives and thus never receive any key events
- // themselves. Therefore, they cannot intercept key events.
- func (b *Box) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Box {
- b.inputCapture = capture
- return b
- }
- // GetInputCapture returns the function installed with SetInputCapture() or nil
- // if no such function has been installed.
- func (b *Box) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
- return b.inputCapture
- }
- // WrapMouseHandler wraps a mouse event handler (see MouseHandler()) with the
- // functionality to capture mouse events (see SetMouseCapture()) before passing
- // them on to the provided (default) event handler.
- //
- // This is only meant to be used by subclassing primitives.
- 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) {
- return func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
- if b.mouseCapture != nil {
- action, event = b.mouseCapture(action, event)
- }
- if event != nil && mouseHandler != nil {
- consumed, capture = mouseHandler(action, event, setFocus)
- }
- return
- }
- }
- // MouseHandler returns nil.
- func (b *Box) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
- return b.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
- if action == MouseLeftClick && b.InRect(event.Position()) {
- setFocus(b)
- consumed = true
- }
- return
- })
- }
- // SetMouseCapture sets a function which captures mouse events (consisting of
- // the original tcell mouse event and the semantic mouse action) before they are
- // forwarded to the primitive's default mouse event handler. This function can
- // then choose to forward that event (or a different one) by returning it or
- // returning a nil mouse event, in which case the default handler will not be
- // called.
- //
- // Providing a nil handler will remove a previously existing handler.
- func (b *Box) SetMouseCapture(capture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)) *Box {
- b.mouseCapture = capture
- return b
- }
- // InRect returns true if the given coordinate is within the bounds of the box's
- // rectangle.
- func (b *Box) InRect(x, y int) bool {
- rectX, rectY, width, height := b.GetRect()
- return x >= rectX && x < rectX+width && y >= rectY && y < rectY+height
- }
- // GetMouseCapture returns the function installed with SetMouseCapture() or nil
- // if no such function has been installed.
- func (b *Box) GetMouseCapture() func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse) {
- return b.mouseCapture
- }
- // SetBackgroundColor sets the box's background color.
- func (b *Box) SetBackgroundColor(color tcell.Color) *Box {
- b.backgroundColor = color
- return b
- }
- // SetBorder sets the flag indicating whether or not the box should have a
- // border.
- func (b *Box) SetBorder(show bool) *Box {
- b.border = show
- return b
- }
- // SetBorderColor sets the box's border color.
- func (b *Box) SetBorderColor(color tcell.Color) *Box {
- b.borderColor = color
- return b
- }
- // SetBorderAttributes sets the border's style attributes. You can combine
- // different attributes using bitmask operations:
- //
- // box.SetBorderAttributes(tcell.AttrUnderline | tcell.AttrBold)
- func (b *Box) SetBorderAttributes(attr tcell.AttrMask) *Box {
- b.borderAttributes = attr
- return b
- }
- // GetBorderAttributes returns the border's style attributes.
- func (b *Box) GetBorderAttributes() tcell.AttrMask {
- return b.borderAttributes
- }
- // GetBorderColor returns the box's border color.
- func (b *Box) GetBorderColor() tcell.Color {
- return b.borderColor
- }
- // GetBackgroundColor returns the box's background color.
- func (b *Box) GetBackgroundColor() tcell.Color {
- return b.backgroundColor
- }
- // SetTitle sets the box's title.
- func (b *Box) SetTitle(title string) *Box {
- b.title = title
- return b
- }
- // GetTitle returns the box's current title.
- func (b *Box) GetTitle() string {
- return b.title
- }
- // SetTitleColor sets the box's title color.
- func (b *Box) SetTitleColor(color tcell.Color) *Box {
- b.titleColor = color
- return b
- }
- // SetTitleAlign sets the alignment of the title, one of AlignLeft, AlignCenter,
- // or AlignRight.
- func (b *Box) SetTitleAlign(align int) *Box {
- b.titleAlign = align
- return b
- }
- // Draw draws this primitive onto the screen.
- func (b *Box) Draw(screen tcell.Screen) {
- // Don't draw anything if there is no space.
- if b.width <= 0 || b.height <= 0 {
- return
- }
- def := tcell.StyleDefault
- // Fill background.
- background := def.Background(b.backgroundColor)
- if b.backgroundColor != tcell.ColorDefault {
- for y := b.y; y < b.y+b.height; y++ {
- for x := b.x; x < b.x+b.width; x++ {
- screen.SetContent(x, y, ' ', nil, background)
- }
- }
- }
- // Draw border.
- if b.border && b.width >= 2 && b.height >= 2 {
- border := background.Foreground(b.borderColor) | tcell.Style(b.borderAttributes)
- var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
- if b.focus.HasFocus() {
- horizontal = Borders.HorizontalFocus
- vertical = Borders.VerticalFocus
- topLeft = Borders.TopLeftFocus
- topRight = Borders.TopRightFocus
- bottomLeft = Borders.BottomLeftFocus
- bottomRight = Borders.BottomRightFocus
- } else {
- horizontal = Borders.Horizontal
- vertical = Borders.Vertical
- topLeft = Borders.TopLeft
- topRight = Borders.TopRight
- bottomLeft = Borders.BottomLeft
- bottomRight = Borders.BottomRight
- }
- for x := b.x + 1; x < b.x+b.width-1; x++ {
- screen.SetContent(x, b.y, horizontal, nil, border)
- screen.SetContent(x, b.y+b.height-1, horizontal, nil, border)
- }
- for y := b.y + 1; y < b.y+b.height-1; y++ {
- screen.SetContent(b.x, y, vertical, nil, border)
- screen.SetContent(b.x+b.width-1, y, vertical, nil, border)
- }
- screen.SetContent(b.x, b.y, topLeft, nil, border)
- screen.SetContent(b.x+b.width-1, b.y, topRight, nil, border)
- screen.SetContent(b.x, b.y+b.height-1, bottomLeft, nil, border)
- screen.SetContent(b.x+b.width-1, b.y+b.height-1, bottomRight, nil, border)
- // Draw title.
- if b.title != "" && b.width >= 4 {
- printed, _ := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
- if len(b.title)-printed > 0 && printed > 0 {
- _, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
- fg, _, _ := style.Decompose()
- Print(screen, string(SemigraphicsHorizontalEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
- }
- }
- }
- // Call custom draw function.
- if b.draw != nil {
- b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.draw(screen, b.x, b.y, b.width, b.height)
- } else {
- // Remember the inner rect.
- b.innerX = -1
- b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.GetInnerRect()
- }
- }
- // Focus is called when this primitive receives focus.
- func (b *Box) Focus(delegate func(p Primitive)) {
- b.hasFocus = true
- }
- // Blur is called when this primitive loses focus.
- func (b *Box) Blur() {
- b.hasFocus = false
- }
- // HasFocus returns whether or not this primitive has focus.
- func (b *Box) HasFocus() bool {
- return b.hasFocus
- }
- // GetFocusable returns the item's Focusable.
- func (b *Box) GetFocusable() Focusable {
- return b.focus
- }
|