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 }