github.com/jmigpin/editor@v1.6.0/ui/textarea.go (about) 1 package ui 2 3 import ( 4 "image" 5 "unicode" 6 7 "github.com/jmigpin/editor/util/drawutil/drawer4" 8 "github.com/jmigpin/editor/util/evreg" 9 "github.com/jmigpin/editor/util/iout/iorw" 10 "github.com/jmigpin/editor/util/iout/iorw/rwedit" 11 "github.com/jmigpin/editor/util/uiutil/event" 12 "github.com/jmigpin/editor/util/uiutil/widget" 13 ) 14 15 type TextArea struct { 16 *widget.TextEditX 17 18 EvReg evreg.Register 19 SupportClickInsideSelection bool 20 21 ui *UI 22 } 23 24 func NewTextArea(ui *UI) *TextArea { 25 ta := &TextArea{ui: ui} 26 ta.TextEditX = widget.NewTextEditX(ui) 27 return ta 28 } 29 30 //---------- 31 32 func (ta *TextArea) OnInputEvent(ev0 interface{}, p image.Point) event.Handled { 33 h := event.Handled(false) 34 35 // input events callbacks (terminal related) 36 if !h { 37 ev2 := &TextAreaInputEvent{TextArea: ta, Event: ev0} 38 ta.EvReg.RunCallbacks(TextAreaInputEventId, ev2) 39 h = ev2.ReplyHandled 40 } 41 42 // select annotation events 43 if !h { 44 h = ta.handleInputEvent2(ev0, p) 45 // consider handled to avoid root events to select global annotations 46 if h { 47 return true 48 } 49 } 50 51 if !h { 52 h = ta.TextEditX.OnInputEvent(ev0, p) 53 // don't consider handled to allow ui.Row to get inputevents 54 if h { 55 return false 56 } 57 } 58 59 return h 60 } 61 62 func (ta *TextArea) handleInputEvent2(ev0 interface{}, p image.Point) event.Handled { 63 switch ev := ev0.(type) { 64 case *event.MouseClick: 65 switch ev.Button { 66 case event.ButtonRight: 67 m := ev.Mods.ClearLocks() 68 switch { 69 case m.Is(event.ModCtrl): 70 if ta.selAnnCurEv(ev.Point, TASelAnnTypePrint) { 71 return true 72 } 73 case m.Is(event.ModCtrl | event.ModShift): 74 if ta.selAnnCurEv(ev.Point, TASelAnnTypePrintAllPrevious) { 75 return true 76 } 77 } 78 if !ta.SupportClickInsideSelection || !ta.PointIndexInsideSelection(ev.Point) { 79 rwedit.MoveCursorToPoint(ta.EditCtx(), ev.Point, false) 80 } 81 i := ta.GetIndex(ev.Point) 82 ev2 := &TextAreaCmdEvent{ta, i} 83 ta.EvReg.RunCallbacks(TextAreaCmdEventId, ev2) 84 return true 85 } 86 case *event.MouseDown: 87 switch ev.Button { 88 case event.ButtonRight: 89 ta.ENode.Cursor = event.PointerCursor 90 case event.ButtonLeft: 91 m := ev.Mods.ClearLocks() 92 if m.Is(event.ModCtrl) { 93 if ta.selAnnCurEv(ev.Point, TASelAnnTypeCurrent) { 94 return true 95 } 96 } 97 case event.ButtonWheelUp: 98 m := ev.Mods.ClearLocks() 99 if m.Is(event.ModCtrl) { 100 if ta.selAnnCurEv(ev.Point, TASelAnnTypeCurrentPrev) { 101 return true 102 } 103 } 104 case event.ButtonWheelDown: 105 m := ev.Mods.ClearLocks() 106 if m.Is(event.ModCtrl) { 107 if ta.selAnnCurEv(ev.Point, TASelAnnTypeCurrentNext) { 108 return true 109 } 110 } 111 } 112 case *event.MouseUp: 113 switch ev.Button { 114 case event.ButtonRight: 115 ta.ENode.Cursor = event.NoneCursor 116 } 117 case *event.MouseDragStart: 118 switch ev.Button { 119 case event.ButtonRight: 120 ta.ENode.Cursor = event.NoneCursor 121 } 122 case *event.KeyDown: 123 m := ev.Mods.ClearLocks() 124 switch { 125 case m.Is(event.ModNone): 126 switch ev.KeySym { 127 case event.KSymTab: 128 return ta.inlineCompleteEv() 129 } 130 } 131 } 132 return false 133 } 134 135 //---------- 136 137 func (ta *TextArea) selAnnCurEv(p image.Point, typ TASelAnnType) bool { 138 if d, ok := ta.Drawer.(*drawer4.Drawer); ok { 139 if d.Opt.Annotations.On { 140 i, o, ok := d.AnnotationsIndexOf(p) 141 if ok { 142 ev2 := &TextAreaSelectAnnotationEvent{ta, i, o, typ} 143 ta.EvReg.RunCallbacks(TextAreaSelectAnnotationEventId, ev2) 144 return true 145 } 146 } 147 } 148 return false 149 } 150 func (ta *TextArea) selAnnEv(typ TASelAnnType) { 151 ev2 := &TextAreaSelectAnnotationEvent{ta, 0, 0, typ} 152 ta.EvReg.RunCallbacks(TextAreaSelectAnnotationEventId, ev2) 153 } 154 155 //---------- 156 157 func (ta *TextArea) inlineCompleteEv() event.Handled { 158 c := ta.Cursor() 159 if c.HaveSelection() { 160 return false 161 } 162 163 // previous rune should not be a space 164 ru, _, err := iorw.ReadRuneAt(ta.RW(), c.Index()-1) 165 if err != nil { 166 return false 167 } 168 if unicode.IsSpace(ru) { 169 return false 170 } 171 172 ev2 := &TextAreaInlineCompleteEvent{ta, c.Index(), false} 173 ta.EvReg.RunCallbacks(TextAreaInlineCompleteEventId, ev2) 174 return ev2.ReplyHandled 175 } 176 177 //---------- 178 179 func (ta *TextArea) PointIndexInsideSelection(p image.Point) bool { 180 c := ta.Cursor() 181 if s, e, ok := c.SelectionIndexes(); ok { 182 i := ta.GetIndex(p) 183 return i >= s && i < e 184 } 185 return false 186 } 187 188 //---------- 189 190 func (ta *TextArea) Layout() { 191 ta.TextEditX.Layout() 192 ta.setDrawer4Opts() 193 194 ev2 := &TextAreaLayoutEvent{TextArea: ta} 195 ta.EvReg.RunCallbacks(TextAreaLayoutEventId, ev2) 196 } 197 198 func (ta *TextArea) setDrawer4Opts() { 199 if d, ok := ta.Drawer.(*drawer4.Drawer); ok { 200 // scale cursor based on lineheight 201 w := 1 202 u := d.LineHeight() 203 u2 := int(float64(u) * 0.08) 204 if u2 > 1 { 205 w = u2 206 } 207 d.Opt.Cursor.AddedWidth = w 208 209 // set startoffsetx based on cursor 210 d.Opt.RuneReader.StartOffsetX = d.Opt.Cursor.AddedWidth * 2 211 } 212 } 213 214 //---------- 215 216 const ( 217 TextAreaCmdEventId = iota 218 TextAreaSelectAnnotationEventId 219 TextAreaInlineCompleteEventId 220 TextAreaInputEventId 221 TextAreaLayoutEventId 222 ) 223 224 //---------- 225 226 type TextAreaCmdEvent struct { 227 TextArea *TextArea 228 Index int 229 } 230 231 //---------- 232 233 type TextAreaSelectAnnotationEvent struct { 234 TextArea *TextArea 235 AnnotationIndex int 236 Offset int // annotation string click offset 237 Type TASelAnnType 238 } 239 240 type TASelAnnType int 241 242 const ( 243 TASelAnnTypeCurrent TASelAnnType = iota // make current 244 TASelAnnTypeCurrentPrev 245 TASelAnnTypeCurrentNext 246 TASelAnnTypePrint 247 TASelAnnTypePrintAllPrevious 248 ) 249 250 //---------- 251 252 type TextAreaInlineCompleteEvent struct { 253 TextArea *TextArea 254 Offset int 255 256 ReplyHandled event.Handled // allow callbacks to set value 257 } 258 259 //---------- 260 261 type TextAreaInputEvent struct { 262 TextArea *TextArea 263 Event interface{} 264 ReplyHandled event.Handled 265 } 266 267 //---------- 268 269 type TextAreaLayoutEvent struct { 270 TextArea *TextArea 271 }