...

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

Documentation: github.com/rivo/tview

     1  package tview
     2  
     3  import (
     4  	"github.com/gdamore/tcell/v2"
     5  )
     6  
     7  // Configuration values.
     8  const (
     9  	FlexRow       = 0 // One item per row.
    10  	FlexColumn    = 1 // One item per column.
    11  	FlexRowCSS    = 1 // As defined in CSS, items distributed along a row.
    12  	FlexColumnCSS = 0 // As defined in CSS, items distributed within a column.
    13  )
    14  
    15  // flexItem holds layout options for one item.
    16  type flexItem struct {
    17  	Item       Primitive // The item to be positioned. May be nil for an empty item.
    18  	FixedSize  int       // The item's fixed size which may not be changed, 0 if it has no fixed size.
    19  	Proportion int       // The item's proportion.
    20  	Focus      bool      // Whether or not this item attracts the layout's focus.
    21  }
    22  
    23  // Flex is a basic implementation of the Flexbox layout. The contained
    24  // primitives are arranged horizontally or vertically. The way they are
    25  // distributed along that dimension depends on their layout settings, which is
    26  // either a fixed length or a proportional length. See AddItem() for details.
    27  //
    28  // See https://github.com/rivo/tview/wiki/Flex for an example.
    29  type Flex struct {
    30  	*Box
    31  
    32  	// The items to be positioned.
    33  	items []*flexItem
    34  
    35  	// FlexRow or FlexColumn.
    36  	direction int
    37  
    38  	// If set to true, Flex will use the entire screen as its available space
    39  	// instead its box dimensions.
    40  	fullScreen bool
    41  }
    42  
    43  // NewFlex returns a new flexbox layout container with no primitives and its
    44  // direction set to FlexColumn. To add primitives to this layout, see AddItem().
    45  // To change the direction, see SetDirection().
    46  //
    47  // Note that Box, the superclass of Flex, will not clear its contents so that
    48  // any nil flex items will leave their background unchanged. To clear a Flex's
    49  // background before any items are drawn, set it to a box with the desired
    50  // color:
    51  //
    52  //   flex.Box = NewBox()
    53  func NewFlex() *Flex {
    54  	f := &Flex{
    55  		direction: FlexColumn,
    56  	}
    57  	f.Box = NewBox()
    58  	f.Box.dontClear = true
    59  	return f
    60  }
    61  
    62  // SetDirection sets the direction in which the contained primitives are
    63  // distributed. This can be either FlexColumn (default) or FlexRow. Note that
    64  // these are the opposite of what you would expect coming from CSS. You may also
    65  // use FlexColumnCSS or FlexRowCSS, to remain in line with the CSS definition.
    66  func (f *Flex) SetDirection(direction int) *Flex {
    67  	f.direction = direction
    68  	return f
    69  }
    70  
    71  // SetFullScreen sets the flag which, when true, causes the flex layout to use
    72  // the entire screen space instead of whatever size it is currently assigned to.
    73  func (f *Flex) SetFullScreen(fullScreen bool) *Flex {
    74  	f.fullScreen = fullScreen
    75  	return f
    76  }
    77  
    78  // AddItem adds a new item to the container. The "fixedSize" argument is a width
    79  // or height that may not be changed by the layout algorithm. A value of 0 means
    80  // that its size is flexible and may be changed. The "proportion" argument
    81  // defines the relative size of the item compared to other flexible-size items.
    82  // For example, items with a proportion of 2 will be twice as large as items
    83  // with a proportion of 1. The proportion must be at least 1 if fixedSize == 0
    84  // (ignored otherwise).
    85  //
    86  // If "focus" is set to true, the item will receive focus when the Flex
    87  // primitive receives focus. If multiple items have the "focus" flag set to
    88  // true, the first one will receive focus.
    89  //
    90  // You can provide a nil value for the primitive. This will still consume screen
    91  // space but nothing will be drawn.
    92  func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *Flex {
    93  	f.items = append(f.items, &flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
    94  	return f
    95  }
    96  
    97  // RemoveItem removes all items for the given primitive from the container,
    98  // keeping the order of the remaining items intact.
    99  func (f *Flex) RemoveItem(p Primitive) *Flex {
   100  	for index := len(f.items) - 1; index >= 0; index-- {
   101  		if f.items[index].Item == p {
   102  			f.items = append(f.items[:index], f.items[index+1:]...)
   103  		}
   104  	}
   105  	return f
   106  }
   107  
   108  // GetItemCount returns the number of items in this container.
   109  func (f *Flex) GetItemCount() int {
   110  	return len(f.items)
   111  }
   112  
   113  // GetItem returns the primitive at the given index, starting with 0 for the
   114  // first primitive in this container.
   115  //
   116  // This function will panic for out of range indices.
   117  func (f *Flex) GetItem(index int) Primitive {
   118  	return f.items[index].Item
   119  }
   120  
   121  // Clear removes all items from the container.
   122  func (f *Flex) Clear() *Flex {
   123  	f.items = nil
   124  	return f
   125  }
   126  
   127  // ResizeItem sets a new size for the item(s) with the given primitive. If there
   128  // are multiple Flex items with the same primitive, they will all receive the
   129  // same size. For details regarding the size parameters, see AddItem().
   130  func (f *Flex) ResizeItem(p Primitive, fixedSize, proportion int) *Flex {
   131  	for _, item := range f.items {
   132  		if item.Item == p {
   133  			item.FixedSize = fixedSize
   134  			item.Proportion = proportion
   135  		}
   136  	}
   137  	return f
   138  }
   139  
   140  // Draw draws this primitive onto the screen.
   141  func (f *Flex) Draw(screen tcell.Screen) {
   142  	f.Box.DrawForSubclass(screen, f)
   143  
   144  	// Calculate size and position of the items.
   145  
   146  	// Do we use the entire screen?
   147  	if f.fullScreen {
   148  		width, height := screen.Size()
   149  		f.SetRect(0, 0, width, height)
   150  	}
   151  
   152  	// How much space can we distribute?
   153  	x, y, width, height := f.GetInnerRect()
   154  	var proportionSum int
   155  	distSize := width
   156  	if f.direction == FlexRow {
   157  		distSize = height
   158  	}
   159  	for _, item := range f.items {
   160  		if item.FixedSize > 0 {
   161  			distSize -= item.FixedSize
   162  		} else {
   163  			proportionSum += item.Proportion
   164  		}
   165  	}
   166  
   167  	// Calculate positions and draw items.
   168  	pos := x
   169  	if f.direction == FlexRow {
   170  		pos = y
   171  	}
   172  	for _, item := range f.items {
   173  		size := item.FixedSize
   174  		if size <= 0 {
   175  			if proportionSum > 0 {
   176  				size = distSize * item.Proportion / proportionSum
   177  				distSize -= size
   178  				proportionSum -= item.Proportion
   179  			} else {
   180  				size = 0
   181  			}
   182  		}
   183  		if item.Item != nil {
   184  			if f.direction == FlexColumn {
   185  				item.Item.SetRect(pos, y, size, height)
   186  			} else {
   187  				item.Item.SetRect(x, pos, width, size)
   188  			}
   189  		}
   190  		pos += size
   191  
   192  		if item.Item != nil {
   193  			if item.Item.HasFocus() {
   194  				defer item.Item.Draw(screen)
   195  			} else {
   196  				item.Item.Draw(screen)
   197  			}
   198  		}
   199  	}
   200  }
   201  
   202  // Focus is called when this primitive receives focus.
   203  func (f *Flex) Focus(delegate func(p Primitive)) {
   204  	for _, item := range f.items {
   205  		if item.Item != nil && item.Focus {
   206  			delegate(item.Item)
   207  			return
   208  		}
   209  	}
   210  	f.Box.Focus(delegate)
   211  }
   212  
   213  // HasFocus returns whether or not this primitive has focus.
   214  func (f *Flex) HasFocus() bool {
   215  	for _, item := range f.items {
   216  		if item.Item != nil && item.Item.HasFocus() {
   217  			return true
   218  		}
   219  	}
   220  	return f.Box.HasFocus()
   221  }
   222  
   223  // MouseHandler returns the mouse handler for this primitive.
   224  func (f *Flex) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   225  	return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   226  		if !f.InRect(event.Position()) {
   227  			return false, nil
   228  		}
   229  
   230  		// Pass mouse events along to the first child item that takes it.
   231  		for _, item := range f.items {
   232  			if item.Item == nil {
   233  				continue
   234  			}
   235  			consumed, capture = item.Item.MouseHandler()(action, event, setFocus)
   236  			if consumed {
   237  				return
   238  			}
   239  		}
   240  
   241  		return
   242  	})
   243  }
   244  
   245  // InputHandler returns the handler for this primitive.
   246  func (f *Flex) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
   247  	return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
   248  		for _, item := range f.items {
   249  			if item.Item != nil && item.Item.HasFocus() {
   250  				if handler := item.Item.InputHandler(); handler != nil {
   251  					handler(event, setFocus)
   252  					return
   253  				}
   254  			}
   255  		}
   256  	})
   257  }
   258  

View as plain text