...

Source file src/github.com/rivo/tview/box.go

Documentation: github.com/rivo/tview

     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 InputHandler()) with the
   157  // functionality to capture input (see 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.
   173  func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
   174  	return b.WrapInputHandler(nil)
   175  }
   176  
   177  // SetInputCapture installs a function which captures key events before they are
   178  // forwarded to the primitive's default key event handler. This function can
   179  // then choose to forward that key event (or a different one) to the default
   180  // handler by returning it. If nil is returned, the default handler will not
   181  // be called.
   182  //
   183  // Providing a nil handler will remove a previously existing handler.
   184  //
   185  // This function can also be used on container primitives (like Flex, Grid, or
   186  // Form) as keyboard events will be handed down until they are handled.
   187  func (b *Box) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Box {
   188  	b.inputCapture = capture
   189  	return b
   190  }
   191  
   192  // GetInputCapture returns the function installed with SetInputCapture() or nil
   193  // if no such function has been installed.
   194  func (b *Box) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
   195  	return b.inputCapture
   196  }
   197  
   198  // WrapMouseHandler wraps a mouse event handler (see MouseHandler()) with the
   199  // functionality to capture mouse events (see SetMouseCapture()) before passing
   200  // them on to the provided (default) event handler.
   201  //
   202  // This is only meant to be used by subclassing primitives.
   203  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) {
   204  	return func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   205  		if b.mouseCapture != nil {
   206  			action, event = b.mouseCapture(action, event)
   207  		}
   208  		if event != nil && mouseHandler != nil {
   209  			consumed, capture = mouseHandler(action, event, setFocus)
   210  		}
   211  		return
   212  	}
   213  }
   214  
   215  // MouseHandler returns nil.
   216  func (b *Box) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   217  	return b.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   218  		if action == MouseLeftDown && b.InRect(event.Position()) {
   219  			setFocus(b)
   220  			consumed = true
   221  		}
   222  		return
   223  	})
   224  }
   225  
   226  // SetMouseCapture sets a function which captures mouse events (consisting of
   227  // the original tcell mouse event and the semantic mouse action) before they are
   228  // forwarded to the primitive's default mouse event handler. This function can
   229  // then choose to forward that event (or a different one) by returning it or
   230  // returning a nil mouse event, in which case the default handler will not be
   231  // called.
   232  //
   233  // Providing a nil handler will remove a previously existing handler.
   234  func (b *Box) SetMouseCapture(capture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)) *Box {
   235  	b.mouseCapture = capture
   236  	return b
   237  }
   238  
   239  // InRect returns true if the given coordinate is within the bounds of the box's
   240  // rectangle.
   241  func (b *Box) InRect(x, y int) bool {
   242  	rectX, rectY, width, height := b.GetRect()
   243  	return x >= rectX && x < rectX+width && y >= rectY && y < rectY+height
   244  }
   245  
   246  // GetMouseCapture returns the function installed with SetMouseCapture() or nil
   247  // if no such function has been installed.
   248  func (b *Box) GetMouseCapture() func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse) {
   249  	return b.mouseCapture
   250  }
   251  
   252  // SetBackgroundColor sets the box's background color.
   253  func (b *Box) SetBackgroundColor(color tcell.Color) *Box {
   254  	b.backgroundColor = color
   255  	b.borderStyle = b.borderStyle.Background(color)
   256  	return b
   257  }
   258  
   259  // SetBorder sets the flag indicating whether or not the box should have a
   260  // border.
   261  func (b *Box) SetBorder(show bool) *Box {
   262  	b.border = show
   263  	return b
   264  }
   265  
   266  // SetBorderStyle sets the box's border style.
   267  func (b *Box) SetBorderStyle(style tcell.Style) *Box {
   268  	b.borderStyle = style
   269  	return b
   270  }
   271  
   272  // SetBorderColor sets the box's border color.
   273  func (b *Box) SetBorderColor(color tcell.Color) *Box {
   274  	b.borderStyle = b.borderStyle.Foreground(color)
   275  	return b
   276  }
   277  
   278  // SetBorderAttributes sets the border's style attributes. You can combine
   279  // different attributes using bitmask operations:
   280  //
   281  //	box.SetBorderAttributes(tcell.AttrUnderline | tcell.AttrBold)
   282  func (b *Box) SetBorderAttributes(attr tcell.AttrMask) *Box {
   283  	b.borderStyle = b.borderStyle.Attributes(attr)
   284  	return b
   285  }
   286  
   287  // GetBorderAttributes returns the border's style attributes.
   288  func (b *Box) GetBorderAttributes() tcell.AttrMask {
   289  	_, _, attr := b.borderStyle.Decompose()
   290  	return attr
   291  }
   292  
   293  // GetBorderColor returns the box's border color.
   294  func (b *Box) GetBorderColor() tcell.Color {
   295  	color, _, _ := b.borderStyle.Decompose()
   296  	return color
   297  }
   298  
   299  // GetBackgroundColor returns the box's background color.
   300  func (b *Box) GetBackgroundColor() tcell.Color {
   301  	return b.backgroundColor
   302  }
   303  
   304  // SetTitle sets the box's title.
   305  func (b *Box) SetTitle(title string) *Box {
   306  	b.title = title
   307  	return b
   308  }
   309  
   310  // GetTitle returns the box's current title.
   311  func (b *Box) GetTitle() string {
   312  	return b.title
   313  }
   314  
   315  // SetTitleColor sets the box's title color.
   316  func (b *Box) SetTitleColor(color tcell.Color) *Box {
   317  	b.titleColor = color
   318  	return b
   319  }
   320  
   321  // SetTitleAlign sets the alignment of the title, one of AlignLeft, AlignCenter,
   322  // or AlignRight.
   323  func (b *Box) SetTitleAlign(align int) *Box {
   324  	b.titleAlign = align
   325  	return b
   326  }
   327  
   328  // Draw draws this primitive onto the screen.
   329  func (b *Box) Draw(screen tcell.Screen) {
   330  	b.DrawForSubclass(screen, b)
   331  }
   332  
   333  // DrawForSubclass draws this box under the assumption that primitive p is a
   334  // subclass of this box. This is needed e.g. to draw proper box frames which
   335  // depend on the subclass's focus.
   336  //
   337  // Only call this function from your own custom primitives. It is not needed in
   338  // applications that have no custom primitives.
   339  func (b *Box) DrawForSubclass(screen tcell.Screen, p Primitive) {
   340  	// Don't draw anything if there is no space.
   341  	if b.width <= 0 || b.height <= 0 {
   342  		return
   343  	}
   344  
   345  	// Fill background.
   346  	background := tcell.StyleDefault.Background(b.backgroundColor)
   347  	if !b.dontClear {
   348  		for y := b.y; y < b.y+b.height; y++ {
   349  			for x := b.x; x < b.x+b.width; x++ {
   350  				screen.SetContent(x, y, ' ', nil, background)
   351  			}
   352  		}
   353  	}
   354  
   355  	// Draw border.
   356  	if b.border && b.width >= 2 && b.height >= 2 {
   357  		var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
   358  		if p.HasFocus() {
   359  			horizontal = Borders.HorizontalFocus
   360  			vertical = Borders.VerticalFocus
   361  			topLeft = Borders.TopLeftFocus
   362  			topRight = Borders.TopRightFocus
   363  			bottomLeft = Borders.BottomLeftFocus
   364  			bottomRight = Borders.BottomRightFocus
   365  		} else {
   366  			horizontal = Borders.Horizontal
   367  			vertical = Borders.Vertical
   368  			topLeft = Borders.TopLeft
   369  			topRight = Borders.TopRight
   370  			bottomLeft = Borders.BottomLeft
   371  			bottomRight = Borders.BottomRight
   372  		}
   373  		for x := b.x + 1; x < b.x+b.width-1; x++ {
   374  			screen.SetContent(x, b.y, horizontal, nil, b.borderStyle)
   375  			screen.SetContent(x, b.y+b.height-1, horizontal, nil, b.borderStyle)
   376  		}
   377  		for y := b.y + 1; y < b.y+b.height-1; y++ {
   378  			screen.SetContent(b.x, y, vertical, nil, b.borderStyle)
   379  			screen.SetContent(b.x+b.width-1, y, vertical, nil, b.borderStyle)
   380  		}
   381  		screen.SetContent(b.x, b.y, topLeft, nil, b.borderStyle)
   382  		screen.SetContent(b.x+b.width-1, b.y, topRight, nil, b.borderStyle)
   383  		screen.SetContent(b.x, b.y+b.height-1, bottomLeft, nil, b.borderStyle)
   384  		screen.SetContent(b.x+b.width-1, b.y+b.height-1, bottomRight, nil, b.borderStyle)
   385  
   386  		// Draw title.
   387  		if b.title != "" && b.width >= 4 {
   388  			printed, _ := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
   389  			if len(b.title)-printed > 0 && printed > 0 {
   390  				_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
   391  				fg, _, _ := style.Decompose()
   392  				Print(screen, string(SemigraphicsHorizontalEllipsis), b.x+b.width-2, b.y, 1, AlignLeft, fg)
   393  			}
   394  		}
   395  	}
   396  
   397  	// Call custom draw function.
   398  	if b.draw != nil {
   399  		b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.draw(screen, b.x, b.y, b.width, b.height)
   400  	} else {
   401  		// Remember the inner rect.
   402  		b.innerX = -1
   403  		b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.GetInnerRect()
   404  	}
   405  }
   406  
   407  // SetFocusFunc sets a callback function which is invoked when this primitive
   408  // receives focus. Container primitives such as Flex or Grid may not be notified
   409  // if one of their descendents receive focus directly.
   410  //
   411  // Set to nil to remove the callback function.
   412  func (b *Box) SetFocusFunc(callback func()) *Box {
   413  	b.focus = callback
   414  	return b
   415  }
   416  
   417  // SetBlurFunc sets a callback function which is invoked when this primitive
   418  // loses focus. This does not apply to container primitives such as Flex or
   419  // Grid.
   420  //
   421  // Set to nil to remove the callback function.
   422  func (b *Box) SetBlurFunc(callback func()) *Box {
   423  	b.blur = callback
   424  	return b
   425  }
   426  
   427  // Focus is called when this primitive receives focus.
   428  func (b *Box) Focus(delegate func(p Primitive)) {
   429  	b.hasFocus = true
   430  	if b.focus != nil {
   431  		b.focus()
   432  	}
   433  }
   434  
   435  // Blur is called when this primitive loses focus.
   436  func (b *Box) Blur() {
   437  	if b.blur != nil {
   438  		b.blur()
   439  	}
   440  	b.hasFocus = false
   441  }
   442  
   443  // HasFocus returns whether or not this primitive has focus.
   444  func (b *Box) HasFocus() bool {
   445  	return b.hasFocus
   446  }
   447  

View as plain text