github.com/Seikaijyu/gio@v0.0.1/app/window.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package app 4 5 import ( 6 "errors" 7 "fmt" 8 "image" 9 "image/color" 10 "runtime" 11 "time" 12 "unicode" 13 "unicode/utf16" 14 "unicode/utf8" 15 16 "github.com/Seikaijyu/gio/f32" 17 "github.com/Seikaijyu/gio/font/gofont" 18 "github.com/Seikaijyu/gio/gpu" 19 "github.com/Seikaijyu/gio/internal/debug" 20 "github.com/Seikaijyu/gio/internal/ops" 21 "github.com/Seikaijyu/gio/io/event" 22 "github.com/Seikaijyu/gio/io/key" 23 "github.com/Seikaijyu/gio/io/pointer" 24 "github.com/Seikaijyu/gio/io/profile" 25 "github.com/Seikaijyu/gio/io/router" 26 "github.com/Seikaijyu/gio/io/system" 27 "github.com/Seikaijyu/gio/layout" 28 "github.com/Seikaijyu/gio/op" 29 "github.com/Seikaijyu/gio/text" 30 "github.com/Seikaijyu/gio/unit" 31 "github.com/Seikaijyu/gio/widget" 32 "github.com/Seikaijyu/gio/widget/material" 33 34 _ "github.com/Seikaijyu/gio/app/internal/log" 35 ) 36 37 // Option configures a window. 38 type Option func(unit.Metric, *Config) 39 40 // Window represents an operating system window. 41 type Window struct { 42 ctx context 43 gpu gpu.GPU 44 45 // driverFuncs is a channel of functions to run when 46 // the Window has a valid driver. 47 driverFuncs chan func(d driver) 48 // wakeups wakes up the native event loop to send a 49 // WakeupEvent that flushes driverFuncs. 50 wakeups chan struct{} 51 // wakeupFuncs is sent wakeup functions when the driver changes. 52 wakeupFuncs chan func() 53 // redraws is notified when a redraw is requested by the client. 54 redraws chan struct{} 55 // immediateRedraws is like redraw but doesn't need a wakeup. 56 immediateRedraws chan struct{} 57 // scheduledRedraws is sent the most recent delayed redraw time. 58 scheduledRedraws chan time.Time 59 // options are the options waiting to be applied. 60 options chan []Option 61 // actions are the actions waiting to be performed. 62 actions chan system.Action 63 64 // out is where the platform backend delivers events bound for the 65 // user program. 66 out chan event.Event 67 frames chan *op.Ops 68 frameAck chan struct{} 69 destroy chan struct{} 70 71 stage system.Stage 72 animating bool 73 hasNextFrame bool 74 nextFrame time.Time 75 // viewport is the latest frame size with insets applied. 76 viewport image.Rectangle 77 // metric is the metric from the most recent frame. 78 metric unit.Metric 79 80 queue queue 81 cursor pointer.Cursor 82 decorations struct { 83 op.Ops 84 // enabled tracks the Decorated option as 85 // given to the Option method. It may differ 86 // from Config.Decorated depending on platform 87 // capability. 88 enabled bool 89 Config 90 height unit.Dp 91 currentHeight int 92 *material.Theme 93 *widget.Decorations 94 } 95 96 callbacks callbacks 97 98 nocontext bool 99 100 // semantic data, lazily evaluated if requested by a backend to speed up 101 // the cases where semantic data is not needed. 102 semantic struct { 103 // uptodate tracks whether the fields below are up to date. 104 uptodate bool 105 root router.SemanticID 106 prevTree []router.SemanticNode 107 tree []router.SemanticNode 108 ids map[router.SemanticID]router.SemanticNode 109 } 110 111 imeState editorState 112 113 // event stores the state required for processing and delivering events 114 // from NextEvent. If we had support for range over func, this would 115 // be the iterator state. 116 eventState struct { 117 created bool 118 initialOpts []Option 119 wakeup func() 120 timer *time.Timer 121 } 122 } 123 124 type editorState struct { 125 router.EditorState 126 compose key.Range 127 } 128 129 type callbacks struct { 130 w *Window 131 d driver 132 busy bool 133 waitEvents []event.Event 134 } 135 136 // queue is an event.Queue implementation that distributes system events 137 // to the input handlers declared in the most recent frame. 138 type queue struct { 139 q router.Router 140 } 141 142 // NewWindow creates a new window for a set of window 143 // options. The options are hints; the platform is free to 144 // ignore or adjust them. 145 // 146 // If the current program is running on iOS or Android, 147 // NewWindow returns the window previously created by the 148 // platform. 149 // 150 // Calling NewWindow more than once is not supported on 151 // iOS, Android, WebAssembly. 152 func NewWindow(options ...Option) *Window { 153 debug.Parse() 154 // Measure decoration height. 155 deco := new(widget.Decorations) 156 theme := material.NewTheme() 157 theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular())) 158 decoStyle := material.Decorations(theme, deco, 0, "") 159 gtx := layout.Context{ 160 Ops: new(op.Ops), 161 // Measure in Dp. 162 Metric: unit.Metric{}, 163 } 164 // Allow plenty of space. 165 gtx.Constraints.Max.Y = 200 166 dims := decoStyle.Layout(gtx) 167 decoHeight := unit.Dp(dims.Size.Y) 168 defaultOptions := []Option{ 169 Size(800, 600), 170 Title("Ninki.UI"), 171 Decorated(true), 172 decoHeightOpt(decoHeight), 173 } 174 options = append(defaultOptions, options...) 175 var cnf Config 176 cnf.apply(unit.Metric{}, options) 177 178 w := &Window{ 179 out: make(chan event.Event), 180 immediateRedraws: make(chan struct{}), 181 redraws: make(chan struct{}, 1), 182 scheduledRedraws: make(chan time.Time, 1), 183 frames: make(chan *op.Ops), 184 frameAck: make(chan struct{}), 185 driverFuncs: make(chan func(d driver), 1), 186 wakeups: make(chan struct{}, 1), 187 wakeupFuncs: make(chan func()), 188 destroy: make(chan struct{}), 189 options: make(chan []Option, 1), 190 actions: make(chan system.Action, 1), 191 nocontext: cnf.CustomRenderer, 192 } 193 194 w.decorations.Theme = theme 195 w.decorations.Decorations = deco 196 w.decorations.enabled = cnf.Decorated 197 w.decorations.height = decoHeight 198 w.imeState.compose = key.Range{Start: -1, End: -1} 199 w.semantic.ids = make(map[router.SemanticID]router.SemanticNode) 200 w.callbacks.w = w 201 w.eventState.initialOpts = options 202 return w 203 } 204 205 func decoHeightOpt(h unit.Dp) Option { 206 return func(m unit.Metric, c *Config) { 207 c.decoHeight = h 208 } 209 } 210 211 // update the window contents, input operations declare input handlers, 212 // and so on. The supplied operations list completely replaces the window state 213 // from previous calls. 214 func (w *Window) update(frame *op.Ops) { 215 w.frames <- frame 216 <-w.frameAck 217 } 218 219 func (w *Window) validateAndProcess(d driver, size image.Point, sync bool, frame *op.Ops, sigChan chan<- struct{}) error { 220 signal := func() { 221 if sigChan != nil { 222 // We're done with frame, let the client continue. 223 sigChan <- struct{}{} 224 // Signal at most once. 225 sigChan = nil 226 } 227 } 228 defer signal() 229 for { 230 if w.gpu == nil && !w.nocontext { 231 var err error 232 if w.ctx == nil { 233 w.ctx, err = d.NewContext() 234 if err != nil { 235 return err 236 } 237 sync = true 238 } 239 } 240 if sync && w.ctx != nil { 241 if err := w.ctx.Refresh(); err != nil { 242 if errors.Is(err, errOutOfDate) { 243 // Surface couldn't be created for transient reasons. Skip 244 // this frame and wait for the next. 245 return nil 246 } 247 w.destroyGPU() 248 if errors.Is(err, gpu.ErrDeviceLost) { 249 continue 250 } 251 return err 252 } 253 } 254 if w.ctx != nil { 255 if err := w.ctx.Lock(); err != nil { 256 w.destroyGPU() 257 return err 258 } 259 } 260 if w.gpu == nil && !w.nocontext { 261 gpu, err := gpu.New(w.ctx.API()) 262 if err != nil { 263 w.ctx.Unlock() 264 w.destroyGPU() 265 return err 266 } 267 w.gpu = gpu 268 } 269 if w.gpu != nil { 270 if err := w.frame(frame, size); err != nil { 271 w.ctx.Unlock() 272 if errors.Is(err, errOutOfDate) { 273 // GPU surface needs refreshing. 274 sync = true 275 continue 276 } 277 w.destroyGPU() 278 if errors.Is(err, gpu.ErrDeviceLost) { 279 continue 280 } 281 return err 282 } 283 } 284 w.queue.q.Frame(frame) 285 // Let the client continue as soon as possible, in particular before 286 // a potentially blocking Present. 287 signal() 288 var err error 289 if w.gpu != nil { 290 err = w.ctx.Present() 291 w.ctx.Unlock() 292 } 293 return err 294 } 295 } 296 297 func (w *Window) frame(frame *op.Ops, viewport image.Point) error { 298 if runtime.GOOS == "js" { 299 // Use transparent black when Gio is embedded, to allow mixing of Gio and 300 // foreign content below. 301 w.gpu.Clear(color.NRGBA{A: 0x00, R: 0x00, G: 0x00, B: 0x00}) 302 } else { 303 w.gpu.Clear(color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}) 304 } 305 target, err := w.ctx.RenderTarget() 306 if err != nil { 307 return err 308 } 309 return w.gpu.Frame(frame, target, viewport) 310 } 311 312 func (w *Window) processFrame(d driver, frameStart time.Time) { 313 for k := range w.semantic.ids { 314 delete(w.semantic.ids, k) 315 } 316 w.semantic.uptodate = false 317 q := &w.queue.q 318 switch q.TextInputState() { 319 case router.TextInputOpen: 320 d.ShowTextInput(true) 321 case router.TextInputClose: 322 d.ShowTextInput(false) 323 } 324 if hint, ok := q.TextInputHint(); ok { 325 d.SetInputHint(hint) 326 } 327 if txt, ok := q.WriteClipboard(); ok { 328 d.WriteClipboard(txt) 329 } 330 if q.ReadClipboard() { 331 d.ReadClipboard() 332 } 333 oldState := w.imeState 334 newState := oldState 335 newState.EditorState = q.EditorState() 336 if newState != oldState { 337 w.imeState = newState 338 d.EditorStateChanged(oldState, newState) 339 } 340 if q.Profiling() && w.gpu != nil { 341 frameDur := time.Since(frameStart) 342 frameDur = frameDur.Truncate(100 * time.Microsecond) 343 quantum := 100 * time.Microsecond 344 timings := fmt.Sprintf("tot:%7s %s", frameDur.Round(quantum), w.gpu.Profile()) 345 q.Queue(profile.Event{Timings: timings}) 346 } 347 if t, ok := q.WakeupTime(); ok { 348 w.setNextFrame(t) 349 } 350 w.updateAnimation(d) 351 } 352 353 // Invalidate the window such that a FrameEvent will be generated immediately. 354 // If the window is inactive, the event is sent when the window becomes active. 355 // 356 // Note that Invalidate is intended for externally triggered updates, such as a 357 // response from a network request. InvalidateOp is more efficient for animation 358 // and similar internal updates. 359 // 360 // Invalidate is safe for concurrent use. 361 func (w *Window) Invalidate() { 362 select { 363 case w.immediateRedraws <- struct{}{}: 364 return 365 default: 366 } 367 select { 368 case w.redraws <- struct{}{}: 369 w.wakeup() 370 default: 371 } 372 } 373 374 // Option applies the options to the window. 375 func (w *Window) Option(opts ...Option) { 376 if len(opts) == 0 { 377 return 378 } 379 for { 380 select { 381 case old := <-w.options: 382 opts = append(old, opts...) 383 case w.options <- opts: 384 w.wakeup() 385 return 386 } 387 } 388 } 389 390 // WriteClipboard writes a string to the clipboard. 391 func (w *Window) WriteClipboard(s string) { 392 w.driverDefer(func(d driver) { 393 d.WriteClipboard(s) 394 }) 395 } 396 397 // Run f in the same thread as the native window event loop, and wait for f to 398 // return or the window to close. Run is guaranteed not to deadlock if it is 399 // invoked during the handling of a ViewEvent, system.FrameEvent, 400 // system.StageEvent; call Run in a separate goroutine to avoid deadlock in all 401 // other cases. 402 // 403 // Note that most programs should not call Run; configuring a Window with 404 // CustomRenderer is a notable exception. 405 func (w *Window) Run(f func()) { 406 done := make(chan struct{}) 407 w.driverDefer(func(d driver) { 408 defer close(done) 409 f() 410 }) 411 select { 412 case <-done: 413 case <-w.destroy: 414 } 415 } 416 417 // driverDefer is like Run but can be run from any context. It doesn't wait 418 // for f to return. 419 func (w *Window) driverDefer(f func(d driver)) { 420 select { 421 case w.driverFuncs <- f: 422 w.wakeup() 423 case <-w.destroy: 424 } 425 } 426 427 func (w *Window) updateAnimation(d driver) { 428 animate := false 429 if w.stage >= system.StageInactive && w.hasNextFrame { 430 if dt := time.Until(w.nextFrame); dt <= 0 { 431 animate = true 432 } else { 433 // Schedule redraw. 434 select { 435 case <-w.scheduledRedraws: 436 default: 437 } 438 w.scheduledRedraws <- w.nextFrame 439 } 440 } 441 if animate != w.animating { 442 w.animating = animate 443 d.SetAnimating(animate) 444 } 445 } 446 447 func (w *Window) wakeup() { 448 select { 449 case w.wakeups <- struct{}{}: 450 default: 451 } 452 } 453 454 func (w *Window) setNextFrame(at time.Time) { 455 if !w.hasNextFrame || at.Before(w.nextFrame) { 456 w.hasNextFrame = true 457 w.nextFrame = at 458 } 459 } 460 461 func (c *callbacks) SetDriver(d driver) { 462 c.d = d 463 var wakeup func() 464 if d != nil { 465 wakeup = d.Wakeup 466 } 467 c.w.wakeupFuncs <- wakeup 468 } 469 470 func (c *callbacks) Event(e event.Event) bool { 471 if c.d == nil { 472 panic("event while no driver active") 473 } 474 c.waitEvents = append(c.waitEvents, e) 475 if c.busy { 476 return true 477 } 478 c.busy = true 479 var handled bool 480 for len(c.waitEvents) > 0 { 481 e := c.waitEvents[0] 482 copy(c.waitEvents, c.waitEvents[1:]) 483 c.waitEvents = c.waitEvents[:len(c.waitEvents)-1] 484 handled = c.w.processEvent(c.d, e) 485 } 486 c.busy = false 487 select { 488 case <-c.w.destroy: 489 return handled 490 default: 491 } 492 c.w.updateState(c.d) 493 if _, ok := e.(wakeupEvent); ok { 494 select { 495 case opts := <-c.w.options: 496 cnf := Config{Decorated: c.w.decorations.enabled} 497 for _, opt := range opts { 498 opt(c.w.metric, &cnf) 499 } 500 c.w.decorations.enabled = cnf.Decorated 501 decoHeight := c.w.decorations.height 502 if !c.w.decorations.enabled { 503 decoHeight = 0 504 } 505 opts = append(opts, decoHeightOpt(decoHeight)) 506 c.d.Configure(opts) 507 default: 508 } 509 select { 510 case acts := <-c.w.actions: 511 c.d.Perform(acts) 512 default: 513 } 514 } 515 return handled 516 } 517 518 // SemanticRoot returns the ID of the semantic root. 519 func (c *callbacks) SemanticRoot() router.SemanticID { 520 c.w.updateSemantics() 521 return c.w.semantic.root 522 } 523 524 // LookupSemantic looks up a semantic node from an ID. The zero ID denotes the root. 525 func (c *callbacks) LookupSemantic(semID router.SemanticID) (router.SemanticNode, bool) { 526 c.w.updateSemantics() 527 n, found := c.w.semantic.ids[semID] 528 return n, found 529 } 530 531 func (c *callbacks) AppendSemanticDiffs(diffs []router.SemanticID) []router.SemanticID { 532 c.w.updateSemantics() 533 if tree := c.w.semantic.prevTree; len(tree) > 0 { 534 c.w.collectSemanticDiffs(&diffs, c.w.semantic.prevTree[0]) 535 } 536 return diffs 537 } 538 539 func (c *callbacks) SemanticAt(pos f32.Point) (router.SemanticID, bool) { 540 c.w.updateSemantics() 541 return c.w.queue.q.SemanticAt(pos) 542 } 543 544 func (c *callbacks) EditorState() editorState { 545 return c.w.imeState 546 } 547 548 func (c *callbacks) SetComposingRegion(r key.Range) { 549 c.w.imeState.compose = r 550 } 551 552 func (c *callbacks) EditorInsert(text string) { 553 sel := c.w.imeState.Selection.Range 554 c.EditorReplace(sel, text) 555 start := sel.Start 556 if sel.End < start { 557 start = sel.End 558 } 559 sel.Start = start + utf8.RuneCountInString(text) 560 sel.End = sel.Start 561 c.SetEditorSelection(sel) 562 } 563 564 func (c *callbacks) EditorReplace(r key.Range, text string) { 565 c.w.imeState.Replace(r, text) 566 c.Event(key.EditEvent{Range: r, Text: text}) 567 c.Event(key.SnippetEvent(c.w.imeState.Snippet.Range)) 568 } 569 570 func (c *callbacks) SetEditorSelection(r key.Range) { 571 c.w.imeState.Selection.Range = r 572 c.Event(key.SelectionEvent(r)) 573 } 574 575 func (c *callbacks) SetEditorSnippet(r key.Range) { 576 if sn := c.EditorState().Snippet.Range; sn == r { 577 // No need to expand. 578 return 579 } 580 c.Event(key.SnippetEvent(r)) 581 } 582 583 func (w *Window) moveFocus(dir router.FocusDirection, d driver) { 584 if w.queue.q.MoveFocus(dir) { 585 w.queue.q.RevealFocus(w.viewport) 586 } else { 587 var v image.Point 588 switch dir { 589 case router.FocusRight: 590 v = image.Pt(+1, 0) 591 case router.FocusLeft: 592 v = image.Pt(-1, 0) 593 case router.FocusDown: 594 v = image.Pt(0, +1) 595 case router.FocusUp: 596 v = image.Pt(0, -1) 597 default: 598 return 599 } 600 const scrollABit = unit.Dp(50) 601 dist := v.Mul(int(w.metric.Dp(scrollABit))) 602 w.queue.q.ScrollFocus(dist) 603 } 604 } 605 606 func (c *callbacks) ClickFocus() { 607 c.w.queue.q.ClickFocus() 608 c.w.setNextFrame(time.Time{}) 609 c.w.updateAnimation(c.d) 610 } 611 612 func (c *callbacks) ActionAt(p f32.Point) (system.Action, bool) { 613 return c.w.queue.q.ActionAt(p) 614 } 615 616 func (e *editorState) Replace(r key.Range, text string) { 617 if r.Start > r.End { 618 r.Start, r.End = r.End, r.Start 619 } 620 runes := []rune(text) 621 newEnd := r.Start + len(runes) 622 adjust := func(pos int) int { 623 switch { 624 case newEnd < pos && pos <= r.End: 625 return newEnd 626 case r.End < pos: 627 diff := newEnd - r.End 628 return pos + diff 629 } 630 return pos 631 } 632 e.Selection.Start = adjust(e.Selection.Start) 633 e.Selection.End = adjust(e.Selection.End) 634 if e.compose.Start != -1 { 635 e.compose.Start = adjust(e.compose.Start) 636 e.compose.End = adjust(e.compose.End) 637 } 638 s := e.Snippet 639 if r.End < s.Start || r.Start > s.End { 640 // Discard snippet if it doesn't overlap with replacement. 641 s = key.Snippet{ 642 Range: key.Range{ 643 Start: r.Start, 644 End: r.Start, 645 }, 646 } 647 } 648 var newSnippet []rune 649 snippet := []rune(s.Text) 650 // Append first part of existing snippet. 651 if end := r.Start - s.Start; end > 0 { 652 newSnippet = append(newSnippet, snippet[:end]...) 653 } 654 // Append replacement. 655 newSnippet = append(newSnippet, runes...) 656 // Append last part of existing snippet. 657 if start := r.End; start < s.End { 658 newSnippet = append(newSnippet, snippet[start-s.Start:]...) 659 } 660 // Adjust snippet range to include replacement. 661 if r.Start < s.Start { 662 s.Start = r.Start 663 } 664 s.End = s.Start + len(newSnippet) 665 s.Text = string(newSnippet) 666 e.Snippet = s 667 } 668 669 // UTF16Index converts the given index in runes into an index in utf16 characters. 670 func (e *editorState) UTF16Index(runes int) int { 671 if runes == -1 { 672 return -1 673 } 674 if runes < e.Snippet.Start { 675 // Assume runes before sippet are one UTF-16 character each. 676 return runes 677 } 678 chars := e.Snippet.Start 679 runes -= e.Snippet.Start 680 for _, r := range e.Snippet.Text { 681 if runes == 0 { 682 break 683 } 684 runes-- 685 chars++ 686 if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar { 687 chars++ 688 } 689 } 690 // Assume runes after snippets are one UTF-16 character each. 691 return chars + runes 692 } 693 694 // RunesIndex converts the given index in utf16 characters to an index in runes. 695 func (e *editorState) RunesIndex(chars int) int { 696 if chars == -1 { 697 return -1 698 } 699 if chars < e.Snippet.Start { 700 // Assume runes before offset are one UTF-16 character each. 701 return chars 702 } 703 runes := e.Snippet.Start 704 chars -= e.Snippet.Start 705 for _, r := range e.Snippet.Text { 706 if chars == 0 { 707 break 708 } 709 chars-- 710 runes++ 711 if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar { 712 chars-- 713 } 714 } 715 // Assume runes after snippets are one UTF-16 character each. 716 return runes + chars 717 } 718 719 func (w *Window) waitAck(d driver) { 720 for { 721 select { 722 case f := <-w.driverFuncs: 723 f(d) 724 case w.out <- theFlushEvent: 725 // A dummy event went through, so we know the application has processed the previous event. 726 return 727 case <-w.immediateRedraws: 728 // Invalidate was called during frame processing. 729 w.setNextFrame(time.Time{}) 730 w.updateAnimation(d) 731 } 732 } 733 } 734 735 func (w *Window) destroyGPU() { 736 if w.gpu != nil { 737 w.ctx.Lock() 738 w.gpu.Release() 739 w.ctx.Unlock() 740 w.gpu = nil 741 } 742 if w.ctx != nil { 743 w.ctx.Release() 744 w.ctx = nil 745 } 746 } 747 748 // waitFrame waits for the client to either call FrameEvent.Frame 749 // or to continue event handling. 750 func (w *Window) waitFrame(d driver) *op.Ops { 751 for { 752 select { 753 case f := <-w.driverFuncs: 754 f(d) 755 case frame := <-w.frames: 756 // The client called FrameEvent.Frame. 757 return frame 758 case w.out <- theFlushEvent: 759 // The client ignored FrameEvent and continued processing 760 // events. 761 return nil 762 case <-w.immediateRedraws: 763 // Invalidate was called during frame processing. 764 w.setNextFrame(time.Time{}) 765 } 766 } 767 } 768 769 // updateSemantics refreshes the semantics tree, the id to node map and the ids of 770 // updated nodes. 771 func (w *Window) updateSemantics() { 772 if w.semantic.uptodate { 773 return 774 } 775 w.semantic.uptodate = true 776 w.semantic.prevTree, w.semantic.tree = w.semantic.tree, w.semantic.prevTree 777 w.semantic.tree = w.queue.q.AppendSemantics(w.semantic.tree[:0]) 778 w.semantic.root = w.semantic.tree[0].ID 779 for _, n := range w.semantic.tree { 780 w.semantic.ids[n.ID] = n 781 } 782 } 783 784 // collectSemanticDiffs traverses the previous semantic tree, noting changed nodes. 785 func (w *Window) collectSemanticDiffs(diffs *[]router.SemanticID, n router.SemanticNode) { 786 newNode, exists := w.semantic.ids[n.ID] 787 // Ignore deleted nodes, as their disappearance will be reported through an 788 // ancestor node. 789 if !exists { 790 return 791 } 792 diff := newNode.Desc != n.Desc || len(n.Children) != len(newNode.Children) 793 for i, ch := range n.Children { 794 if !diff { 795 newCh := newNode.Children[i] 796 diff = ch.ID != newCh.ID 797 } 798 w.collectSemanticDiffs(diffs, ch) 799 } 800 if diff { 801 *diffs = append(*diffs, n.ID) 802 } 803 } 804 805 func (w *Window) updateState(d driver) { 806 for { 807 select { 808 case f := <-w.driverFuncs: 809 f(d) 810 case <-w.redraws: 811 w.setNextFrame(time.Time{}) 812 w.updateAnimation(d) 813 default: 814 return 815 } 816 } 817 } 818 819 func (w *Window) processEvent(d driver, e event.Event) bool { 820 select { 821 case <-w.destroy: 822 return false 823 default: 824 } 825 switch e2 := e.(type) { 826 case system.StageEvent: 827 if e2.Stage < system.StageInactive { 828 if w.gpu != nil { 829 w.ctx.Lock() 830 w.gpu.Release() 831 w.gpu = nil 832 w.ctx.Unlock() 833 } 834 } 835 w.stage = e2.Stage 836 w.updateAnimation(d) 837 w.out <- e 838 w.waitAck(d) 839 case frameEvent: 840 if e2.Size == (image.Point{}) { 841 panic(errors.New("internal error: zero-sized Draw")) 842 } 843 if w.stage < system.StageInactive { 844 // No drawing if not visible. 845 break 846 } 847 w.metric = e2.Metric 848 var frameStart time.Time 849 if w.queue.q.Profiling() { 850 frameStart = time.Now() 851 } 852 w.hasNextFrame = false 853 e2.Frame = w.update 854 e2.Queue = &w.queue 855 856 // Prepare the decorations and update the frame insets. 857 wrapper := &w.decorations.Ops 858 wrapper.Reset() 859 viewport := image.Rectangle{ 860 Min: image.Point{ 861 X: e2.Metric.Dp(e2.Insets.Left), 862 Y: e2.Metric.Dp(e2.Insets.Top), 863 }, 864 Max: image.Point{ 865 X: e2.Size.X - e2.Metric.Dp(e2.Insets.Right), 866 Y: e2.Size.Y - e2.Metric.Dp(e2.Insets.Bottom), 867 }, 868 } 869 // Scroll to focus if viewport is shrinking in any dimension. 870 if old, new := w.viewport.Size(), viewport.Size(); new.X < old.X || new.Y < old.Y { 871 w.queue.q.RevealFocus(viewport) 872 } 873 w.viewport = viewport 874 viewSize := e2.Size 875 m := op.Record(wrapper) 876 size, offset := w.decorate(d, e2.FrameEvent, wrapper) 877 e2.FrameEvent.Size = size 878 deco := m.Stop() 879 w.out <- e2.FrameEvent 880 frame := w.waitFrame(d) 881 var signal chan<- struct{} 882 if frame != nil { 883 signal = w.frameAck 884 off := op.Offset(offset).Push(wrapper) 885 ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal)) 886 off.Pop() 887 } 888 deco.Add(wrapper) 889 if err := w.validateAndProcess(d, viewSize, e2.Sync, wrapper, signal); err != nil { 890 w.destroyGPU() 891 w.out <- system.DestroyEvent{Err: err} 892 close(w.destroy) 893 break 894 } 895 w.processFrame(d, frameStart) 896 w.updateCursor(d) 897 case system.DestroyEvent: 898 w.destroyGPU() 899 w.out <- e2 900 close(w.destroy) 901 case ViewEvent: 902 w.out <- e2 903 w.waitAck(d) 904 case ConfigEvent: 905 w.decorations.Config = e2.Config 906 e2.Config = w.effectiveConfig() 907 w.out <- e2 908 case wakeupEvent: 909 case event.Event: 910 handled := w.queue.q.Queue(e2) 911 if e, ok := e.(key.Event); ok && !handled { 912 if e.State == key.Press { 913 handled = true 914 isMobile := runtime.GOOS == "ios" || runtime.GOOS == "android" 915 switch { 916 case e.Name == key.NameTab && e.Modifiers == 0: 917 w.moveFocus(router.FocusForward, d) 918 case e.Name == key.NameTab && e.Modifiers == key.ModShift: 919 w.moveFocus(router.FocusBackward, d) 920 case e.Name == key.NameUpArrow && e.Modifiers == 0 && isMobile: 921 w.moveFocus(router.FocusUp, d) 922 case e.Name == key.NameDownArrow && e.Modifiers == 0 && isMobile: 923 w.moveFocus(router.FocusDown, d) 924 case e.Name == key.NameLeftArrow && e.Modifiers == 0 && isMobile: 925 w.moveFocus(router.FocusLeft, d) 926 case e.Name == key.NameRightArrow && e.Modifiers == 0 && isMobile: 927 w.moveFocus(router.FocusRight, d) 928 default: 929 handled = false 930 } 931 } 932 // As a special case, the top-most input handler receives all unhandled 933 // events. 934 if !handled { 935 handled = w.queue.q.QueueTopmost(e) 936 } 937 } 938 w.updateCursor(d) 939 if handled { 940 w.setNextFrame(time.Time{}) 941 w.updateAnimation(d) 942 } 943 return handled 944 } 945 return true 946 } 947 948 // NextEvent blocks until an event is received from the window, such as 949 // [io/system.FrameEvent]. It blocks forever if called after [io/system.DestroyEvent] 950 // has been returned. 951 func (w *Window) NextEvent() event.Event { 952 state := &w.eventState 953 if !state.created { 954 state.created = true 955 if err := newWindow(&w.callbacks, state.initialOpts); err != nil { 956 close(w.destroy) 957 return system.DestroyEvent{Err: err} 958 } 959 } 960 for { 961 var ( 962 wakeups <-chan struct{} 963 timeC <-chan time.Time 964 ) 965 if state.wakeup != nil { 966 wakeups = w.wakeups 967 if state.timer != nil { 968 timeC = state.timer.C 969 } 970 } 971 select { 972 case t := <-w.scheduledRedraws: 973 if state.timer != nil { 974 state.timer.Stop() 975 } 976 state.timer = time.NewTimer(time.Until(t)) 977 case e := <-w.out: 978 // Receiving a flushEvent indicates to the platform backend that 979 // all previous events have been processed by the user program. 980 if _, ok := e.(flushEvent); ok { 981 break 982 } 983 return e 984 case <-timeC: 985 select { 986 case w.redraws <- struct{}{}: 987 state.wakeup() 988 default: 989 } 990 case <-wakeups: 991 state.wakeup() 992 case state.wakeup = <-w.wakeupFuncs: 993 } 994 } 995 } 996 997 func (w *Window) updateCursor(d driver) { 998 if c := w.queue.q.Cursor(); c != w.cursor { 999 w.cursor = c 1000 d.SetCursor(c) 1001 } 1002 } 1003 1004 func (w *Window) fallbackDecorate() bool { 1005 cnf := w.decorations.Config 1006 return w.decorations.enabled && !cnf.Decorated && cnf.Mode != Fullscreen && !w.nocontext 1007 } 1008 1009 // decorate the window if enabled and returns the corresponding Insets. 1010 func (w *Window) decorate(d driver, e system.FrameEvent, o *op.Ops) (size, offset image.Point) { 1011 if !w.fallbackDecorate() { 1012 return e.Size, image.Pt(0, 0) 1013 } 1014 deco := w.decorations.Decorations 1015 allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize | 1016 system.ActionClose | system.ActionMove 1017 style := material.Decorations(w.decorations.Theme, deco, allActions, w.decorations.Config.Title) 1018 // Update the decorations based on the current window mode. 1019 var actions system.Action 1020 switch m := w.decorations.Config.Mode; m { 1021 case Windowed: 1022 actions |= system.ActionUnmaximize 1023 case Minimized: 1024 actions |= system.ActionMinimize 1025 case Maximized: 1026 actions |= system.ActionMaximize 1027 case Fullscreen: 1028 actions |= system.ActionFullscreen 1029 default: 1030 panic(fmt.Errorf("unknown WindowMode %v", m)) 1031 } 1032 deco.Perform(actions) 1033 gtx := layout.Context{ 1034 Ops: o, 1035 Now: e.Now, 1036 Queue: e.Queue, 1037 Metric: e.Metric, 1038 Constraints: layout.Exact(e.Size), 1039 } 1040 style.Layout(gtx) 1041 // Update the window based on the actions on the decorations. 1042 w.Perform(deco.Update(gtx)) 1043 // Offset to place the frame content below the decorations. 1044 decoHeight := gtx.Dp(w.decorations.Config.decoHeight) 1045 if w.decorations.currentHeight != decoHeight { 1046 w.decorations.currentHeight = decoHeight 1047 w.out <- ConfigEvent{Config: w.effectiveConfig()} 1048 } 1049 e.Size.Y -= w.decorations.currentHeight 1050 return e.Size, image.Pt(0, decoHeight) 1051 } 1052 1053 func (w *Window) effectiveConfig() Config { 1054 cnf := w.decorations.Config 1055 cnf.Size.Y -= w.decorations.currentHeight 1056 cnf.Decorated = w.decorations.enabled || cnf.Decorated 1057 return cnf 1058 } 1059 1060 // Perform the actions on the window. 1061 func (w *Window) Perform(actions system.Action) { 1062 walkActions(actions, func(action system.Action) { 1063 switch action { 1064 case system.ActionMinimize: 1065 w.Option(Minimized.Option()) 1066 case system.ActionMaximize: 1067 w.Option(Maximized.Option()) 1068 case system.ActionUnmaximize: 1069 w.Option(Windowed.Option()) 1070 default: 1071 return 1072 } 1073 actions &^= action 1074 }) 1075 if actions == 0 { 1076 return 1077 } 1078 for { 1079 select { 1080 case old := <-w.actions: 1081 actions |= old 1082 case w.actions <- actions: 1083 w.wakeup() 1084 return 1085 } 1086 } 1087 } 1088 1089 func (q *queue) Events(k event.Tag) []event.Event { 1090 return q.q.Events(k) 1091 } 1092 1093 // Title sets the title of the window. 1094 func Title(t string) Option { 1095 return func(_ unit.Metric, cnf *Config) { 1096 cnf.Title = t 1097 } 1098 } 1099 1100 // Size sets the size of the window. The mode will be changed to Windowed. 1101 func Size(w, h unit.Dp) Option { 1102 if w <= 0 { 1103 panic("width must be larger than or equal to 0") 1104 } 1105 if h <= 0 { 1106 panic("height must be larger than or equal to 0") 1107 } 1108 return func(m unit.Metric, cnf *Config) { 1109 cnf.Mode = Windowed 1110 cnf.Size = image.Point{ 1111 X: m.Dp(w), 1112 Y: m.Dp(h), 1113 } 1114 } 1115 } 1116 1117 // MaxSize sets the maximum size of the window. 1118 func MaxSize(w, h unit.Dp) Option { 1119 if w <= 0 { 1120 panic("width must be larger than or equal to 0") 1121 } 1122 if h <= 0 { 1123 panic("height must be larger than or equal to 0") 1124 } 1125 return func(m unit.Metric, cnf *Config) { 1126 cnf.MaxSize = image.Point{ 1127 X: m.Dp(w), 1128 Y: m.Dp(h), 1129 } 1130 } 1131 } 1132 1133 // MinSize sets the minimum size of the window. 1134 func MinSize(w, h unit.Dp) Option { 1135 if w <= 0 { 1136 panic("width must be larger than or equal to 0") 1137 } 1138 if h <= 0 { 1139 panic("height must be larger than or equal to 0") 1140 } 1141 return func(m unit.Metric, cnf *Config) { 1142 cnf.MinSize = image.Point{ 1143 X: m.Dp(w), 1144 Y: m.Dp(h), 1145 } 1146 } 1147 } 1148 1149 // StatusColor sets the color of the Android status bar. 1150 func StatusColor(color color.NRGBA) Option { 1151 return func(_ unit.Metric, cnf *Config) { 1152 cnf.StatusColor = color 1153 } 1154 } 1155 1156 // NavigationColor sets the color of the navigation bar on Android, or the address bar in browsers. 1157 func NavigationColor(color color.NRGBA) Option { 1158 return func(_ unit.Metric, cnf *Config) { 1159 cnf.NavigationColor = color 1160 } 1161 } 1162 1163 // CustomRenderer controls whether the window contents is 1164 // rendered by the client. If true, no GPU context is created. 1165 // 1166 // Caller must assume responsibility for rendering which includes 1167 // initializing the render backend, swapping the framebuffer and 1168 // handling frame pacing. 1169 func CustomRenderer(custom bool) Option { 1170 return func(_ unit.Metric, cnf *Config) { 1171 cnf.CustomRenderer = custom 1172 } 1173 } 1174 1175 // Decorated controls whether Gio and/or the platform are responsible 1176 // for drawing window decorations. Providing false indicates that 1177 // the application will either be undecorated or will draw its own decorations. 1178 func Decorated(enabled bool) Option { 1179 return func(_ unit.Metric, cnf *Config) { 1180 cnf.Decorated = enabled 1181 } 1182 } 1183 1184 // flushEvent is sent to detect when the user program 1185 // has completed processing of all prior events. Its an 1186 // [io/event.Event] but only for internal use. 1187 type flushEvent struct{} 1188 1189 func (t flushEvent) ImplementsEvent() {} 1190 1191 // theFlushEvent avoids allocating garbage when sending 1192 // flushEvents. 1193 var theFlushEvent flushEvent