...

Source file src/gitlab.com/tslocum/cview/box.go

Documentation: gitlab.com/tslocum/cview

     1  package cview
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/gdamore/tcell/v2"
     7  )
     8  
     9  // Box is the base Primitive for all widgets. It has a background color and
    10  // optional surrounding elements such as a border and a title. It does not have
    11  // inner text. Widgets embed Box and draw their text over it.
    12  type Box struct {
    13  	// The position of the rect.
    14  	x, y, width, height int
    15  
    16  	// Padding.
    17  	paddingTop, paddingBottom, paddingLeft, paddingRight int
    18  
    19  	// The inner rect reserved for the box's content.
    20  	innerX, innerY, innerWidth, innerHeight int
    21  
    22  	// Whether or not the box is visible.
    23  	visible bool
    24  
    25  	// The border color when the box has focus.
    26  	borderColorFocused tcell.Color
    27  
    28  	// The box's background color.
    29  	backgroundColor tcell.Color
    30  
    31  	// Whether or not the box's background is transparent.
    32  	backgroundTransparent bool
    33  
    34  	// Whether or not a border is drawn, reducing the box's space for content by
    35  	// two in width and height.
    36  	border bool
    37  
    38  	// The color of the border.
    39  	borderColor tcell.Color
    40  
    41  	// The style attributes of the border.
    42  	borderAttributes tcell.AttrMask
    43  
    44  	// The title. Only visible if there is a border, too.
    45  	title []byte
    46  
    47  	// The color of the title.
    48  	titleColor tcell.Color
    49  
    50  	// The alignment of the title.
    51  	titleAlign int
    52  
    53  	// Provides a way to find out if this box has focus. We always go through
    54  	// this interface because it may be overridden by implementing classes.
    55  	focus Focusable
    56  
    57  	// Whether or not this box has focus.
    58  	hasFocus bool
    59  
    60  	// Whether or not this box shows its focus.
    61  	showFocus bool
    62  
    63  	// An optional capture function which receives a key event and returns the
    64  	// event to be forwarded to the primitive's default input handler (nil if
    65  	// nothing should be forwarded).
    66  	inputCapture func(event *tcell.EventKey) *tcell.EventKey
    67  
    68  	// An optional function which is called before the box is drawn.
    69  	draw func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)
    70  
    71  	// An optional capture function which receives a mouse event and returns the
    72  	// event to be forwarded to the primitive's default mouse event handler (at
    73  	// least one nil if nothing should be forwarded).
    74  	mouseCapture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)
    75  
    76  	l sync.RWMutex
    77  }
    78  
    79  // NewBox returns a Box without a border.
    80  func NewBox() *Box {
    81  	b := &Box{
    82  		width:              15,
    83  		height:             10,
    84  		visible:            true,
    85  		backgroundColor:    Styles.PrimitiveBackgroundColor,
    86  		borderColor:        Styles.BorderColor,
    87  		titleColor:         Styles.TitleColor,
    88  		borderColorFocused: ColorUnset,
    89  		titleAlign:         AlignCenter,
    90  		showFocus:          true,
    91  	}
    92  	b.focus = b
    93  	b.updateInnerRect()
    94  	return b
    95  }
    96  
    97  func (b *Box) updateInnerRect() {
    98  	x, y, width, height := b.x, b.y, b.width, b.height
    99  
   100  	// Subtract border space
   101  	if b.border {
   102  		x++
   103  		y++
   104  		width -= 2
   105  		height -= 2
   106  	}
   107  
   108  	// Subtract padding
   109  	x, y, width, height =
   110  		x+b.paddingLeft,
   111  		y+b.paddingTop,
   112  		width-b.paddingLeft-b.paddingRight,
   113  		height-b.paddingTop-b.paddingBottom
   114  
   115  	if width < 0 {
   116  		width = 0
   117  	}
   118  	if height < 0 {
   119  		height = 0
   120  	}
   121  
   122  	b.innerX, b.innerY, b.innerWidth, b.innerHeight = x, y, width, height
   123  }
   124  
   125  // GetPadding returns the size of the padding around the box content.
   126  func (b *Box) GetPadding() (top, bottom, left, right int) {
   127  	b.l.RLock()
   128  	defer b.l.RUnlock()
   129  
   130  	return b.paddingTop, b.paddingBottom, b.paddingLeft, b.paddingRight
   131  }
   132  
   133  // SetPadding sets the size of the padding around the box content.
   134  func (b *Box) SetPadding(top, bottom, left, right int) {
   135  	b.l.Lock()
   136  	defer b.l.Unlock()
   137  
   138  	b.paddingTop, b.paddingBottom, b.paddingLeft, b.paddingRight = top, bottom, left, right
   139  
   140  	b.updateInnerRect()
   141  }
   142  
   143  // GetRect returns the current position of the rectangle, x, y, width, and
   144  // height.
   145  func (b *Box) GetRect() (int, int, int, int) {
   146  	b.l.RLock()
   147  	defer b.l.RUnlock()
   148  
   149  	return b.x, b.y, b.width, b.height
   150  }
   151  
   152  // GetInnerRect returns the position of the inner rectangle (x, y, width,
   153  // height), without the border and without any padding. Width and height values
   154  // will clamp to 0 and thus never be negative.
   155  func (b *Box) GetInnerRect() (int, int, int, int) {
   156  	b.l.RLock()
   157  	defer b.l.RUnlock()
   158  
   159  	return b.innerX, b.innerY, b.innerWidth, b.innerHeight
   160  }
   161  
   162  // SetRect sets a new position of the primitive. Note that this has no effect
   163  // if this primitive is part of a layout (e.g. Flex, Grid) or if it was added
   164  // like this:
   165  //
   166  //   application.SetRoot(b, true)
   167  func (b *Box) SetRect(x, y, width, height int) {
   168  	b.l.Lock()
   169  	defer b.l.Unlock()
   170  
   171  	b.x, b.y, b.width, b.height = x, y, width, height
   172  
   173  	b.updateInnerRect()
   174  }
   175  
   176  // SetVisible sets the flag indicating whether or not the box is visible.
   177  func (b *Box) SetVisible(v bool) {
   178  	b.l.Lock()
   179  	defer b.l.Unlock()
   180  
   181  	b.visible = v
   182  }
   183  
   184  // GetVisible returns a value indicating whether or not the box is visible.
   185  func (b *Box) GetVisible() bool {
   186  	b.l.RLock()
   187  	defer b.l.RUnlock()
   188  
   189  	return b.visible
   190  }
   191  
   192  // SetDrawFunc sets a callback function which is invoked after the box primitive
   193  // has been drawn. This allows you to add a more individual style to the box
   194  // (and all primitives which extend it).
   195  //
   196  // The function is provided with the box's dimensions (set via SetRect()). It
   197  // must return the box's inner dimensions (x, y, width, height) which will be
   198  // returned by GetInnerRect(), used by descendent primitives to draw their own
   199  // content.
   200  func (b *Box) SetDrawFunc(handler func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)) {
   201  	b.l.Lock()
   202  	defer b.l.Unlock()
   203  
   204  	b.draw = handler
   205  }
   206  
   207  // GetDrawFunc returns the callback function which was installed with
   208  // SetDrawFunc() or nil if no such function has been installed.
   209  func (b *Box) GetDrawFunc() func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) {
   210  	b.l.RLock()
   211  	defer b.l.RUnlock()
   212  
   213  	return b.draw
   214  }
   215  
   216  // WrapInputHandler wraps an input handler (see InputHandler()) with the
   217  // functionality to capture input (see SetInputCapture()) before passing it
   218  // on to the provided (default) input handler.
   219  //
   220  // This is only meant to be used by subclassing primitives.
   221  func (b *Box) WrapInputHandler(inputHandler func(*tcell.EventKey, func(p Primitive))) func(*tcell.EventKey, func(p Primitive)) {
   222  	return func(event *tcell.EventKey, setFocus func(p Primitive)) {
   223  		if b.inputCapture != nil {
   224  			event = b.inputCapture(event)
   225  		}
   226  		if event != nil && inputHandler != nil {
   227  			inputHandler(event, setFocus)
   228  		}
   229  	}
   230  }
   231  
   232  // InputHandler returns nil.
   233  func (b *Box) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
   234  	b.l.RLock()
   235  	defer b.l.RUnlock()
   236  
   237  	return b.WrapInputHandler(nil)
   238  }
   239  
   240  // SetInputCapture installs a function which captures key events before they are
   241  // forwarded to the primitive's default key event handler. This function can
   242  // then choose to forward that key event (or a different one) to the default
   243  // handler by returning it. If nil is returned, the default handler will not
   244  // be called.
   245  //
   246  // Providing a nil handler will remove a previously existing handler.
   247  //
   248  // Note that this function will not have an effect on primitives composed of
   249  // other primitives, such as Form, Flex, or Grid. Key events are only captured
   250  // by the primitives that have focus (e.g. InputField) and only one primitive
   251  // can have focus at a time. Composing primitives such as Form pass the focus on
   252  // to their contained primitives and thus never receive any key events
   253  // themselves. Therefore, they cannot intercept key events.
   254  func (b *Box) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) {
   255  	b.l.Lock()
   256  	defer b.l.Unlock()
   257  
   258  	b.inputCapture = capture
   259  }
   260  
   261  // GetInputCapture returns the function installed with SetInputCapture() or nil
   262  // if no such function has been installed.
   263  func (b *Box) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
   264  	b.l.RLock()
   265  	defer b.l.RUnlock()
   266  
   267  	return b.inputCapture
   268  }
   269  
   270  // WrapMouseHandler wraps a mouse event handler (see MouseHandler()) with the
   271  // functionality to capture mouse events (see SetMouseCapture()) before passing
   272  // them on to the provided (default) event handler.
   273  //
   274  // This is only meant to be used by subclassing primitives.
   275  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) {
   276  	return func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   277  		if b.mouseCapture != nil {
   278  			action, event = b.mouseCapture(action, event)
   279  		}
   280  		if event != nil && mouseHandler != nil {
   281  			consumed, capture = mouseHandler(action, event, setFocus)
   282  		}
   283  		return
   284  	}
   285  }
   286  
   287  // MouseHandler returns nil.
   288  func (b *Box) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   289  	return b.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   290  		if action == MouseLeftClick && b.InRect(event.Position()) {
   291  			setFocus(b)
   292  			consumed = true
   293  		}
   294  		return
   295  	})
   296  }
   297  
   298  // SetMouseCapture sets a function which captures mouse events (consisting of
   299  // the original tcell mouse event and the semantic mouse action) before they are
   300  // forwarded to the primitive's default mouse event handler. This function can
   301  // then choose to forward that event (or a different one) by returning it or
   302  // returning a nil mouse event, in which case the default handler will not be
   303  // called.
   304  //
   305  // Providing a nil handler will remove a previously existing handler.
   306  func (b *Box) SetMouseCapture(capture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)) {
   307  	b.mouseCapture = capture
   308  }
   309  
   310  // InRect returns true if the given coordinate is within the bounds of the box's
   311  // rectangle.
   312  func (b *Box) InRect(x, y int) bool {
   313  	rectX, rectY, width, height := b.GetRect()
   314  	return x >= rectX && x < rectX+width && y >= rectY && y < rectY+height
   315  }
   316  
   317  // GetMouseCapture returns the function installed with SetMouseCapture() or nil
   318  // if no such function has been installed.
   319  func (b *Box) GetMouseCapture() func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse) {
   320  	return b.mouseCapture
   321  }
   322  
   323  // SetBackgroundColor sets the box's background color.
   324  func (b *Box) SetBackgroundColor(color tcell.Color) {
   325  	b.l.Lock()
   326  	defer b.l.Unlock()
   327  
   328  	b.backgroundColor = color
   329  }
   330  
   331  // GetBackgroundColor returns the box's background color.
   332  func (b *Box) GetBackgroundColor() tcell.Color {
   333  	b.l.RLock()
   334  	defer b.l.RUnlock()
   335  	return b.backgroundColor
   336  }
   337  
   338  // SetBackgroundTransparent sets the flag indicating whether or not the box's
   339  // background is transparent.
   340  func (b *Box) SetBackgroundTransparent(transparent bool) {
   341  	b.l.Lock()
   342  	defer b.l.Unlock()
   343  
   344  	b.backgroundTransparent = transparent
   345  }
   346  
   347  // GetBorder returns a value indicating whether the box have a border
   348  // or not.
   349  func (b *Box) GetBorder() bool {
   350  	b.l.RLock()
   351  	defer b.l.RUnlock()
   352  	return b.border
   353  }
   354  
   355  // SetBorder sets the flag indicating whether or not the box should have a
   356  // border.
   357  func (b *Box) SetBorder(show bool) {
   358  	b.l.Lock()
   359  	defer b.l.Unlock()
   360  
   361  	b.border = show
   362  
   363  	b.updateInnerRect()
   364  }
   365  
   366  // SetBorderColor sets the box's border color.
   367  func (b *Box) SetBorderColor(color tcell.Color) {
   368  	b.l.Lock()
   369  	defer b.l.Unlock()
   370  
   371  	b.borderColor = color
   372  }
   373  
   374  // SetBorderColorFocused sets the box's border color when the box is focused.
   375  func (b *Box) SetBorderColorFocused(color tcell.Color) {
   376  	b.l.Lock()
   377  	defer b.l.Unlock()
   378  	b.borderColorFocused = color
   379  }
   380  
   381  // SetBorderAttributes sets the border's style attributes. You can combine
   382  // different attributes using bitmask operations:
   383  //
   384  //   box.SetBorderAttributes(tcell.AttrUnderline | tcell.AttrBold)
   385  func (b *Box) SetBorderAttributes(attr tcell.AttrMask) {
   386  	b.l.Lock()
   387  	defer b.l.Unlock()
   388  
   389  	b.borderAttributes = attr
   390  }
   391  
   392  // SetTitle sets the box's title.
   393  func (b *Box) SetTitle(title string) {
   394  	b.l.Lock()
   395  	defer b.l.Unlock()
   396  
   397  	b.title = []byte(title)
   398  }
   399  
   400  // GetTitle returns the box's current title.
   401  func (b *Box) GetTitle() string {
   402  	b.l.RLock()
   403  	defer b.l.RUnlock()
   404  
   405  	return string(b.title)
   406  }
   407  
   408  // SetTitleColor sets the box's title color.
   409  func (b *Box) SetTitleColor(color tcell.Color) {
   410  	b.l.Lock()
   411  	defer b.l.Unlock()
   412  
   413  	b.titleColor = color
   414  }
   415  
   416  // SetTitleAlign sets the alignment of the title, one of AlignLeft, AlignCenter,
   417  // or AlignRight.
   418  func (b *Box) SetTitleAlign(align int) {
   419  	b.l.Lock()
   420  	defer b.l.Unlock()
   421  
   422  	b.titleAlign = align
   423  }
   424  
   425  // Draw draws this primitive onto the screen.
   426  func (b *Box) Draw(screen tcell.Screen) {
   427  	b.l.Lock()
   428  	defer b.l.Unlock()
   429  
   430  	// Don't draw anything if the box is hidden
   431  	if !b.visible {
   432  		return
   433  	}
   434  
   435  	// Don't draw anything if there is no space.
   436  	if b.width <= 0 || b.height <= 0 {
   437  		return
   438  	}
   439  
   440  	def := tcell.StyleDefault
   441  
   442  	// Fill background.
   443  	background := def.Background(b.backgroundColor)
   444  	if !b.backgroundTransparent {
   445  		for y := b.y; y < b.y+b.height; y++ {
   446  			for x := b.x; x < b.x+b.width; x++ {
   447  				screen.SetContent(x, y, ' ', nil, background)
   448  			}
   449  		}
   450  	}
   451  
   452  	// Draw border.
   453  	if b.border && b.width >= 2 && b.height >= 2 {
   454  		border := SetAttributes(background.Foreground(b.borderColor), b.borderAttributes)
   455  		var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
   456  
   457  		var hasFocus bool
   458  		if b.focus == b {
   459  			hasFocus = b.hasFocus
   460  		} else {
   461  			hasFocus = b.focus.HasFocus()
   462  		}
   463  
   464  		if hasFocus && b.borderColorFocused != ColorUnset {
   465  			border = SetAttributes(background.Foreground(b.borderColorFocused), b.borderAttributes)
   466  		}
   467  
   468  		if hasFocus && b.showFocus {
   469  			horizontal = Borders.HorizontalFocus
   470  			vertical = Borders.VerticalFocus
   471  			topLeft = Borders.TopLeftFocus
   472  			topRight = Borders.TopRightFocus
   473  			bottomLeft = Borders.BottomLeftFocus
   474  			bottomRight = Borders.BottomRightFocus
   475  		} else {
   476  			horizontal = Borders.Horizontal
   477  			vertical = Borders.Vertical
   478  			topLeft = Borders.TopLeft
   479  			topRight = Borders.TopRight
   480  			bottomLeft = Borders.BottomLeft
   481  			bottomRight = Borders.BottomRight
   482  		}
   483  		for x := b.x + 1; x < b.x+b.width-1; x++ {
   484  			screen.SetContent(x, b.y, horizontal, nil, border)
   485  			screen.SetContent(x, b.y+b.height-1, horizontal, nil, border)
   486  		}
   487  		for y := b.y + 1; y < b.y+b.height-1; y++ {
   488  			screen.SetContent(b.x, y, vertical, nil, border)
   489  			screen.SetContent(b.x+b.width-1, y, vertical, nil, border)
   490  		}
   491  		screen.SetContent(b.x, b.y, topLeft, nil, border)
   492  		screen.SetContent(b.x+b.width-1, b.y, topRight, nil, border)
   493  		screen.SetContent(b.x, b.y+b.height-1, bottomLeft, nil, border)
   494  		screen.SetContent(b.x+b.width-1, b.y+b.height-1, bottomRight, nil, border)
   495  
   496  		// Draw title.
   497  		if len(b.title) > 0 && b.width >= 4 {
   498  			printed, _ := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
   499  			if len(b.title)-printed > 0 && printed > 0 {
   500  				_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
   501  				fg, _, _ := style.Decompose()
   502  				Print(screen, []byte(string(SemigraphicsHorizontalEllipsis)), b.x+b.width-2, b.y, 1, AlignLeft, fg)
   503  			}
   504  		}
   505  	}
   506  
   507  	// Call custom draw function.
   508  	if b.draw != nil {
   509  		b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.draw(screen, b.x, b.y, b.width, b.height)
   510  	}
   511  }
   512  
   513  // ShowFocus sets the flag indicating whether or not the borders of this
   514  // primitive should change thickness when focused.
   515  func (b *Box) ShowFocus(showFocus bool) {
   516  	b.l.Lock()
   517  	defer b.l.Unlock()
   518  
   519  	b.showFocus = showFocus
   520  }
   521  
   522  // Focus is called when this primitive receives focus.
   523  func (b *Box) Focus(delegate func(p Primitive)) {
   524  	b.l.Lock()
   525  	defer b.l.Unlock()
   526  
   527  	b.hasFocus = true
   528  }
   529  
   530  // Blur is called when this primitive loses focus.
   531  func (b *Box) Blur() {
   532  	b.l.Lock()
   533  	defer b.l.Unlock()
   534  
   535  	b.hasFocus = false
   536  }
   537  
   538  // HasFocus returns whether or not this primitive has focus.
   539  func (b *Box) HasFocus() bool {
   540  	b.l.RLock()
   541  	defer b.l.RUnlock()
   542  
   543  	return b.hasFocus
   544  }
   545  
   546  // GetFocusable returns the item's Focusable.
   547  func (b *Box) GetFocusable() Focusable {
   548  	b.l.RLock()
   549  	defer b.l.RUnlock()
   550  
   551  	return b.focus
   552  }
   553  
   554  // GetBorderPadding returns the size of the padding around the box content.
   555  //
   556  // Deprecated: This function is provided for backwards compatibility.
   557  // Developers should use GetPadding instead.
   558  func (b *Box) GetBorderPadding() (top, bottom, left, right int) {
   559  	return b.GetPadding()
   560  }
   561  
   562  // SetBorderPadding sets the size of the padding around the box content.
   563  //
   564  // Deprecated: This function is provided for backwards compatibility.
   565  // Developers should use SetPadding instead.
   566  func (b *Box) SetBorderPadding(top, bottom, left, right int) {
   567  	b.SetPadding(top, bottom, left, right)
   568  }
   569  

View as plain text