github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/framework/richtext/click.go (about)

     1  package richtext
     2  
     3  import (
     4  	"image"
     5  	"time"
     6  	"unicode/utf8"
     7  
     8  	"github.com/gop9/olt/framework"
     9  	"github.com/gop9/olt/framework/clipboard"
    10  	"github.com/gop9/olt/framework/label"
    11  	"github.com/gop9/olt/framework/rect"
    12  	gkey "github.com/gop9/olt/gio/io/key"
    13  	"github.com/gop9/olt/gio/io/pointer"
    14  
    15  	"golang.org/x/image/math/fixed"
    16  )
    17  
    18  func (rtxt *RichText) handleClick(w *framework.Window, r rect.Rect, in *framework.Input, styleSel styleSel, line *line, chunkIdx int, hovering *bool, linkClick *int32) {
    19  	if rtxt.flags&Selectable == 0 && !styleSel.isLink && rtxt.flags&Clipboard == 0 && styleSel.Tooltip == nil && rtxt.flags&Keyboard == 0 {
    20  		return
    21  	}
    22  
    23  	if rtxt.flags&Clipboard != 0 && r.W > 0 && r.H > 0 {
    24  		fn := styleSel.ContextMenu
    25  		if fn == nil {
    26  			fn = func(w *framework.Window) {
    27  				w.Row(20).Dynamic(1)
    28  				if w.MenuItem(label.TA("Copy", "LC")) {
    29  					clipboard.Set(rtxt.Get(rtxt.Sel))
    30  					w.Close()
    31  				}
    32  			}
    33  		}
    34  		if w := w.ContextualOpen(0, image.Point{}, r, fn); w != nil {
    35  			rtxt.focused = true
    36  		}
    37  	}
    38  
    39  	oldSel := rtxt.Sel
    40  
    41  	if rtxt.down {
    42  		rtxt.focused = true
    43  		if in.Mouse.HoveringRect(r) {
    44  			if !in.Mouse.Down(pointer.ButtonLeft) {
    45  				if rtxt.isClick && styleSel.isLink && in.Mouse.HoveringRect(r) {
    46  					if styleSel.link != nil {
    47  						styleSel.link()
    48  					} else if linkClick != nil {
    49  						*linkClick = line.coordToIndex(in.Mouse.Pos, chunkIdx, rtxt.adv)
    50  					}
    51  				}
    52  				rtxt.down = false
    53  				rtxt.isClick = false
    54  			} else {
    55  				q := line.coordToIndex(in.Mouse.Pos, chunkIdx, rtxt.adv)
    56  				if q < rtxt.dragStart {
    57  					rtxt.Sel.S = q
    58  					rtxt.Sel.E = rtxt.dragStart
    59  				} else {
    60  					rtxt.Sel.S = rtxt.dragStart
    61  					rtxt.Sel.E = q
    62  				}
    63  				rtxt.expandSelection()
    64  				if q != rtxt.dragStart {
    65  					rtxt.isClick = false
    66  				}
    67  			}
    68  		}
    69  	} else {
    70  		if in.Mouse.Down(pointer.ButtonLeft) && in.Mouse.HoveringRect(r) {
    71  			rtxt.focused = true
    72  			q := line.coordToIndex(in.Mouse.Pos, chunkIdx, rtxt.adv)
    73  			if time.Since(rtxt.lastClickTime) < 200*time.Millisecond && q == rtxt.dragStart {
    74  				rtxt.clickCount++
    75  			} else {
    76  				rtxt.clickCount = 1
    77  			}
    78  			if rtxt.clickCount > 3 {
    79  				rtxt.clickCount = 3
    80  			}
    81  			rtxt.lastClickTime = time.Now()
    82  			rtxt.dragStart = q
    83  			rtxt.Sel.S = rtxt.dragStart
    84  			rtxt.Sel.E = rtxt.Sel.S
    85  			rtxt.expandSelection()
    86  			rtxt.down = true
    87  			rtxt.isClick = true
    88  		}
    89  		if (styleSel.isLink || styleSel.Tooltip != nil) && hovering != nil && in.Mouse.HoveringRect(r) {
    90  			*hovering = true
    91  		}
    92  	}
    93  
    94  	if rtxt.flags&Selectable == 0 {
    95  		rtxt.Sel = oldSel
    96  	}
    97  	return
    98  }
    99  
   100  func (rtxt *RichText) expandSelection() {
   101  	switch rtxt.clickCount {
   102  	case 2:
   103  		sline := rtxt.findLine(rtxt.Sel.S)
   104  		eline := rtxt.findLine(rtxt.Sel.E)
   105  
   106  		var citer citer
   107  		for citer.Init(sline, rtxt.Sel.S); citer.Valid(); citer.Prev() {
   108  			if citer.Char() == ' ' {
   109  				citer.Next()
   110  				break
   111  			}
   112  
   113  		}
   114  		rtxt.Sel.S = citer.off
   115  
   116  		for citer.Init(eline, rtxt.Sel.E); citer.Valid(); citer.Next() {
   117  			if citer.Char() == ' ' {
   118  				break
   119  			}
   120  		}
   121  		rtxt.Sel.E = citer.off
   122  	case 3:
   123  		sline := rtxt.findLine(rtxt.Sel.S)
   124  		eline := rtxt.findLine(rtxt.Sel.E)
   125  		if len(sline.off) > 0 {
   126  			rtxt.Sel.S = sline.off[0]
   127  			rtxt.Sel.E = eline.endoff()
   128  		}
   129  	}
   130  }
   131  
   132  func (rtxt *RichText) findLine(q int32) line {
   133  	for _, line := range rtxt.lines {
   134  		if len(line.off) <= 0 {
   135  			continue
   136  		}
   137  		if line.sel().contains(q) {
   138  			return line
   139  		}
   140  	}
   141  	return rtxt.lines[len(rtxt.lines)-1]
   142  }
   143  
   144  func (line line) coordToIndex(p image.Point, chunkIdx int, adv []fixed.Int26_6) int32 {
   145  	advance, runeoff := line.chunkAdvance(chunkIdx)
   146  	if len(line.chunks) == 0 {
   147  		return line.off[0]
   148  	}
   149  	chunk := line.chunks[chunkIdx]
   150  
   151  	x := advance + line.leftMargin + line.p.X
   152  
   153  	w := fixed.I(0)
   154  
   155  	off := line.off[chunkIdx]
   156  	for chunk.len() > 0 {
   157  		w += adv[runeoff]
   158  
   159  		if x+w.Ceil() > p.X {
   160  			return off
   161  		}
   162  
   163  		var rsz int
   164  		if chunk.b != nil {
   165  			_, rsz = utf8.DecodeRune(chunk.b)
   166  			chunk.b = chunk.b[rsz:]
   167  		} else {
   168  			_, rsz = utf8.DecodeRuneInString(chunk.s)
   169  			chunk.s = chunk.s[rsz:]
   170  		}
   171  		off += int32(rsz)
   172  		runeoff++
   173  	}
   174  
   175  	return off
   176  }
   177  
   178  type citer struct {
   179  	valid bool
   180  	off   int32
   181  	line  line
   182  	i, j  int
   183  }
   184  
   185  func (citer *citer) Init(line line, off int32) {
   186  	citer.valid = true
   187  	citer.off = off
   188  	citer.line = line
   189  	found := false
   190  	for i := range citer.line.chunks {
   191  		if citer.line.off[i] <= off && off < citer.line.off[i]+citer.line.chunks[i].len() {
   192  			citer.i = i
   193  			citer.j = int(off - citer.line.off[i])
   194  			found = true
   195  			break
   196  		}
   197  	}
   198  	if !found {
   199  		citer.i = len(citer.line.chunks)
   200  		citer.j = 0
   201  		citer.valid = false
   202  	}
   203  	if len(citer.line.chunks) <= 0 {
   204  		citer.valid = false
   205  	}
   206  }
   207  
   208  func (citer *citer) Valid() bool {
   209  	if !citer.valid {
   210  		return false
   211  	}
   212  	if citer.i < 0 || citer.i >= len(citer.line.chunks) {
   213  		return false
   214  	}
   215  	chunk := citer.line.chunks[citer.i]
   216  	if citer.j < 0 {
   217  		return false
   218  	}
   219  	if chunk.b != nil {
   220  		return citer.j < len(chunk.b)
   221  	}
   222  	return citer.j < len(chunk.s)
   223  }
   224  
   225  func (citer *citer) Char() byte {
   226  	chunk := citer.line.chunks[citer.i]
   227  	if chunk.b != nil {
   228  		return chunk.b[citer.j]
   229  	}
   230  	return chunk.s[citer.j]
   231  }
   232  
   233  func (citer *citer) Prev() {
   234  	citer.j--
   235  	citer.off--
   236  	if citer.j < 0 {
   237  		citer.i--
   238  		if citer.i < 0 {
   239  			citer.i = 0
   240  			citer.off++
   241  			citer.valid = false
   242  		} else {
   243  			chunk := citer.line.chunks[citer.i]
   244  			if chunk.b != nil {
   245  				citer.j = len(chunk.b) - 1
   246  			} else {
   247  				citer.j = len(chunk.s) - 1
   248  			}
   249  		}
   250  	}
   251  }
   252  
   253  func (citer *citer) Next() {
   254  	citer.j++
   255  	citer.off++
   256  	for citer.j >= int(citer.line.chunks[citer.i].len()) {
   257  		citer.j = 0
   258  		citer.i++
   259  		if citer.i >= len(citer.line.chunks) {
   260  			citer.valid = false
   261  			return
   262  		}
   263  	}
   264  }
   265  
   266  func (rtxt *RichText) handleKeyboard(in *framework.Input) (arrowKey, pageKey int) {
   267  	if !rtxt.focused || rtxt.flags&Keyboard == 0 {
   268  		return
   269  	}
   270  
   271  	for _, k := range in.Keyboard.Keys {
   272  		switch {
   273  		case k.Modifiers == gkey.ModControl && k.Code == gkey.CodeC:
   274  			if rtxt.flags&Clipboard != 0 {
   275  				clipboard.Set(rtxt.Get(rtxt.Sel))
   276  			}
   277  		case k.Code == gkey.CodeUpArrow:
   278  			return -1, 0
   279  		case k.Code == gkey.CodeDownArrow:
   280  			return +1, 0
   281  		case k.Code == gkey.CodePageDown:
   282  			return 0, +1
   283  		case k.Code == gkey.CodePageUp:
   284  			return 0, -1
   285  		}
   286  	}
   287  
   288  	return 0, 0
   289  }