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  }