...

Source file src/code.rocketnine.space/tslocum/cview/box.go

Documentation: code.rocketnine.space/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. The screen is not cleared before drawing the
   340  // application. Overlaying transparent widgets directly onto the screen may
   341  // result in artifacts. To resolve this, add a blank, non-transparent Box to
   342  // the bottom layer of the interface via Panels, or set a handler via
   343  // SetBeforeDrawFunc which clears the screen.
   344  func (b *Box) SetBackgroundTransparent(transparent bool) {
   345  	b.l.Lock()
   346  	defer b.l.Unlock()
   347  
   348  	b.backgroundTransparent = transparent
   349  }
   350  
   351  // GetBorder returns a value indicating whether the box have a border
   352  // or not.
   353  func (b *Box) GetBorder() bool {
   354  	b.l.RLock()
   355  	defer b.l.RUnlock()
   356  	return b.border
   357  }
   358  
   359  // SetBorder sets the flag indicating whether or not the box should have a
   360  // border.
   361  func (b *Box) SetBorder(show bool) {
   362  	b.l.Lock()
   363  	defer b.l.Unlock()
   364  
   365  	b.border = show
   366  
   367  	b.updateInnerRect()
   368  }
   369  
   370  // SetBorderColor sets the box's border color.
   371  func (b *Box) SetBorderColor(color tcell.Color) {
   372  	b.l.Lock()
   373  	defer b.l.Unlock()
   374  
   375  	b.borderColor = color
   376  }
   377  
   378  // SetBorderColorFocused sets the box's border color when the box is focused.
   379  func (b *Box) SetBorderColorFocused(color tcell.Color) {
   380  	b.l.Lock()
   381  	defer b.l.Unlock()
   382  	b.borderColorFocused = color
   383  }
   384  
   385  // SetBorderAttributes sets the border's style attributes. You can combine
   386  // different attributes using bitmask operations:
   387  //
   388  //   box.SetBorderAttributes(tcell.AttrUnderline | tcell.AttrBold)
   389  func (b *Box) SetBorderAttributes(attr tcell.AttrMask) {
   390  	b.l.Lock()
   391  	defer b.l.Unlock()
   392  
   393  	b.borderAttributes = attr
   394  }
   395  
   396  // SetTitle sets the box's title.
   397  func (b *Box) SetTitle(title string) {
   398  	b.l.Lock()
   399  	defer b.l.Unlock()
   400  
   401  	b.title = []byte(title)
   402  }
   403  
   404  // GetTitle returns the box's current title.
   405  func (b *Box) GetTitle() string {
   406  	b.l.RLock()
   407  	defer b.l.RUnlock()
   408  
   409  	return string(b.title)
   410  }
   411  
   412  // SetTitleColor sets the box's title color.
   413  func (b *Box) SetTitleColor(color tcell.Color) {
   414  	b.l.Lock()
   415  	defer b.l.Unlock()
   416  
   417  	b.titleColor = color
   418  }
   419  
   420  // SetTitleAlign sets the alignment of the title, one of AlignLeft, AlignCenter,
   421  // or AlignRight.
   422  func (b *Box) SetTitleAlign(align int) {
   423  	b.l.Lock()
   424  	defer b.l.Unlock()
   425  
   426  	b.titleAlign = align
   427  }
   428  
   429  // Draw draws this primitive onto the screen.
   430  func (b *Box) Draw(screen tcell.Screen) {
   431  	b.l.Lock()
   432  	defer b.l.Unlock()
   433  
   434  	// Don't draw anything if the box is hidden
   435  	if !b.visible {
   436  		return
   437  	}
   438  
   439  	// Don't draw anything if there is no space.
   440  	if b.width <= 0 || b.height <= 0 {
   441  		return
   442  	}
   443  
   444  	def := tcell.StyleDefault
   445  
   446  	// Fill background.
   447  	background := def.Background(b.backgroundColor)
   448  	if !b.backgroundTransparent {
   449  		for y := b.y; y < b.y+b.height; y++ {
   450  			for x := b.x; x < b.x+b.width; x++ {
   451  				screen.SetContent(x, y, ' ', nil, background)
   452  			}
   453  		}
   454  	}
   455  
   456  	// Draw border.
   457  	if b.border && b.width >= 2 && b.height >= 2 {
   458  		border := SetAttributes(background.Foreground(b.borderColor), b.borderAttributes)
   459  		var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune
   460  
   461  		var hasFocus bool
   462  		if b.focus == b {
   463  			hasFocus = b.hasFocus
   464  		} else {
   465  			hasFocus = b.focus.HasFocus()
   466  		}
   467  
   468  		if hasFocus && b.borderColorFocused != ColorUnset {
   469  			border = SetAttributes(background.Foreground(b.borderColorFocused), b.borderAttributes)
   470  		}
   471  
   472  		if hasFocus && b.showFocus {
   473  			horizontal = Borders.HorizontalFocus
   474  			vertical = Borders.VerticalFocus
   475  			topLeft = Borders.TopLeftFocus
   476  			topRight = Borders.TopRightFocus
   477  			bottomLeft = Borders.BottomLeftFocus
   478  			bottomRight = Borders.BottomRightFocus
   479  		} else {
   480  			horizontal = Borders.Horizontal
   481  			vertical = Borders.Vertical
   482  			topLeft = Borders.TopLeft
   483  			topRight = Borders.TopRight
   484  			bottomLeft = Borders.BottomLeft
   485  			bottomRight = Borders.BottomRight
   486  		}
   487  		for x := b.x + 1; x < b.x+b.width-1; x++ {
   488  			screen.SetContent(x, b.y, horizontal, nil, border)
   489  			screen.SetContent(x, b.y+b.height-1, horizontal, nil, border)
   490  		}
   491  		for y := b.y + 1; y < b.y+b.height-1; y++ {
   492  			screen.SetContent(b.x, y, vertical, nil, border)
   493  			screen.SetContent(b.x+b.width-1, y, vertical, nil, border)
   494  		}
   495  		screen.SetContent(b.x, b.y, topLeft, nil, border)
   496  		screen.SetContent(b.x+b.width-1, b.y, topRight, nil, border)
   497  		screen.SetContent(b.x, b.y+b.height-1, bottomLeft, nil, border)
   498  		screen.SetContent(b.x+b.width-1, b.y+b.height-1, bottomRight, nil, border)
   499  
   500  		// Draw title.
   501  		if len(b.title) > 0 && b.width >= 4 {
   502  			printed, _ := Print(screen, b.title, b.x+1, b.y, b.width-2, b.titleAlign, b.titleColor)
   503  			if len(b.title)-printed > 0 && printed > 0 {
   504  				_, _, style, _ := screen.GetContent(b.x+b.width-2, b.y)
   505  				fg, _, _ := style.Decompose()
   506  				Print(screen, []byte(string(SemigraphicsHorizontalEllipsis)), b.x+b.width-2, b.y, 1, AlignLeft, fg)
   507  			}
   508  		}
   509  	}
   510  
   511  	// Call custom draw function.
   512  	if b.draw != nil {
   513  		b.innerX, b.innerY, b.innerWidth, b.innerHeight = b.draw(screen, b.x, b.y, b.width, b.height)
   514  	}
   515  }
   516  
   517  // ShowFocus sets the flag indicating whether or not the borders of this
   518  // primitive should change thickness when focused.
   519  func (b *Box) ShowFocus(showFocus bool) {
   520  	b.l.Lock()
   521  	defer b.l.Unlock()
   522  
   523  	b.showFocus = showFocus
   524  }
   525  
   526  // Focus is called when this primitive receives focus.
   527  func (b *Box) Focus(delegate func(p Primitive)) {
   528  	b.l.Lock()
   529  	defer b.l.Unlock()
   530  
   531  	b.hasFocus = true
   532  }
   533  
   534  // Blur is called when this primitive loses focus.
   535  func (b *Box) Blur() {
   536  	b.l.Lock()
   537  	defer b.l.Unlock()
   538  
   539  	b.hasFocus = false
   540  }
   541  
   542  // HasFocus returns whether or not this primitive has focus.
   543  func (b *Box) HasFocus() bool {
   544  	b.l.RLock()
   545  	defer b.l.RUnlock()
   546  
   547  	return b.hasFocus
   548  }
   549  
   550  // GetFocusable returns the item's Focusable.
   551  func (b *Box) GetFocusable() Focusable {
   552  	b.l.RLock()
   553  	defer b.l.RUnlock()
   554  
   555  	return b.focus
   556  }
   557  
   558  // GetBorderPadding returns the size of the padding around the box content.
   559  //
   560  // Deprecated: This function is provided for backwards compatibility.
   561  // Developers should use GetPadding instead.
   562  func (b *Box) GetBorderPadding() (top, bottom, left, right int) {
   563  	return b.GetPadding()
   564  }
   565  
   566  // SetBorderPadding sets the size of the padding around the box content.
   567  //
   568  // Deprecated: This function is provided for backwards compatibility.
   569  // Developers should use SetPadding instead.
   570  func (b *Box) SetBorderPadding(top, bottom, left, right int) {
   571  	b.SetPadding(top, bottom, left, right)
   572  }
   573  

View as plain text