nt

A sensible note-taking program
git clone git://git.laack.co/nt.git
Log | Files | Refs | README

flex.go (8120B)


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