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  }