github.com/jmigpin/editor@v1.6.0/util/uiutil/widget/texteditx.go (about) 1 package widget 2 3 import ( 4 "fmt" 5 "image/color" 6 "time" 7 8 "github.com/jmigpin/editor/util/drawutil" 9 "github.com/jmigpin/editor/util/drawutil/drawer4" 10 "github.com/jmigpin/editor/util/imageutil" 11 "github.com/jmigpin/editor/util/iout/iorw" 12 ) 13 14 // textedit with extensions 15 type TextEditX struct { 16 *TextEdit 17 18 commentLineStr string // Used in comment/uncomment lines 19 20 flash struct { 21 start time.Time 22 now time.Time 23 dur time.Duration 24 line struct { 25 on bool 26 } 27 index struct { 28 on bool 29 index int 30 len int 31 } 32 } 33 } 34 35 func NewTextEditX(uiCtx UIContext) *TextEditX { 36 te := &TextEditX{ 37 TextEdit: NewTextEdit(uiCtx), 38 } 39 40 if d, ok := te.Text.Drawer.(*drawer4.Drawer); ok { 41 d.Opt.Cursor.On = true 42 43 // setup colorize order 44 d.Opt.Colorize.Groups = []*drawer4.ColorizeGroup{ 45 &d.Opt.SyntaxHighlight.Group, 46 &d.Opt.WordHighlight.Group, 47 &d.Opt.ParenthesisHighlight.Group, 48 {}, // 3=terminal 49 {}, // 4=selection 50 {}, // 5=flash 51 } 52 } 53 54 return te 55 } 56 57 //---------- 58 59 func (te *TextEditX) PaintBase() { 60 te.TextEdit.PaintBase() 61 te.iterateFlash() 62 } 63 64 func (te *TextEditX) Paint() { 65 te.updateSelectionOpt() 66 te.updateFlashOpt() 67 te.TextEdit.Paint() 68 } 69 70 //---------- 71 72 func (te *TextEditX) updateSelectionOpt() { 73 if d, ok := te.Drawer.(*drawer4.Drawer); ok { 74 g := d.Opt.Colorize.Groups[4] 75 c := te.Cursor() 76 if s, e, ok := c.SelectionIndexes(); ok { 77 // colors 78 pcol := te.TreeThemePaletteColor 79 fg := pcol("text_selection_fg") 80 bg := pcol("text_selection_bg") 81 // colorize ops 82 g.Ops = []*drawer4.ColorizeOp{ 83 {Offset: s, Fg: fg, Bg: bg}, 84 {Offset: e}, 85 } 86 // don't draw other colorizations 87 d.Opt.WordHighlight.Group.Off = true 88 d.Opt.ParenthesisHighlight.Group.Off = true 89 } else { 90 g.Ops = nil 91 // draw other colorizations 92 d.Opt.WordHighlight.Group.Off = false 93 d.Opt.ParenthesisHighlight.Group.Off = false 94 } 95 } 96 } 97 98 //---------- 99 100 func (te *TextEditX) FlashLine(index int) { 101 te.startFlash(index, 0, true) 102 } 103 104 func (te *TextEditX) FlashIndexLen(index int, len int) { 105 te.startFlash(index, len, len == 0) 106 } 107 108 // Safe to use concurrently. If line is true then len is calculated. 109 func (te *TextEditX) startFlash(index, len int, line bool) { 110 te.uiCtx.RunOnUIGoRoutine(func() { 111 te.flash.start = time.Now() 112 te.flash.dur = 500 * time.Millisecond 113 114 if line { 115 // recalc index/len 116 i0, i1 := te.flashLineIndexes(index) 117 index = i0 118 len = i1 - index 119 120 te.flash.line.on = true 121 // need at least len 1 or the colorize op will be canceled 122 if len == 0 { 123 len = 1 124 } 125 } 126 127 // flash index (accurate runes) 128 te.flash.index.on = true 129 te.flash.index.index = index 130 te.flash.index.len = len 131 132 te.MarkNeedsPaint() 133 }) 134 } 135 136 func (te *TextEditX) flashLineIndexes(offset int) (int, int) { 137 rd := te.EditCtx().LocalReader(offset) 138 s, e, newline, err := iorw.LinesIndexes(rd, offset, offset) 139 if err != nil { 140 return 0, 0 141 } 142 if newline { 143 e-- 144 } 145 return s, e 146 } 147 148 //---------- 149 150 func (te *TextEditX) iterateFlash() { 151 if !te.flash.line.on && !te.flash.index.on { 152 return 153 } 154 155 te.flash.now = time.Now() 156 end := te.flash.start.Add(te.flash.dur) 157 158 // animation time ended 159 if te.flash.now.After(end) { 160 te.flash.index.on = false 161 te.flash.line.on = false 162 } else { 163 te.uiCtx.RunOnUIGoRoutine(func() { 164 te.MarkNeedsPaint() 165 }) 166 } 167 } 168 169 func (te *TextEditX) updateFlashOpt() { 170 if d, ok := te.Drawer.(*drawer4.Drawer); ok { 171 te.updateFlashOpt4(d) 172 } 173 } 174 175 func (te *TextEditX) updateFlashOpt4(d *drawer4.Drawer) { 176 g := d.Opt.Colorize.Groups[5] 177 if !te.flash.index.on { 178 g.Ops = nil 179 return 180 } 181 182 // tint percentage 183 t := te.flash.now.Sub(te.flash.start) 184 perc := 1.0 - (float64(t) / float64(te.flash.dur)) 185 186 // process color function 187 bg3 := te.TreeThemePaletteColor("text_bg") 188 pc := func(fg, bg color.Color) (_, _ color.Color) { 189 fg2 := imageutil.TintOrShade(fg, perc) 190 if bg == nil { 191 bg = bg3 192 } 193 bg2 := imageutil.TintOrShade(bg, perc) 194 return fg2, bg2 195 } 196 197 s := te.flash.index.index 198 e := s + te.flash.index.len 199 line := te.flash.line.on 200 g.Ops = []*drawer4.ColorizeOp{ 201 {Offset: s, ProcColor: pc, Line: line}, 202 {Offset: e}, 203 } 204 } 205 206 //---------- 207 208 func (te *TextEditX) EnableParenthesisMatch(v bool) { 209 if d, ok := te.Drawer.(*drawer4.Drawer); ok { 210 d.Opt.ParenthesisHighlight.On = v 211 } 212 } 213 214 //---------- 215 216 func (te *TextEditX) EnableSyntaxHighlight(v bool) { 217 if d, ok := te.Drawer.(*drawer4.Drawer); ok { 218 d.Opt.SyntaxHighlight.On = v 219 } 220 } 221 222 //---------- 223 224 func (te *TextEditX) EnableCursorWordHighlight(v bool) { 225 if d, ok := te.Drawer.(*drawer4.Drawer); ok { 226 d.Opt.WordHighlight.On = v 227 } 228 } 229 230 //---------- 231 232 func (te *TextEditX) SetCommentStrings(a ...interface{}) { 233 cs := []*drawutil.SyntaxHighlightComment{} 234 firstLine := true 235 for _, v := range a { 236 switch t := v.(type) { 237 case string: 238 // line comment 239 c := &drawutil.SyntaxHighlightComment{IsLine: true, S: t} 240 cs = append(cs, c) 241 // keep first definition for shortcut comment insertion 242 if firstLine { 243 firstLine = false 244 te.ctx.Fns.LineCommentStr = func() string { return t } 245 } 246 case [2]string: 247 // multiline comment 248 c := &drawutil.SyntaxHighlightComment{S: t[0], E: t[1]} 249 cs = append(cs, c) 250 default: 251 panic(fmt.Sprintf("unexpected type: %v", t)) 252 } 253 } 254 255 if d, ok := te.Drawer.(*drawer4.Drawer); ok { 256 opt := &d.Opt.SyntaxHighlight 257 opt.Comment.Defs = cs 258 } 259 } 260 261 //---------- 262 263 func (te *TextEditX) OnThemeChange() { 264 te.Text.OnThemeChange() 265 266 pcol := te.TreeThemePaletteColor 267 268 if d, ok := te.Drawer.(*drawer4.Drawer); ok { 269 d.Opt.Cursor.Fg = pcol("text_cursor_fg") 270 d.Opt.LineWrap.Fg = pcol("text_wrapline_fg") 271 d.Opt.LineWrap.Bg = pcol("text_wrapline_bg") 272 273 // annotations 274 d.Opt.Annotations.Fg = pcol("text_annotations_fg") 275 d.Opt.Annotations.Bg = pcol("text_annotations_bg") 276 d.Opt.Annotations.Selected.Fg = pcol("text_annotations_select_fg") 277 d.Opt.Annotations.Selected.Bg = pcol("text_annotations_select_bg") 278 279 // word highlight 280 d.Opt.WordHighlight.Fg = pcol("text_highlightword_fg") 281 d.Opt.WordHighlight.Bg = pcol("text_highlightword_bg") 282 283 // parenthesis highlight 284 d.Opt.ParenthesisHighlight.Fg = pcol("text_parenthesis_fg") 285 d.Opt.ParenthesisHighlight.Bg = pcol("text_parenthesis_bg") 286 287 // syntax highlight 288 opt := &d.Opt.SyntaxHighlight 289 opt.Comment.Fg = pcol("text_colorize_comments_fg") 290 opt.Comment.Bg = pcol("text_colorize_comments_bg") 291 opt.String.Fg = pcol("text_colorize_string_fg") 292 opt.String.Bg = pcol("text_colorize_string_bg") 293 } 294 }