github.com/jmigpin/editor@v1.6.0/ui/ui.go (about) 1 package ui 2 3 import ( 4 "image" 5 6 "github.com/jmigpin/editor/util/uiutil" 7 ) 8 9 type UI struct { 10 *uiutil.BasicUI 11 Root *Root 12 OnError func(error) 13 } 14 15 func NewUI(winName string) (*UI, error) { 16 ui := &UI{} 17 18 ui.Root = NewRoot(ui) 19 20 bui, err := uiutil.NewBasicUI(winName, ui.Root) 21 if err != nil { 22 return nil, err 23 } 24 ui.BasicUI = bui 25 26 // set theme before root init 27 c1 := &ColorThemeCycler 28 c1.Set(c1.CurName, ui.Root) 29 c2 := &FontThemeCycler 30 c2.Set(c2.CurName, ui.Root) 31 32 // build ui - needs ui.BasicUI to be set 33 ui.Root.Init() 34 35 return ui, nil 36 } 37 38 //---------- 39 40 func (ui *UI) WarpPointerToRectanglePad(r image.Rectangle) { 41 p, err := ui.QueryPointer() 42 if err != nil { 43 return 44 } 45 46 pad := 5 47 48 set := func(v *int, min, max int) { 49 if max-min < pad*2 { 50 *v = min + (max-min)/2 51 } else { 52 if *v < min+pad { 53 *v = min + pad 54 } else if *v > max-pad { 55 *v = max - pad 56 } 57 } 58 } 59 60 set(&p.X, r.Min.X, r.Max.X) 61 set(&p.Y, r.Min.Y, r.Max.Y) 62 63 ui.WarpPointer(p) 64 } 65 66 //---------- 67 68 func (ui *UI) resizeRowToGoodSize(row *Row) { 69 if row.PrevSibling() == nil { 70 return 71 } 72 prevRow := row.PrevSiblingWrapper().(*Row) 73 b := ui.rowInsertionBounds(prevRow) 74 col := row.Col 75 colDy := col.Bounds.Dy() 76 perc := float64(b.Min.Y-col.Bounds.Min.Y) / float64(colDy) 77 col.RowsLayout.Spl.Resize(row, perc) 78 } 79 80 //---------- 81 82 func (ui *UI) GoodRowPos() *RowPos { 83 var best struct { 84 area int 85 col *Column 86 nextRow *Row 87 } 88 89 // default position if nothing better is found 90 best.col = ui.Root.Cols.FirstChildColumn() 91 92 for _, c := range ui.Root.Cols.Columns() { 93 rows := c.Rows() 94 95 // space before first row 96 s := c.Bounds.Size() 97 if len(rows) > 0 { 98 s.Y = rows[0].Bounds.Min.Y - c.Bounds.Min.Y 99 } 100 a := s.X * s.Y 101 if a > best.area { 102 best.area = a 103 best.col = c 104 best.nextRow = nil 105 if len(rows) > 0 { 106 best.nextRow = rows[0] 107 } 108 } 109 110 // space between rows 111 for _, r := range rows { 112 b := ui.rowInsertionBounds(r) 113 s := b.Size() 114 a := s.X * s.Y 115 if a > best.area { 116 best.area = a 117 best.col = c 118 best.nextRow = r.NextRow() 119 } 120 } 121 } 122 123 return NewRowPos(best.col, best.nextRow) 124 } 125 126 //---------- 127 128 func (ui *UI) rowInsertionBounds(prevRow *Row) image.Rectangle { 129 ta := prevRow.TextArea 130 if r2, ok := ui.boundsAfterVisibleCursor(ta); ok { 131 return *r2 132 } else if r3, ok := ui.boundsOfTwoThirds(ta); ok { 133 return *r3 134 } else { 135 b := prevRow.Bounds 136 b.Max = b.Max.Sub(b.Size().Div(2)) // half size from StartPercentLayout 137 return b 138 } 139 } 140 141 func (ui *UI) boundsAfterVisibleCursor(ta *TextArea) (*image.Rectangle, bool) { 142 ci := ta.CursorIndex() 143 if !ta.IndexVisible(ci) { 144 return nil, false 145 } 146 p := ta.GetPoint(ci) 147 lh := ta.LineHeight() 148 r := ta.Bounds 149 r.Min.Y = p.Y + lh 150 r = ta.Bounds.Intersect(r) 151 if r.Dy() < lh*2 { 152 return nil, false 153 } 154 return &r, true 155 } 156 157 func (ui *UI) boundsOfTwoThirds(ta *TextArea) (*image.Rectangle, bool) { 158 lh := ta.LineHeight() 159 if ta.Bounds.Size().Y < lh { 160 return nil, false 161 } 162 b := ta.Bounds 163 b.Min.Y = b.Max.Y - (ta.Bounds.Dy() * 2 / 3) 164 r := ta.Bounds.Intersect(b) 165 return &r, true 166 } 167 168 //---------- 169 170 func (ui *UI) Error(err error) { 171 if ui.OnError != nil { 172 ui.OnError(err) 173 } 174 } 175 176 //---------- 177 178 type RowPos struct { 179 Column *Column 180 NextRow *Row 181 } 182 183 func NewRowPos(col *Column, nextRow *Row) *RowPos { 184 return &RowPos{col, nextRow} 185 }