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