...

Source file src/github.com/gdamore/tcell/v2/views/boxlayout.go

Documentation: github.com/gdamore/tcell/v2/views

     1  // Copyright 2016 The Tcell Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use file except in compliance with the License.
     5  // You may obtain a copy of the license at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package views
    16  
    17  import (
    18  	"github.com/gdamore/tcell/v2"
    19  )
    20  
    21  // BoxLayout is a container Widget that lays out its child widgets in
    22  // either a horizontal row or a vertical column.
    23  type BoxLayout struct {
    24  	view    View
    25  	orient  Orientation
    26  	style   tcell.Style // backing style
    27  	cells   []*boxLayoutCell
    28  	width   int
    29  	height  int
    30  	changed bool
    31  
    32  	WidgetWatchers
    33  }
    34  
    35  type boxLayoutCell struct {
    36  	widget Widget
    37  	fill   float64 // fill factor - 0.0 means no expansion
    38  	pad    int     // count of padding spaces (stretch)
    39  	frac   float64 // calculated residual spacing, used internally
    40  	view   *ViewPort
    41  }
    42  
    43  func (b *BoxLayout) hLayout() {
    44  	w, h := b.view.Size()
    45  
    46  	totf := 0.0
    47  	for _, c := range b.cells {
    48  		x, y := c.widget.Size()
    49  		totf += c.fill
    50  		b.width += x
    51  		if y > b.height {
    52  			b.height = y
    53  		}
    54  		c.pad = 0
    55  		c.frac = 0
    56  	}
    57  
    58  	extra := w - b.width
    59  	if extra < 0 {
    60  		extra = 0
    61  	}
    62  	resid := extra
    63  	if totf == 0 {
    64  		resid = 0
    65  	}
    66  
    67  	for _, c := range b.cells {
    68  		if c.fill > 0 {
    69  			c.frac = float64(extra) * c.fill / totf
    70  			c.pad = int(c.frac)
    71  			c.frac -= float64(c.pad)
    72  			resid -= c.pad
    73  		}
    74  	}
    75  
    76  	// Distribute any left over padding.  We try to give it to the
    77  	// the cells with the highest residual fraction.  It should be
    78  	// the case that no single cell gets more than one more cell.
    79  	for resid > 0 {
    80  		var best *boxLayoutCell
    81  		for _, c := range b.cells {
    82  			if c.fill == 0 {
    83  				continue
    84  			}
    85  			if best == nil || c.frac > best.frac {
    86  				best = c
    87  			}
    88  		}
    89  		best.pad++
    90  		best.frac = 0
    91  		resid--
    92  	}
    93  
    94  	x, y, xinc := 0, 0, 0
    95  	for _, c := range b.cells {
    96  		cw, _ := c.widget.Size()
    97  
    98  		xinc = cw + c.pad
    99  		cw += c.pad
   100  
   101  		c.view.Resize(x, y, cw, h)
   102  		c.widget.Resize()
   103  		x += xinc
   104  	}
   105  }
   106  
   107  func (b *BoxLayout) vLayout() {
   108  	w, h := b.view.Size()
   109  
   110  	totf := 0.0
   111  	for _, c := range b.cells {
   112  		x, y := c.widget.Size()
   113  		b.height += y
   114  		totf += c.fill
   115  		if x > b.width {
   116  			b.width = x
   117  		}
   118  		c.pad = 0
   119  		c.frac = 0
   120  	}
   121  
   122  	extra := h - b.height
   123  	if extra < 0 {
   124  		extra = 0
   125  	}
   126  
   127  	resid := extra
   128  	if totf == 0 {
   129  		resid = 0
   130  	}
   131  
   132  	for _, c := range b.cells {
   133  		if c.fill > 0 {
   134  			c.frac = float64(extra) * c.fill / totf
   135  			c.pad = int(c.frac)
   136  			c.frac -= float64(c.pad)
   137  			resid -= c.pad
   138  		}
   139  	}
   140  
   141  	// Distribute any left over padding.  We try to give it to the
   142  	// the cells with the highest residual fraction.  It should be
   143  	// the case that no single cell gets more than one more cell.
   144  	for resid > 0 {
   145  		var best *boxLayoutCell
   146  		for _, c := range b.cells {
   147  			if c.fill == 0 {
   148  				continue
   149  			}
   150  			if best == nil || c.frac > best.frac {
   151  				best = c
   152  			}
   153  		}
   154  		best.pad++
   155  		best.frac = 0
   156  		resid--
   157  	}
   158  
   159  	x, y, yinc := 0, 0, 0
   160  	for _, c := range b.cells {
   161  		_, ch := c.widget.Size()
   162  
   163  		yinc = ch + c.pad
   164  		ch += c.pad
   165  		c.view.Resize(x, y, w, ch)
   166  		c.widget.Resize()
   167  		y += yinc
   168  	}
   169  }
   170  
   171  func (b *BoxLayout) layout() {
   172  	if b.view == nil {
   173  		return
   174  	}
   175  	b.width, b.height = 0, 0
   176  	switch b.orient {
   177  	case Horizontal:
   178  		b.hLayout()
   179  	case Vertical:
   180  		b.vLayout()
   181  	default:
   182  		panic("Bad orientation")
   183  	}
   184  	b.changed = false
   185  }
   186  
   187  // Resize adjusts the layout when the underlying View changes size.
   188  func (b *BoxLayout) Resize() {
   189  	b.layout()
   190  
   191  	// Now also let the children know we resized.
   192  	for i := range b.cells {
   193  		b.cells[i].widget.Resize()
   194  	}
   195  	b.PostEventWidgetResize(b)
   196  }
   197  
   198  // Draw is called to update the displayed content.
   199  func (b *BoxLayout) Draw() {
   200  
   201  	if b.view == nil {
   202  		return
   203  	}
   204  	if b.changed {
   205  		b.layout()
   206  	}
   207  	b.view.Fill(' ', b.style)
   208  	for i := range b.cells {
   209  		b.cells[i].widget.Draw()
   210  	}
   211  }
   212  
   213  // Size returns the preferred size in character cells (width, height).
   214  func (b *BoxLayout) Size() (int, int) {
   215  	return b.width, b.height
   216  }
   217  
   218  // SetView sets the View object used for the text bar.
   219  func (b *BoxLayout) SetView(view View) {
   220  	b.changed = true
   221  	b.view = view
   222  	for _, c := range b.cells {
   223  		c.view.SetView(view)
   224  	}
   225  }
   226  
   227  // HandleEvent implements a tcell.EventHandler.  The only events
   228  // we care about are Widget change events from our children. We
   229  // watch for those so that if the child changes, we can arrange
   230  // to update our layout.
   231  func (b *BoxLayout) HandleEvent(ev tcell.Event) bool {
   232  	switch ev.(type) {
   233  	case *EventWidgetContent:
   234  		// This can only have come from one of our children.
   235  		b.changed = true
   236  		b.PostEventWidgetContent(b)
   237  		return true
   238  	}
   239  	for _, c := range b.cells {
   240  		if c.widget.HandleEvent(ev) {
   241  			return true
   242  		}
   243  	}
   244  	return false
   245  }
   246  
   247  // AddWidget adds a widget to the end of the BoxLayout.
   248  func (b *BoxLayout) AddWidget(widget Widget, fill float64) {
   249  	c := &boxLayoutCell{
   250  		widget: widget,
   251  		fill:   fill,
   252  		view:   NewViewPort(b.view, 0, 0, 0, 0),
   253  	}
   254  	widget.SetView(c.view)
   255  	b.cells = append(b.cells, c)
   256  	b.changed = true
   257  	widget.Watch(b)
   258  	b.layout()
   259  	b.PostEventWidgetContent(b)
   260  }
   261  
   262  // InsertWidget inserts a widget at the given offset.  Offset 0 is the
   263  // front.  If the index is longer than the number of widgets, then it
   264  // just gets appended to the end.
   265  func (b *BoxLayout) InsertWidget(index int, widget Widget, fill float64) {
   266  	c := &boxLayoutCell{
   267  		widget: widget,
   268  		fill:   fill,
   269  		view:   NewViewPort(b.view, 0, 0, 0, 0),
   270  	}
   271  	c.widget.SetView(c.view)
   272  	if index < 0 {
   273  		index = 0
   274  	}
   275  	if index > len(b.cells) {
   276  		index = len(b.cells)
   277  	}
   278  	b.cells = append(b.cells, c)
   279  	copy(b.cells[index+1:], b.cells[index:])
   280  	b.cells[index] = c
   281  	widget.Watch(b)
   282  	b.layout()
   283  	b.PostEventWidgetContent(b)
   284  }
   285  
   286  // RemoveWidget removes a Widget from the layout.
   287  func (b *BoxLayout) RemoveWidget(widget Widget) {
   288  	changed := false
   289  	for i := 0; i < len(b.cells); i++ {
   290  		if b.cells[i].widget == widget {
   291  			b.cells = append(b.cells[:i], b.cells[i+1:]...)
   292  			changed = true
   293  		}
   294  	}
   295  	if !changed {
   296  		return
   297  	}
   298  	b.changed = true
   299  	widget.Unwatch(b)
   300  	b.layout()
   301  	b.PostEventWidgetContent(b)
   302  }
   303  
   304  // Widgets returns the list of Widgets for this BoxLayout.
   305  func (b *BoxLayout) Widgets() []Widget {
   306  	w := make([]Widget, 0, len(b.cells))
   307  	for _, c := range b.cells {
   308  		w = append(w, c.widget)
   309  	}
   310  	return w
   311  }
   312  
   313  // SetOrientation sets the orientation as either Horizontal or Vertical.
   314  func (b *BoxLayout) SetOrientation(orient Orientation) {
   315  	if b.orient != orient {
   316  		b.orient = orient
   317  		b.changed = true
   318  		b.PostEventWidgetContent(b)
   319  	}
   320  }
   321  
   322  // SetStyle sets the style used.
   323  func (b *BoxLayout) SetStyle(style tcell.Style) {
   324  	b.style = style
   325  	b.PostEventWidgetContent(b)
   326  }
   327  
   328  // NewBoxLayout creates an empty BoxLayout.
   329  func NewBoxLayout(orient Orientation) *BoxLayout {
   330  	return &BoxLayout{orient: orient}
   331  }
   332  

View as plain text