github.com/jmigpin/editor@v1.6.0/ui/row.go (about) 1 package ui 2 3 import ( 4 "image" 5 6 "github.com/jmigpin/editor/util/drawutil/drawer4" 7 "github.com/jmigpin/editor/util/evreg" 8 "github.com/jmigpin/editor/util/uiutil/event" 9 "github.com/jmigpin/editor/util/uiutil/widget" 10 ) 11 12 type Row struct { 13 *widget.BoxLayout 14 Toolbar *RowToolbar 15 TextArea *TextArea 16 Col *Column 17 EvReg evreg.Register 18 19 ScrollArea *widget.ScrollArea 20 sep *RowSeparator 21 ui *UI 22 } 23 24 func NewRow(col *Column) *Row { 25 row := &Row{Col: col, ui: col.Cols.Root.UI} 26 row.BoxLayout = widget.NewBoxLayout() 27 row.YAxis = true 28 29 // row separator from other rows 30 row.sep = NewRowSeparator(row) 31 row.Append(row.sep) 32 row.SetChildFill(row.sep, true, false) 33 34 // toolbar 35 row.Toolbar = NewRowToolbar(row) 36 row.Append(row.Toolbar) 37 row.SetChildFlex(row.Toolbar, true, false) 38 39 // scrollarea with textarea 40 { 41 row.TextArea = NewTextArea(row.ui) 42 row.TextArea.SupportClickInsideSelection = true 43 row.TextArea.EnableCursorWordHighlight(true) 44 row.TextArea.EnableParenthesisMatch(true) 45 if d, ok := row.TextArea.Drawer.(*drawer4.Drawer); ok { 46 d.Opt.QuickMeasure = true // performance 47 } 48 49 row.ScrollArea = widget.NewScrollArea(row.ui, row.TextArea, false, true) 50 row.ScrollArea.LeftScroll = ScrollBarLeft 51 52 container := WrapInTopShadowOrSeparator(row.ui, row.ScrollArea) 53 row.Append(container) 54 row.SetChildFlex(container, true, true) 55 } 56 57 return row 58 } 59 60 //---------- 61 62 func (row *Row) Close() { 63 row.Col.removeRow(row) 64 row.Col = nil 65 row.sep.Close() 66 row.EvReg.RunCallbacks(RowCloseEventId, &RowCloseEvent{row}) 67 } 68 69 //---------- 70 71 func (row *Row) OnChildMarked(child widget.Node, newMarks widget.Marks) { 72 // dynamic toolbar 73 if row.Toolbar != nil && row.Toolbar.HasAnyMarks(widget.MarkNeedsLayout) { 74 row.MarkNeedsLayout() 75 } 76 } 77 78 //---------- 79 80 func (row *Row) Layout() { 81 ff := row.Toolbar.TreeThemeFontFace() 82 row.ScrollArea.ScrollWidth = UIThemeUtil.GetScrollBarWidth(ff) 83 row.BoxLayout.Layout() 84 } 85 86 //---------- 87 88 func (row *Row) OnInputEvent(ev0 interface{}, p image.Point) event.Handled { 89 ev2 := &RowInputEvent{row, ev0} 90 row.EvReg.RunCallbacks(RowInputEventId, ev2) 91 return false 92 } 93 94 //---------- 95 96 func (row *Row) NextRow() *Row { 97 u := row.NextSiblingWrapper() 98 if u == nil { 99 return nil 100 } 101 return u.(*Row) 102 } 103 104 //---------- 105 106 func (row *Row) Maximize() { 107 col := row.Col 108 col.RowsLayout.Spl.MaximizeNode(row) 109 } 110 111 //---------- 112 113 func (row *Row) resizeWithMoveToPoint(p *image.Point) { 114 col, ok := row.Col.Cols.PointColumnExtra(p) 115 if !ok { 116 return 117 } 118 119 // move to another column 120 if col != row.Col { 121 next, ok := col.PointNextRowExtra(p) 122 if !ok { 123 next = nil 124 } 125 row.Col.removeRow(row) 126 col.insertRowBefore(row, next) 127 } 128 129 bounds := row.Col.Bounds 130 dy := float64(bounds.Dy()) 131 perc := float64(p.Sub(bounds.Min).Y) / dy 132 133 row.Col.RowsLayout.Spl.ResizeWithMove(row, perc) 134 } 135 136 //---------- 137 138 func (row *Row) resizeWithPushJump(up bool, p *image.Point) { 139 jump := 40 140 if up { 141 jump *= -1 142 } 143 144 pad := p.Sub(row.Bounds.Min) 145 146 p2 := row.Bounds.Min 147 p2.Y += jump 148 row.resizeWithPushToPoint(&p2) 149 150 // layout for accurate bounds, to warp pointer 151 row.Col.RowsLayout.Spl.Layout() 152 153 p3 := row.Bounds.Min.Add(pad) 154 p3.Y = row.Bounds.Min.Y // accurate y 155 row.ui.WarpPointer(p3) 156 } 157 158 func (row *Row) resizeWithPushToPoint(p *image.Point) { 159 col := row.Col 160 dy := float64(col.Bounds.Dy()) 161 perc := float64(p.Sub(col.Bounds.Min).Y) / dy 162 163 col.RowsLayout.Spl.SetPercentWithPush(row, perc) 164 } 165 166 //---------- 167 168 func (row *Row) EnsureTextAreaMinimumHeight() { 169 ta := row.TextArea 170 171 taMin := ta.LineHeight() * 3 172 if ta.Bounds.Dy() >= taMin { 173 return 174 } 175 176 hint := image.Point{row.Bounds.Dx(), row.Col.Bounds.Dy()} 177 tbm := row.Toolbar.Measure(hint) 178 minH := tbm.Y + taMin + 2 // pad to cover borders used 179 perc := float64(minH) / float64(row.Col.Bounds.Dy()) 180 181 row.Col.RowsLayout.Spl.SetSizePercentWithPush(row, perc) 182 } 183 184 func (row *Row) EnsureOneToolbarLineYVisible() { 185 minH := row.TextArea.LineHeight() 186 rowY := row.Bounds.Dy() 187 if rowY >= minH { 188 return 189 } 190 perc := float64(minH) / float64(row.Col.Bounds.Dy()) 191 row.Col.RowsLayout.Spl.SetSizePercentWithPush(row, perc) 192 } 193 194 //---------- 195 196 func (row *Row) SetState(s RowState, v bool) { 197 row.Toolbar.Square.SetState(s, v) 198 } 199 func (row *Row) HasState(s RowState) bool { 200 return row.Toolbar.Square.HasState(s) 201 } 202 203 //---------- 204 205 func (row *Row) PosBelow() *RowPos { 206 return NewRowPos(row.Col, row.NextRow()) 207 } 208 209 //---------- 210 211 const ( 212 RowInputEventId = iota 213 RowCloseEventId 214 ) 215 216 type RowInputEvent struct { 217 Row *Row 218 Event interface{} 219 } 220 type RowCloseEvent struct { 221 Row *Row 222 }