github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/framework/context.go (about) 1 package framework 2 3 import ( 4 "bytes" 5 "errors" 6 gkey "github.com/gop9/olt/gio/io/key" 7 "github.com/gop9/olt/gio/io/pointer" 8 "image" 9 "image/draw" 10 "io" 11 "time" 12 13 "github.com/gop9/olt/framework/command" 14 "github.com/gop9/olt/framework/rect" 15 "github.com/gop9/olt/framework/style" 16 ) 17 18 const perfUpdate = false 19 const dumpFrame = false 20 21 var UnknownCommandErr = errors.New("unknown command") 22 23 type context struct { 24 mw MasterWindow 25 Input Input 26 Style style.Style 27 Windows []*Window 28 DockedWindows dockedTree 29 changed int32 30 activateEditor *TextEditor 31 cmds []command.Command 32 trashFrame bool 33 autopos image.Point 34 35 finalCmds command.Buffer 36 37 dockedWindowFocus int 38 floatWindowFocus int 39 scrollwheelFocus int 40 dockedCnt int 41 42 cmdstim []time.Duration // contains timing for all commands 43 } 44 45 func contextAllCommands(ctx *context) { 46 ctx.cmds = ctx.cmds[:0] 47 for i, w := range ctx.Windows { 48 ctx.cmds = append(ctx.cmds, w.cmds.Commands...) 49 if i == 0 { 50 ctx.DockedWindows.Walk(func(w *Window) *Window { 51 ctx.cmds = append(ctx.cmds, w.cmds.Commands...) 52 return w 53 }) 54 } 55 } 56 ctx.cmds = append(ctx.cmds, ctx.finalCmds.Commands...) 57 } 58 59 func (ctx *context) setupMasterWindow(layout *panel, updatefn UpdateFn) { 60 ctx.Windows = append(ctx.Windows, createWindow(ctx, "")) 61 ctx.Windows[0].idx = 0 62 ctx.Windows[0].layout = layout 63 ctx.Windows[0].flags = layout.Flags | WindowNonmodal 64 ctx.Windows[0].updateFn = updatefn 65 } 66 67 func (ctx *context) Update() { 68 for count := 0; count < 2; count++ { 69 contextBegin(ctx, ctx.Windows[0].layout) 70 for i := 0; i < len(ctx.Windows); i++ { 71 ctx.Windows[i].began = false 72 } 73 ctx.Restack() 74 ctx.FindFocus() 75 for i := 0; i < len(ctx.Windows); i++ { // this must not use range or tooltips won't work 76 ctx.updateWindow(ctx.Windows[i]) 77 if i == 0 { 78 t := ctx.DockedWindows.Update(ctx.Windows[0].Bounds, ctx.Style.Scaling) 79 if t != nil { 80 ctx.DockedWindows = *t 81 } 82 } 83 } 84 contextEnd(ctx) 85 if !ctx.trashFrame { 86 break 87 } else { 88 ctx.Reset() 89 } 90 } 91 } 92 93 func (ctx *context) updateWindow(win *Window) { 94 if win.updateFn != nil { 95 win.specialPanelBegin() 96 win.updateFn(win) 97 } 98 99 if !win.began { 100 win.close = true 101 return 102 } 103 104 if win.title == tooltipWindowTitle { 105 win.close = true 106 } 107 108 if win.flags&windowPopup != 0 { 109 panelEnd(ctx, win) 110 } 111 } 112 113 func (ctx *context) processKeyEvent(e gkey.Event, textbuffer *bytes.Buffer) { 114 if e.Direction == gkey.DirRelease { 115 return 116 } 117 118 evinNotext := func() { 119 for _, k := range ctx.Input.Keyboard.Keys { 120 if k.Code == e.Code { 121 k.Modifiers |= e.Modifiers 122 return 123 } 124 } 125 ctx.Input.Keyboard.Keys = append(ctx.Input.Keyboard.Keys, e) 126 } 127 evinText := func() { 128 if e.Modifiers == 0 || e.Modifiers == gkey.ModShift { 129 io.WriteString(textbuffer, string(e.Rune)) 130 } 131 132 evinNotext() 133 } 134 135 switch { 136 case e.Code == gkey.CodeUnknown: 137 if e.Rune > 0 { 138 evinText() 139 } 140 case (e.Code >= gkey.CodeA && e.Code <= gkey.Code0) || e.Code == gkey.CodeSpacebar || e.Code == gkey.CodeHyphenMinus || e.Code == gkey.CodeEqualSign || e.Code == gkey.CodeLeftSquareBracket || e.Code == gkey.CodeRightSquareBracket || e.Code == gkey.CodeBackslash || e.Code == gkey.CodeSemicolon || e.Code == gkey.CodeApostrophe || e.Code == gkey.CodeGraveAccent || e.Code == gkey.CodeComma || e.Code == gkey.CodeFullStop || e.Code == gkey.CodeSlash || (e.Code >= gkey.CodeKeypadSlash && e.Code <= gkey.CodeKeypadPlusSign) || (e.Code >= gkey.CodeKeypad1 && e.Code <= gkey.CodeKeypadEqualSign): 141 evinText() 142 case e.Code == gkey.CodeTab: 143 e.Rune = '\t' 144 evinText() 145 case e.Code == gkey.CodeReturnEnter || e.Code == gkey.CodeKeypadEnter: 146 e.Rune = '\n' 147 evinText() 148 default: 149 evinNotext() 150 } 151 } 152 153 func contextBegin(ctx *context, layout *panel) { 154 for _, w := range ctx.Windows { 155 w.usingSub = false 156 w.curNode = w.rootNode 157 w.close = false 158 w.widgets.reset() 159 w.cmds.Reset() 160 } 161 ctx.finalCmds.Reset() 162 ctx.DockedWindows.Walk(func(w *Window) *Window { 163 w.usingSub = false 164 w.curNode = w.rootNode 165 w.close = false 166 w.widgets.reset() 167 w.cmds.Reset() 168 return w 169 }) 170 171 ctx.trashFrame = false 172 ctx.Windows[0].layout = layout 173 panelBegin(ctx, ctx.Windows[0], "") 174 layout.Offset = &ctx.Windows[0].Scrollbar 175 } 176 177 func contextEnd(ctx *context) { 178 panelEnd(ctx, ctx.Windows[0]) 179 } 180 181 func (ctx *context) Reset() { 182 prevNumWindows := len(ctx.Windows) 183 for i := 0; i < len(ctx.Windows); i++ { 184 if ctx.Windows[i].close { 185 if i != len(ctx.Windows)-1 { 186 copy(ctx.Windows[i:], ctx.Windows[i+1:]) 187 i-- 188 } 189 ctx.Windows = ctx.Windows[:len(ctx.Windows)-1] 190 } 191 } 192 for i := range ctx.Windows { 193 ctx.Windows[i].idx = i 194 } 195 if prevNumWindows == 2 && len(ctx.Windows) == 1 && ctx.Input.Mouse.valid { 196 ctx.DockedWindows.Walk(func(w *Window) *Window { 197 if w.flags&windowDocked == 0 { 198 return w 199 } 200 for _, b := range []pointer.Buttons{pointer.ButtonLeft, pointer.ButtonRight, pointer.ButtonMiddle} { 201 btn := ctx.Input.Mouse.Buttons[b] 202 if btn.Clicked && w.Bounds.Contains(btn.ClickedPos) { 203 ctx.dockedWindowFocus = w.idx 204 return w 205 } 206 } 207 return w 208 }) 209 } 210 ctx.activateEditor = nil 211 in := &ctx.Input 212 in.Mouse.Buttons[pointer.ButtonLeft].Clicked = false 213 in.Mouse.Buttons[pointer.ButtonMiddle].Clicked = false 214 in.Mouse.Buttons[pointer.ButtonRight].Clicked = false 215 in.Mouse.ScrollDelta = 0 216 in.Mouse.Prev.X = in.Mouse.Pos.X 217 in.Mouse.Prev.Y = in.Mouse.Pos.Y 218 in.Mouse.Delta = image.Point{} 219 in.Keyboard.Keys = in.Keyboard.Keys[0:0] 220 } 221 222 func (ctx *context) Restack() { 223 clicked := false 224 for _, b := range []pointer.Buttons{pointer.ButtonLeft, pointer.ButtonRight, pointer.ButtonMiddle} { 225 if ctx.Input.Mouse.Buttons[b].Clicked && ctx.Input.Mouse.Buttons[b].Down { 226 clicked = true 227 break 228 } 229 } 230 if !clicked { 231 return 232 } 233 ctx.dockedWindowFocus = 0 234 nonmodalToplevel := false 235 var toplevelIdx int 236 for i := len(ctx.Windows) - 1; i >= 0; i-- { 237 if ctx.Windows[i].flags&windowTooltip == 0 { 238 toplevelIdx = i 239 nonmodalToplevel = ctx.Windows[i].flags&WindowNonmodal != 0 240 break 241 } 242 } 243 if !nonmodalToplevel { 244 return 245 } 246 // toplevel window is non-modal, proceed to change the stacking order if 247 // the user clicked outside of it 248 restacked := false 249 found := false 250 for i := len(ctx.Windows) - 1; i > 0; i-- { 251 if ctx.Windows[i].flags&windowTooltip != 0 { 252 continue 253 } 254 if ctx.restackClick(ctx.Windows[i]) { 255 found = true 256 if toplevelIdx != i { 257 newToplevel := ctx.Windows[i] 258 copy(ctx.Windows[i:toplevelIdx], ctx.Windows[i+1:toplevelIdx+1]) 259 ctx.Windows[toplevelIdx] = newToplevel 260 restacked = true 261 } 262 break 263 } 264 } 265 if restacked { 266 for i := range ctx.Windows { 267 ctx.Windows[i].idx = i 268 } 269 } 270 if found { 271 return 272 } 273 ctx.DockedWindows.Walk(func(w *Window) *Window { 274 if ctx.restackClick(w) && (w.flags&windowDocked != 0) { 275 ctx.dockedWindowFocus = w.idx 276 } 277 return w 278 }) 279 } 280 281 func (ctx *context) FindFocus() { 282 ctx.floatWindowFocus = 0 283 for i := len(ctx.Windows) - 1; i >= 0; i-- { 284 if ctx.Windows[i].flags&windowTooltip == 0 { 285 ctx.floatWindowFocus = i 286 break 287 } 288 } 289 ctx.scrollwheelFocus = 0 290 for i := len(ctx.Windows) - 1; i > 0; i-- { 291 if ctx.Windows[i].Bounds.Contains(ctx.Input.Mouse.Pos) { 292 ctx.scrollwheelFocus = i 293 break 294 } 295 } 296 if ctx.scrollwheelFocus == 0 { 297 ctx.DockedWindows.Walk(func(w *Window) *Window { 298 if w.Bounds.Contains(ctx.Input.Mouse.Pos) { 299 ctx.scrollwheelFocus = w.idx 300 } 301 return w 302 }) 303 } 304 } 305 306 func (ctx *context) Walk(fn WindowWalkFn) { 307 fn(ctx.Windows[0].title, ctx.Windows[0].Data, false, 0, ctx.Windows[0].Bounds) 308 ctx.DockedWindows.walkExt(func(t *dockedTree) { 309 switch t.Type { 310 case dockedNodeHoriz: 311 fn("", nil, true, t.Split.Size, rect.Rect{}) 312 case dockedNodeVert: 313 fn("", nil, true, -t.Split.Size, rect.Rect{}) 314 case dockedNodeLeaf: 315 if t.W == nil { 316 fn("", nil, true, 0, rect.Rect{}) 317 } else { 318 fn(t.W.title, t.W.Data, true, 0, t.W.Bounds) 319 } 320 } 321 }) 322 for _, win := range ctx.Windows[1:] { 323 if win.flags&WindowNonmodal != 0 { 324 fn(win.title, win.Data, false, 0, win.Bounds) 325 } 326 } 327 } 328 329 func (ctx *context) restackClick(w *Window) bool { 330 if !ctx.Input.Mouse.valid { 331 return false 332 } 333 for _, b := range []pointer.Buttons{pointer.ButtonLeft, pointer.ButtonRight, pointer.ButtonMiddle} { 334 btn := ctx.Input.Mouse.Buttons[b] 335 if btn.Clicked && btn.Down && w.Bounds.Contains(btn.ClickedPos) { 336 return true 337 } 338 } 339 return false 340 } 341 342 type dockedNodeType uint8 343 344 const ( 345 dockedNodeLeaf dockedNodeType = iota 346 dockedNodeVert 347 dockedNodeHoriz 348 ) 349 350 type dockedTree struct { 351 Type dockedNodeType 352 Split ScalableSplit 353 Child [2]*dockedTree 354 W *Window 355 } 356 357 func (t *dockedTree) Update(bounds rect.Rect, scaling float64) *dockedTree { 358 if t == nil { 359 return nil 360 } 361 switch t.Type { 362 case dockedNodeVert: 363 b0, b1, _ := t.Split.verticalnw(bounds, scaling) 364 t.Child[0] = t.Child[0].Update(b0, scaling) 365 t.Child[1] = t.Child[1].Update(b1, scaling) 366 case dockedNodeHoriz: 367 b0, b1, _ := t.Split.horizontalnw(bounds, scaling) 368 t.Child[0] = t.Child[0].Update(b0, scaling) 369 t.Child[1] = t.Child[1].Update(b1, scaling) 370 case dockedNodeLeaf: 371 if t.W != nil { 372 t.W.Bounds = bounds 373 t.W.ctx.updateWindow(t.W) 374 if t.W == nil { 375 return nil 376 } 377 if t.W.close { 378 t.W = nil 379 return nil 380 } 381 return t 382 } 383 return nil 384 } 385 if t.Child[0] == nil { 386 return t.Child[1] 387 } 388 if t.Child[1] == nil { 389 return t.Child[0] 390 } 391 return t 392 } 393 394 func (t *dockedTree) walkExt(fn func(t *dockedTree)) { 395 if t == nil { 396 return 397 } 398 switch t.Type { 399 case dockedNodeVert, dockedNodeHoriz: 400 fn(t) 401 t.Child[0].walkExt(fn) 402 t.Child[1].walkExt(fn) 403 case dockedNodeLeaf: 404 fn(t) 405 } 406 } 407 408 func (t *dockedTree) Walk(fn func(t *Window) *Window) { 409 t.walkExt(func(t *dockedTree) { 410 if t.Type == dockedNodeLeaf && t.W != nil { 411 t.W = fn(t.W) 412 } 413 }) 414 } 415 416 func newDockedLeaf(win *Window) *dockedTree { 417 r := &dockedTree{Type: dockedNodeLeaf, W: win} 418 r.Split.MinSize = 40 419 return r 420 } 421 422 func (t *dockedTree) Dock(win *Window, pos image.Point, bounds rect.Rect, scaling float64) (bool, rect.Rect) { 423 if t == nil { 424 return false, rect.Rect{} 425 } 426 switch t.Type { 427 case dockedNodeVert: 428 b0, b1, _ := t.Split.verticalnw(bounds, scaling) 429 canDock, r := t.Child[0].Dock(win, pos, b0, scaling) 430 if canDock { 431 return canDock, r 432 } 433 canDock, r = t.Child[1].Dock(win, pos, b1, scaling) 434 if canDock { 435 return canDock, r 436 } 437 case dockedNodeHoriz: 438 b0, b1, _ := t.Split.horizontalnw(bounds, scaling) 439 canDock, r := t.Child[0].Dock(win, pos, b0, scaling) 440 if canDock { 441 return canDock, r 442 } 443 canDock, r = t.Child[1].Dock(win, pos, b1, scaling) 444 if canDock { 445 return canDock, r 446 } 447 case dockedNodeLeaf: 448 v := percentages(bounds, 0.03) 449 for i := range v { 450 if v[i].Contains(pos) { 451 if t.W == nil { 452 if win != nil { 453 t.W = win 454 win.ctx.dockWindow(win) 455 } 456 return true, bounds 457 } 458 w := percentages(bounds, 0.5) 459 if win != nil { 460 if i < 2 { 461 // horizontal split 462 t.Type = dockedNodeHoriz 463 t.Split.Size = int(float64(w[0].H) / scaling) 464 t.Child[i] = newDockedLeaf(win) 465 t.Child[-i+1] = newDockedLeaf(t.W) 466 } else { 467 // vertical split 468 t.Type = dockedNodeVert 469 t.Split.Size = int(float64(w[2].W) / scaling) 470 t.Child[i-2] = newDockedLeaf(win) 471 t.Child[-(i-2)+1] = newDockedLeaf(t.W) 472 } 473 474 t.W = nil 475 win.ctx.dockWindow(win) 476 } 477 return true, w[i] 478 } 479 } 480 } 481 return false, rect.Rect{} 482 } 483 484 func (ctx *context) dockWindow(win *Window) { 485 win.undockedSz = image.Point{win.Bounds.W, win.Bounds.H} 486 win.flags |= windowDocked 487 win.layout.Flags |= windowDocked 488 ctx.dockedCnt-- 489 win.idx = ctx.dockedCnt 490 for i := range ctx.Windows { 491 if ctx.Windows[i] == win { 492 if i+1 < len(ctx.Windows) { 493 copy(ctx.Windows[i:], ctx.Windows[i+1:]) 494 } 495 ctx.Windows = ctx.Windows[:len(ctx.Windows)-1] 496 return 497 } 498 } 499 } 500 501 func (t *dockedTree) Undock(win *Window) { 502 t.Walk(func(w *Window) *Window { 503 if w == win { 504 return nil 505 } 506 return w 507 }) 508 win.flags &= ^windowDocked 509 win.layout.Flags &= ^windowDocked 510 win.Bounds.H = win.undockedSz.Y 511 win.Bounds.W = win.undockedSz.X 512 win.idx = len(win.ctx.Windows) 513 win.ctx.Windows = append(win.ctx.Windows, win) 514 } 515 516 func (t *dockedTree) Scale(win *Window, delta image.Point, scaling float64) image.Point { 517 if t == nil || (delta.X == 0 && delta.Y == 0) { 518 return image.Point{} 519 } 520 switch t.Type { 521 case dockedNodeVert: 522 d0 := t.Child[0].Scale(win, delta, scaling) 523 if d0.X != 0 { 524 t.Split.Size += int(float64(d0.X) / scaling) 525 if t.Split.Size <= t.Split.MinSize { 526 t.Split.Size = t.Split.MinSize 527 } 528 d0.X = 0 529 } 530 if d0 != image.ZP { 531 return d0 532 } 533 return t.Child[1].Scale(win, delta, scaling) 534 case dockedNodeHoriz: 535 d0 := t.Child[0].Scale(win, delta, scaling) 536 if d0.Y != 0 { 537 t.Split.Size += int(float64(d0.Y) / scaling) 538 if t.Split.Size <= t.Split.MinSize { 539 t.Split.Size = t.Split.MinSize 540 } 541 d0.Y = 0 542 } 543 if d0 != image.ZP { 544 return d0 545 } 546 return t.Child[1].Scale(win, delta, scaling) 547 case dockedNodeLeaf: 548 if t.W == win { 549 return delta 550 } 551 } 552 return image.Point{} 553 } 554 555 func (ctx *context) ResetWindows() *DockSplit { 556 ctx.DockedWindows = dockedTree{} 557 ctx.Windows = ctx.Windows[:1] 558 ctx.dockedCnt = 0 559 return &DockSplit{ctx, &ctx.DockedWindows} 560 } 561 562 type DockSplit struct { 563 ctx *context 564 node *dockedTree 565 } 566 567 func (ds *DockSplit) Split(horiz bool, size int) (left, right *DockSplit) { 568 if horiz { 569 ds.node.Type = dockedNodeHoriz 570 } else { 571 ds.node.Type = dockedNodeVert 572 } 573 ds.node.Split.Size = size 574 ds.node.Child[0] = &dockedTree{Type: dockedNodeLeaf, Split: ScalableSplit{MinSize: 40}} 575 ds.node.Child[1] = &dockedTree{Type: dockedNodeLeaf, Split: ScalableSplit{MinSize: 40}} 576 return &DockSplit{ds.ctx, ds.node.Child[0]}, &DockSplit{ds.ctx, ds.node.Child[1]} 577 } 578 579 func (ds *DockSplit) Open(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn) { 580 ds.ctx.popupOpen(title, flags, rect, scale, updateFn) 581 ds.node.Type = dockedNodeLeaf 582 ds.node.W = ds.ctx.Windows[len(ds.ctx.Windows)-1] 583 ds.ctx.dockWindow(ds.node.W) 584 } 585 586 func percentages(bounds rect.Rect, f float64) (r [4]rect.Rect) { 587 pw := int(float64(bounds.W) * f) 588 ph := int(float64(bounds.H) * f) 589 // horizontal split 590 r[0] = bounds 591 r[0].H = ph 592 r[1] = bounds 593 r[1].Y += r[1].H - ph 594 r[1].H = ph 595 596 // vertical split 597 r[2] = bounds 598 r[2].W = pw 599 r[3] = bounds 600 r[3].X += r[3].W - pw 601 r[3].W = pw 602 return 603 } 604 605 func clip(dst *image.RGBA, r *image.Rectangle, src image.Image, sp *image.Point) { 606 orig := r.Min 607 *r = r.Intersect(dst.Bounds()) 608 *r = r.Intersect(src.Bounds().Add(orig.Sub(*sp))) 609 dx := r.Min.X - orig.X 610 dy := r.Min.Y - orig.Y 611 if dx == 0 && dy == 0 { 612 return 613 } 614 sp.X += dx 615 sp.Y += dy 616 } 617 618 func drawFill(dst *image.RGBA, r image.Rectangle, src *image.Uniform, sp image.Point, op draw.Op) { 619 clip(dst, &r, src, &sp) 620 if r.Empty() { 621 return 622 } 623 sr, sg, sb, sa := src.RGBA() 624 switch op { 625 case draw.Over: 626 drawFillOver(dst, r, sr, sg, sb, sa) 627 case draw.Src: 628 drawFillSrc(dst, r, sr, sg, sb, sa) 629 default: 630 draw.Draw(dst, r, src, sp, op) 631 } 632 } 633 634 func drawFillSrc(dst *image.RGBA, r image.Rectangle, sr, sg, sb, sa uint32) { 635 sr8 := uint8(sr >> 8) 636 sg8 := uint8(sg >> 8) 637 sb8 := uint8(sb >> 8) 638 sa8 := uint8(sa >> 8) 639 // The built-in copy function is faster than a straightforward for loop to fill the destination with 640 // the color, but copy requires a slice source. We therefore use a for loop to fill the first row, and 641 // then use the first row as the slice source for the remaining rows. 642 i0 := dst.PixOffset(r.Min.X, r.Min.Y) 643 i1 := i0 + r.Dx()*4 644 for i := i0; i < i1; i += 4 { 645 dst.Pix[i+0] = sr8 646 dst.Pix[i+1] = sg8 647 dst.Pix[i+2] = sb8 648 dst.Pix[i+3] = sa8 649 } 650 firstRow := dst.Pix[i0:i1] 651 for y := r.Min.Y + 1; y < r.Max.Y; y++ { 652 i0 += dst.Stride 653 i1 += dst.Stride 654 copy(dst.Pix[i0:i1], firstRow) 655 } 656 }