package tview import ( "maunium.net/go/tcell" ) // Box implements Primitive with a background and optional elements such as a // border and a title. Most subclasses keep their content contained in the box // but don't necessarily have to. // // Note that all classes which subclass from Box will also have access to its // functions. // // 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 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 // If set to true, the inner rect of this box will be within the screen at the // last time the box was drawn. clampToScreen 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 capture function which receives a mouse event and returns the // event to be forwarded to the primitive's default mouse handler (nil if // nothing should be forwarded). mouseCapture func(event *tcell.EventMouse) *tcell.EventMouse pasteCapture func(event *tcell.EventPaste) *tcell.EventPaste // 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) } // 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, clampToScreen: true, } 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. 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 } return x + b.paddingLeft, y + b.paddingTop, width - b.paddingLeft - b.paddingRight, height - b.paddingTop - b.paddingBottom } // SetRect sets a new position of the primitive. func (b *Box) SetRect(x, y, width, height int) { b.x = x b.y = y b.width = width b.height = height } // 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. 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 handler (see MouseHandler()) with the // functionality to capture mouse events (see SetMouseCapture()) before passing it // on to the provided (default) mouse handler. // // This is only meant to be used by subclassing primitives. func (b *Box) WrapMouseHandler(mouseHandler func(*tcell.EventMouse, func(p Primitive))) func(*tcell.EventMouse, func(p Primitive)) { return func(event *tcell.EventMouse, setFocus func(p Primitive)) { if b.mouseCapture != nil { event = b.mouseCapture(event) } if event != nil && mouseHandler != nil { mouseHandler(event, setFocus) } } } // MouseHandler returns nil. func (b *Box) MouseHandler() func(event *tcell.EventMouse, setFocus func(p Primitive)) { return b.WrapMouseHandler(nil) } // SetMouseCapture installs a function which captures mouse events before they are // forwarded to the primitive's default mouse event handler. This function can // then choose to forward that mouse 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. func (b *Box) SetMouseCapture(capture func(event *tcell.EventMouse) *tcell.EventMouse) *Box { b.mouseCapture = capture return b } // GetMouseCapture returns the function installed with SetMouseCapture() or nil // if no such function has been installed. func (b *Box) GetMouseCapture() func(event *tcell.EventMouse) *tcell.EventMouse { return b.mouseCapture } func (b *Box) WrapPasteHandler(pasteHandler func(*tcell.EventPaste)) func(*tcell.EventPaste) { return func(event *tcell.EventPaste) { if b.pasteCapture != nil { event = b.pasteCapture(event) } if event != nil && pasteHandler != nil { pasteHandler(event) } } } func (b *Box) PasteHandler() func(event *tcell.EventPaste) { return b.WrapPasteHandler(func(event *tcell.EventPaste) { // Default paste handler just calls input handler with each character. inputHandler := b.InputHandler() for _, char := range event.Text() { inputHandler(tcell.NewEventKey(tcell.KeyRune, char, tcell.ModNone), nil) } }) } func (b *Box) SetPasteCapture(capture func(event *tcell.EventPaste) *tcell.EventPaste) *Box { b.pasteCapture = capture return b } func (b *Box) GetPasteCapture() func(event *tcell.EventPaste) *tcell.EventPaste { return b.pasteCapture } // 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 } func (b *Box) HasBorder() bool { return b.border } // SetBorderColor sets the box's border color. func (b *Box) SetBorderColor(color tcell.Color) *Box { b.borderColor = color return b } // SetTitle sets the box's title. func (b *Box) SetTitle(title string) *Box { b.title = title return b } // 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 } func (b *Box) GetBackgroundColor() tcell.Color { return b.backgroundColor } func (b *Box) GetBorderColor() tcell.Color { return b.borderColor } // 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) 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) var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune if b.focus.HasFocus() { vertical = GraphicsDbVertBar horizontal = GraphicsDbHorBar topLeft = GraphicsDbTopLeftCorner topRight = GraphicsDbTopRightCorner bottomLeft = GraphicsDbBottomLeftCorner bottomRight = GraphicsDbBottomRightCorner } else { vertical = GraphicsHoriBar horizontal = GraphicsVertBar topLeft = GraphicsTopLeftCorner topRight = GraphicsTopRightCorner bottomLeft = GraphicsBottomLeftCorner bottomRight = GraphicsBottomRightCorner } for x := b.x + 1; x < b.x+b.width-1; x++ { screen.SetContent(x, b.y, vertical, nil, border) screen.SetContent(x, b.y+b.height-1, vertical, nil, border) } for y := b.y + 1; y < b.y+b.height-1; y++ { screen.SetContent(b.x, y, horizontal, nil, border) screen.SetContent(b.x+b.width-1, y, horizontal, 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 StringWidth(b.title)-printed > 0 && printed > 0 { _, _, style, _ := screen.GetContent(b.x+b.width-2, b.y) fg, _, _ := style.Decompose() Print(screen, string(GraphicsEllipsis), 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() } // Clamp inner rect to screen. if b.clampToScreen { width, height := screen.Size() if b.innerX < 0 { b.innerWidth += b.innerX b.innerX = 0 } if b.innerX+b.innerWidth >= width { b.innerWidth = width - b.innerX } if b.innerY+b.innerHeight >= height { b.innerHeight = height - b.innerY } if b.innerY < 0 { b.innerHeight += b.innerY b.innerY = 0 } } } // 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 }