github.com/utopiagio/gio@v0.0.8/app/window.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package app 4 5 import ( 6 //"log" 7 "errors" 8 "fmt" 9 "image" 10 "image/color" 11 "reflect" 12 "runtime" 13 "sync" 14 "time" 15 "unicode/utf8" 16 17 "github.com/utopiagio/gio/f32" 18 "github.com/utopiagio/gio/font/gofont" 19 "github.com/utopiagio/gio/gpu" 20 "github.com/utopiagio/gio/internal/debug" 21 "github.com/utopiagio/gio/internal/ops" 22 "github.com/utopiagio/gio/io/event" 23 "github.com/utopiagio/gio/io/input" 24 "github.com/utopiagio/gio/io/key" 25 "github.com/utopiagio/gio/io/pointer" 26 "github.com/utopiagio/gio/io/system" 27 "github.com/utopiagio/gio/layout" 28 "github.com/utopiagio/gio/op" 29 "github.com/utopiagio/gio/text" 30 "github.com/utopiagio/gio/unit" 31 "github.com/utopiagio/gio/widget" 32 "github.com/utopiagio/gio/widget/material" 33 ) 34 35 // Option configures a window. 36 type Option func(unit.Metric, *Config) 37 38 // Window represents an operating system window. 39 // 40 // The zero-value Window is useful, and calling any method on 41 // it creates and shows a new GUI window. On iOS or Android, 42 // the first Window represents the the window previously 43 // created by the platform. 44 // 45 // More than one Window is not supported on iOS, Android, 46 // WebAssembly. 47 type Window struct { 48 ctx context 49 gpu gpu.GPU 50 // timer tracks the delayed invalidate goroutine. 51 timer struct { 52 // quit is shuts down the goroutine. 53 quit chan struct{} 54 // update the invalidate time. 55 update chan time.Time 56 } 57 58 animating bool 59 hasNextFrame bool 60 nextFrame time.Time 61 // viewport is the latest frame size with insets applied. 62 viewport image.Rectangle 63 // metric is the metric from the most recent frame. 64 metric unit.Metric 65 queue input.Router 66 cursor pointer.Cursor 67 decorations struct { 68 op.Ops 69 // enabled tracks the Decorated option as 70 // given to the Option method. It may differ 71 // from Config.Decorated depending on platform 72 // capability. 73 enabled bool 74 Config 75 height unit.Dp 76 currentHeight int 77 *material.Theme 78 *widget.Decorations 79 } 80 nocontext bool 81 // semantic data, lazily evaluated if requested by a backend to speed up 82 // the cases where semantic data is not needed. 83 semantic struct { 84 // uptodate tracks whether the fields below are up to date. 85 uptodate bool 86 root input.SemanticID 87 prevTree []input.SemanticNode 88 tree []input.SemanticNode 89 ids map[input.SemanticID]input.SemanticNode 90 } 91 imeState editorState 92 driver driver 93 // basic is the driver interface that is needed even after the window is gone. 94 basic basicDriver 95 once sync.Once 96 // coalesced tracks the most recent events waiting to be delivered 97 // to the client. 98 coalesced eventSummary 99 // frame tracks the most recently frame event. 100 lastFrame struct { 101 sync bool 102 size image.Point 103 off image.Point 104 deco op.CallOp 105 } 106 } 107 108 type eventSummary struct { 109 wakeup bool 110 cfg *ConfigEvent 111 view *ViewEvent 112 frame *frameEvent 113 destroy *DestroyEvent 114 } 115 116 type callbacks struct { 117 w *Window 118 } 119 120 func decoHeightOpt(h unit.Dp) Option { 121 return func(m unit.Metric, c *Config) { 122 c.decoHeight = h 123 } 124 } 125 126 func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, sigChan chan<- struct{}) error { 127 signal := func() { 128 if sigChan != nil { 129 // We're done with frame, let the client continue. 130 sigChan <- struct{}{} 131 // Signal at most once. 132 sigChan = nil 133 } 134 } 135 defer signal() 136 for { 137 if w.gpu == nil && !w.nocontext { 138 var err error 139 if w.ctx == nil { 140 w.ctx, err = w.driver.NewContext() 141 if err != nil { 142 return err 143 } 144 sync = true 145 } 146 } 147 if sync && w.ctx != nil { 148 if err := w.ctx.Refresh(); err != nil { 149 if errors.Is(err, errOutOfDate) { 150 // Surface couldn't be created for transient reasons. Skip 151 // this frame and wait for the next. 152 return nil 153 } 154 w.destroyGPU() 155 if errors.Is(err, gpu.ErrDeviceLost) { 156 continue 157 } 158 return err 159 } 160 } 161 if w.ctx != nil { 162 if err := w.ctx.Lock(); err != nil { 163 w.destroyGPU() 164 return err 165 } 166 } 167 if w.gpu == nil && !w.nocontext { 168 gpu, err := gpu.New(w.ctx.API()) 169 if err != nil { 170 w.ctx.Unlock() 171 w.destroyGPU() 172 return err 173 } 174 w.gpu = gpu 175 } 176 if w.gpu != nil { 177 if err := w.frame(frame, size); err != nil { 178 w.ctx.Unlock() 179 if errors.Is(err, errOutOfDate) { 180 // GPU surface needs refreshing. 181 sync = true 182 continue 183 } 184 w.destroyGPU() 185 if errors.Is(err, gpu.ErrDeviceLost) { 186 continue 187 } 188 return err 189 } 190 } 191 w.queue.Frame(frame) 192 // Let the client continue as soon as possible, in particular before 193 // a potentially blocking Present. 194 signal() 195 var err error 196 if w.gpu != nil { 197 err = w.ctx.Present() 198 w.ctx.Unlock() 199 } 200 return err 201 } 202 } 203 204 func (w *Window) frame(frame *op.Ops, viewport image.Point) error { 205 if runtime.GOOS == "js" { 206 // Use transparent black when Gio is embedded, to allow mixing of Gio and 207 // foreign content below. 208 w.gpu.Clear(color.NRGBA{A: 0x00, R: 0x00, G: 0x00, B: 0x00}) 209 } else { 210 w.gpu.Clear(color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}) 211 } 212 target, err := w.ctx.RenderTarget() 213 if err != nil { 214 return err 215 } 216 return w.gpu.Frame(frame, target, viewport) 217 } 218 219 func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) { 220 wrapper := &w.decorations.Ops 221 off := op.Offset(w.lastFrame.off).Push(wrapper) 222 ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal)) 223 off.Pop() 224 w.lastFrame.deco.Add(wrapper) 225 if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil { 226 w.destroyGPU() 227 w.driver.ProcessEvent(DestroyEvent{Err: err}) 228 return 229 } 230 w.updateState() 231 w.updateCursor() 232 } 233 234 func (w *Window) updateState() { 235 for k := range w.semantic.ids { 236 delete(w.semantic.ids, k) 237 } 238 w.semantic.uptodate = false 239 q := &w.queue 240 switch q.TextInputState() { 241 case input.TextInputOpen: 242 w.driver.ShowTextInput(true) 243 case input.TextInputClose: 244 w.driver.ShowTextInput(false) 245 } 246 if hint, ok := q.TextInputHint(); ok { 247 w.driver.SetInputHint(hint) 248 } 249 if mime, txt, ok := q.WriteClipboard(); ok { 250 w.driver.WriteClipboard(mime, txt) 251 } 252 if q.ClipboardRequested() { 253 w.driver.ReadClipboard() 254 } 255 oldState := w.imeState 256 newState := oldState 257 newState.EditorState = q.EditorState() 258 if newState != oldState { 259 w.imeState = newState 260 w.driver.EditorStateChanged(oldState, newState) 261 } 262 if t, ok := q.WakeupTime(); ok { 263 w.setNextFrame(t) 264 } 265 w.updateAnimation() 266 } 267 268 // Invalidate the window such that a [FrameEvent] will be generated immediately. 269 // If the window is inactive, an unspecified event is sent instead. 270 // 271 // Note that Invalidate is intended for externally triggered updates, such as a 272 // response from a network request. The [op.InvalidateCmd] command is more efficient 273 // for animation. 274 // 275 // Invalidate is safe for concurrent use. 276 func (w *Window) Invalidate() { 277 w.init() 278 w.basic.Invalidate() 279 } 280 281 282 // Option applies the options to the window. The options are hints; the platform is 283 // free to ignore or adjust them. 284 // ************************************************************************** 285 // ************ RNW Added check if driver running 15.04.2024 ************ 286 func (w *Window) Option(opts ...Option) { 287 if len(opts) == 0 { 288 return 289 } 290 // Only run init once on first call to new Window object 291 if w.driver == nil { 292 w.init(opts...) 293 } else { 294 // Dont run Run() function to update config on first call 295 // Config is updated after WM_MOVE and WM_SIZE messages received 296 w.Run(func() { 297 cnf := Config{Decorated: w.decorations.enabled} 298 for _, opt := range opts { 299 opt(w.metric, &cnf) 300 } 301 w.decorations.enabled = cnf.Decorated 302 decoHeight := w.decorations.height 303 if !w.decorations.enabled { 304 decoHeight = 0 305 } 306 opts = append(opts, decoHeightOpt(decoHeight)) 307 w.driver.Configure(opts) 308 w.setNextFrame(time.Time{}) 309 w.updateAnimation() 310 }) 311 } 312 } 313 // ************************************************************************* 314 315 // Run f in the same thread as the native window event loop, and wait for f to 316 // return or the window to close. 317 // 318 // Note that most programs should not call Run; configuring a Window with 319 // [CustomRenderer] is a notable exception. 320 func (w *Window) Run(f func()) { 321 w.init() 322 if w.driver == nil { 323 return 324 } 325 done := make(chan struct{}) 326 w.driver.Run(func() { 327 defer close(done) 328 f() 329 }) 330 <-done 331 } 332 333 func (w *Window) updateAnimation() { 334 if w.driver == nil { 335 return 336 } 337 animate := false 338 if w.hasNextFrame { 339 if dt := time.Until(w.nextFrame); dt <= 0 { 340 animate = true 341 } else { 342 // Schedule redraw. 343 w.scheduleInvalidate(w.nextFrame) 344 } 345 } 346 if animate != w.animating { 347 w.animating = animate 348 w.driver.SetAnimating(animate) 349 } 350 } 351 352 func (w *Window) scheduleInvalidate(t time.Time) { 353 if w.timer.quit == nil { 354 w.timer.quit = make(chan struct{}) 355 w.timer.update = make(chan time.Time) 356 go func() { 357 var timer *time.Timer 358 for { 359 var timeC <-chan time.Time 360 if timer != nil { 361 timeC = timer.C 362 } 363 select { 364 case <-w.timer.quit: 365 w.timer.quit <- struct{}{} 366 return 367 case t := <-w.timer.update: 368 if timer != nil { 369 timer.Stop() 370 } 371 timer = time.NewTimer(time.Until(t)) 372 case <-timeC: 373 w.Invalidate() 374 } 375 } 376 }() 377 } 378 w.timer.update <- t 379 } 380 381 func (w *Window) setNextFrame(at time.Time) { 382 if !w.hasNextFrame || at.Before(w.nextFrame) { 383 w.hasNextFrame = true 384 w.nextFrame = at 385 } 386 } 387 388 func (c *callbacks) SetDriver(d basicDriver) { 389 c.w.basic = d 390 if d, ok := d.(driver); ok { 391 c.w.driver = d 392 } 393 } 394 395 func (c *callbacks) ProcessFrame(frame *op.Ops, ack chan<- struct{}) { 396 c.w.processFrame(frame, ack) 397 } 398 399 func (c *callbacks) ProcessEvent(e event.Event) bool { 400 return c.w.processEvent(e) 401 } 402 403 // SemanticRoot returns the ID of the semantic root. 404 func (c *callbacks) SemanticRoot() input.SemanticID { 405 c.w.updateSemantics() 406 return c.w.semantic.root 407 } 408 409 // LookupSemantic looks up a semantic node from an ID. The zero ID denotes the root. 410 func (c *callbacks) LookupSemantic(semID input.SemanticID) (input.SemanticNode, bool) { 411 c.w.updateSemantics() 412 n, found := c.w.semantic.ids[semID] 413 return n, found 414 } 415 416 func (c *callbacks) AppendSemanticDiffs(diffs []input.SemanticID) []input.SemanticID { 417 c.w.updateSemantics() 418 if tree := c.w.semantic.prevTree; len(tree) > 0 { 419 c.w.collectSemanticDiffs(&diffs, c.w.semantic.prevTree[0]) 420 } 421 return diffs 422 } 423 424 func (c *callbacks) SemanticAt(pos f32.Point) (input.SemanticID, bool) { 425 c.w.updateSemantics() 426 return c.w.queue.SemanticAt(pos) 427 } 428 429 func (c *callbacks) EditorState() editorState { 430 return c.w.imeState 431 } 432 433 func (c *callbacks) SetComposingRegion(r key.Range) { 434 c.w.imeState.compose = r 435 } 436 437 func (c *callbacks) EditorInsert(text string) { 438 sel := c.w.imeState.Selection.Range 439 c.EditorReplace(sel, text) 440 start := sel.Start 441 if sel.End < start { 442 start = sel.End 443 } 444 sel.Start = start + utf8.RuneCountInString(text) 445 sel.End = sel.Start 446 c.SetEditorSelection(sel) 447 } 448 449 func (c *callbacks) EditorReplace(r key.Range, text string) { 450 c.w.imeState.Replace(r, text) 451 c.w.driver.ProcessEvent(key.EditEvent{Range: r, Text: text}) 452 c.w.driver.ProcessEvent(key.SnippetEvent(c.w.imeState.Snippet.Range)) 453 } 454 455 func (c *callbacks) SetEditorSelection(r key.Range) { 456 c.w.imeState.Selection.Range = r 457 c.w.driver.ProcessEvent(key.SelectionEvent(r)) 458 } 459 460 func (c *callbacks) SetEditorSnippet(r key.Range) { 461 if sn := c.EditorState().Snippet.Range; sn == r { 462 // No need to expand. 463 return 464 } 465 c.w.driver.ProcessEvent(key.SnippetEvent(r)) 466 } 467 468 func (w *Window) moveFocus(dir key.FocusDirection) { 469 w.queue.MoveFocus(dir) 470 if _, handled := w.queue.WakeupTime(); handled { 471 w.queue.RevealFocus(w.viewport) 472 } else { 473 var v image.Point 474 switch dir { 475 case key.FocusRight: 476 v = image.Pt(+1, 0) 477 case key.FocusLeft: 478 v = image.Pt(-1, 0) 479 case key.FocusDown: 480 v = image.Pt(0, +1) 481 case key.FocusUp: 482 v = image.Pt(0, -1) 483 default: 484 return 485 } 486 const scrollABit = unit.Dp(50) 487 dist := v.Mul(int(w.metric.Dp(scrollABit))) 488 w.queue.ScrollFocus(dist) 489 } 490 } 491 492 func (c *callbacks) ClickFocus() { 493 c.w.queue.ClickFocus() 494 c.w.setNextFrame(time.Time{}) 495 c.w.updateAnimation() 496 } 497 498 func (c *callbacks) ActionAt(p f32.Point) (system.Action, bool) { 499 return c.w.queue.ActionAt(p) 500 } 501 502 func (w *Window) destroyGPU() { 503 if w.gpu != nil { 504 w.ctx.Lock() 505 w.gpu.Release() 506 w.ctx.Unlock() 507 w.gpu = nil 508 } 509 if w.ctx != nil { 510 w.ctx.Release() 511 w.ctx = nil 512 } 513 } 514 515 // updateSemantics refreshes the semantics tree, the id to node map and the ids of 516 // updated nodes. 517 func (w *Window) updateSemantics() { 518 if w.semantic.uptodate { 519 return 520 } 521 w.semantic.uptodate = true 522 w.semantic.prevTree, w.semantic.tree = w.semantic.tree, w.semantic.prevTree 523 w.semantic.tree = w.queue.AppendSemantics(w.semantic.tree[:0]) 524 w.semantic.root = w.semantic.tree[0].ID 525 for _, n := range w.semantic.tree { 526 w.semantic.ids[n.ID] = n 527 } 528 } 529 530 // collectSemanticDiffs traverses the previous semantic tree, noting changed nodes. 531 func (w *Window) collectSemanticDiffs(diffs *[]input.SemanticID, n input.SemanticNode) { 532 newNode, exists := w.semantic.ids[n.ID] 533 // Ignore deleted nodes, as their disappearance will be reported through an 534 // ancestor node. 535 if !exists { 536 return 537 } 538 diff := newNode.Desc != n.Desc || len(n.Children) != len(newNode.Children) 539 for i, ch := range n.Children { 540 if !diff { 541 newCh := newNode.Children[i] 542 diff = ch.ID != newCh.ID 543 } 544 w.collectSemanticDiffs(diffs, ch) 545 } 546 if diff { 547 *diffs = append(*diffs, n.ID) 548 } 549 } 550 551 func (c *callbacks) Invalidate() { 552 c.w.setNextFrame(time.Time{}) 553 c.w.updateAnimation() 554 // Guarantee a wakeup, even when not animating. 555 c.w.processEvent(wakeupEvent{}) 556 } 557 558 func (c *callbacks) nextEvent() (event.Event, bool) { 559 s := &c.w.coalesced 560 // Every event counts as a wakeup. 561 defer func() { s.wakeup = false }() 562 switch { 563 case s.view != nil: 564 e := *s.view 565 s.view = nil 566 return e, true 567 case s.destroy != nil: 568 e := *s.destroy 569 // Clear pending events after DestroyEvent is delivered. 570 *s = eventSummary{} 571 return e, true 572 case s.cfg != nil: 573 e := *s.cfg 574 s.cfg = nil 575 return e, true 576 case s.frame != nil: 577 e := *s.frame 578 s.frame = nil 579 return e.FrameEvent, true 580 case s.wakeup: 581 return wakeupEvent{}, true 582 } 583 return nil, false 584 } 585 586 func (w *Window) processEvent(e event.Event) bool { 587 switch e2 := e.(type) { 588 case wakeupEvent: 589 w.coalesced.wakeup = true 590 case frameEvent: 591 if e2.Size == (image.Point{}) { 592 panic(errors.New("internal error: zero-sized Draw")) 593 } 594 w.metric = e2.Metric 595 w.hasNextFrame = false 596 e2.Frame = w.driver.Frame 597 e2.Source = w.queue.Source() 598 // Prepare the decorations and update the frame insets. 599 viewport := image.Rectangle{ 600 Min: image.Point{ 601 X: e2.Metric.Dp(e2.Insets.Left), 602 Y: e2.Metric.Dp(e2.Insets.Top), 603 }, 604 Max: image.Point{ 605 X: e2.Size.X - e2.Metric.Dp(e2.Insets.Right), 606 Y: e2.Size.Y - e2.Metric.Dp(e2.Insets.Bottom), 607 }, 608 } 609 // Scroll to focus if viewport is shrinking in any dimension. 610 if old, new := w.viewport.Size(), viewport.Size(); new.X < old.X || new.Y < old.Y { 611 w.queue.RevealFocus(viewport) 612 } 613 w.viewport = viewport 614 wrapper := &w.decorations.Ops 615 wrapper.Reset() 616 m := op.Record(wrapper) 617 offset := w.decorate(e2.FrameEvent, wrapper) 618 w.lastFrame.deco = m.Stop() 619 w.lastFrame.size = e2.Size 620 w.lastFrame.sync = e2.Sync 621 w.lastFrame.off = offset 622 e2.Size = e2.Size.Sub(offset) 623 w.coalesced.frame = &e2 624 case DestroyEvent: 625 w.destroyGPU() 626 w.driver = nil 627 if q := w.timer.quit; q != nil { 628 q <- struct{}{} 629 <-q 630 } 631 w.coalesced.destroy = &e2 632 case ViewEvent: 633 if reflect.ValueOf(e2).IsZero() && w.gpu != nil { 634 w.ctx.Lock() 635 w.gpu.Release() 636 w.gpu = nil 637 w.ctx.Unlock() 638 } 639 w.coalesced.view = &e2 640 case ConfigEvent: 641 wasFocused := w.decorations.Config.Focused 642 w.decorations.Config = e2.Config 643 e2.Config = w.effectiveConfig() 644 w.coalesced.cfg = &e2 645 if f := w.decorations.Config.Focused; f != wasFocused { 646 w.queue.Queue(key.FocusEvent{Focus: f}) 647 } 648 t, handled := w.queue.WakeupTime() 649 if handled { 650 w.setNextFrame(t) 651 w.updateAnimation() 652 } 653 return handled 654 case event.Event: 655 focusDir := key.FocusDirection(-1) 656 if e, ok := e2.(key.Event); ok && e.State == key.Press { 657 isMobile := runtime.GOOS == "ios" || runtime.GOOS == "android" 658 switch { 659 case e.Name == key.NameTab && e.Modifiers == 0: 660 focusDir = key.FocusForward 661 case e.Name == key.NameTab && e.Modifiers == key.ModShift: 662 focusDir = key.FocusBackward 663 case e.Name == key.NameUpArrow && e.Modifiers == 0 && isMobile: 664 focusDir = key.FocusUp 665 case e.Name == key.NameDownArrow && e.Modifiers == 0 && isMobile: 666 focusDir = key.FocusDown 667 case e.Name == key.NameLeftArrow && e.Modifiers == 0 && isMobile: 668 focusDir = key.FocusLeft 669 case e.Name == key.NameRightArrow && e.Modifiers == 0 && isMobile: 670 focusDir = key.FocusRight 671 } 672 } 673 e := e2 674 if focusDir != -1 { 675 e = input.SystemEvent{Event: e} 676 } 677 w.queue.Queue(e) 678 t, handled := w.queue.WakeupTime() 679 if focusDir != -1 && !handled { 680 w.moveFocus(focusDir) 681 t, handled = w.queue.WakeupTime() 682 } 683 w.updateCursor() 684 if handled { 685 w.setNextFrame(t) 686 w.updateAnimation() 687 } 688 return handled 689 } 690 return true 691 } 692 693 // Event blocks until an event is received from the window, such as 694 // [FrameEvent], or until [Invalidate] is called. 695 func (w *Window) Event() event.Event { 696 w.init() 697 return w.basic.Event() 698 } 699 700 func (w *Window) init(initial ...Option) { 701 702 w.once.Do(func() { 703 //fmt.Println("Window.init().........") 704 debug.Parse() 705 // Measure decoration height. 706 deco := new(widget.Decorations) 707 theme := material.NewTheme() 708 theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular())) 709 decoStyle := material.Decorations(theme, deco, 0, "") 710 gtx := layout.Context{ 711 Ops: new(op.Ops), 712 // Measure in Dp. 713 Metric: unit.Metric{}, 714 } 715 // Allow plenty of space. 716 gtx.Constraints.Max.Y = 200 717 dims := decoStyle.Layout(gtx) 718 decoHeight := unit.Dp(dims.Size.Y) 719 defaultOptions := []Option{ 720 Pos(-10000, -10000), // ******** RNW Added Pos (image.Point) to config 01.11.2023 ********* 721 Size(800, 600), 722 Title("Gio"), 723 Decorated(true), 724 decoHeightOpt(decoHeight), 725 } 726 options := append(defaultOptions, initial...) 727 var cnf Config 728 cnf.apply(unit.Metric{}, options) 729 730 w.nocontext = cnf.CustomRenderer 731 w.decorations.Theme = theme 732 w.decorations.Decorations = deco 733 w.decorations.enabled = cnf.Decorated 734 w.decorations.height = decoHeight 735 w.imeState.compose = key.Range{Start: -1, End: -1} 736 w.semantic.ids = make(map[input.SemanticID]input.SemanticNode) 737 newWindow(&callbacks{w}, options) 738 }) 739 } 740 741 func (w *Window) updateCursor() { 742 if c := w.queue.Cursor(); c != w.cursor { 743 w.cursor = c 744 w.driver.SetCursor(c) 745 } 746 } 747 748 func (w *Window) fallbackDecorate() bool { 749 cnf := w.decorations.Config 750 return w.decorations.enabled && !cnf.Decorated && cnf.Mode != Fullscreen && !w.nocontext 751 } 752 753 // decorate the window if enabled and returns the corresponding Insets. 754 func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point { 755 if !w.fallbackDecorate() { 756 return image.Pt(0, 0) 757 } 758 deco := w.decorations.Decorations 759 allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize | 760 system.ActionClose | system.ActionMove 761 style := material.Decorations(w.decorations.Theme, deco, allActions, w.decorations.Config.Title) 762 // Update the decorations based on the current window mode. 763 var actions system.Action 764 switch m := w.decorations.Config.Mode; m { 765 case Windowed: 766 actions |= system.ActionUnmaximize 767 case Minimized: 768 actions |= system.ActionMinimize 769 case Maximized: 770 actions |= system.ActionMaximize 771 case Fullscreen: 772 actions |= system.ActionFullscreen 773 default: 774 panic(fmt.Errorf("unknown WindowMode %v", m)) 775 } 776 deco.Perform(actions) 777 gtx := layout.Context{ 778 Ops: o, 779 Now: e.Now, 780 Source: e.Source, 781 Metric: e.Metric, 782 Constraints: layout.Exact(e.Size), 783 } 784 // Update the window based on the actions on the decorations. 785 opts, acts := splitActions(deco.Update(gtx)) 786 w.driver.Configure(opts) 787 w.driver.Perform(acts) 788 style.Layout(gtx) 789 // Offset to place the frame content below the decorations. 790 decoHeight := gtx.Dp(w.decorations.Config.decoHeight) 791 if w.decorations.currentHeight != decoHeight { 792 w.decorations.currentHeight = decoHeight 793 w.coalesced.cfg = &ConfigEvent{Config: w.effectiveConfig()} 794 } 795 return image.Pt(0, decoHeight) 796 } 797 798 func (w *Window) effectiveConfig() Config { 799 cnf := w.decorations.Config 800 cnf.Size.Y -= w.decorations.currentHeight 801 cnf.Decorated = w.decorations.enabled || cnf.Decorated 802 return cnf 803 } 804 805 // splitActions splits options from actions and return them and the remaining 806 // actions. 807 func splitActions(actions system.Action) ([]Option, system.Action) { 808 var opts []Option 809 walkActions(actions, func(action system.Action) { 810 switch action { 811 case system.ActionMinimize: 812 opts = append(opts, Minimized.Option()) 813 case system.ActionMaximize: 814 opts = append(opts, Maximized.Option()) 815 case system.ActionUnmaximize: 816 opts = append(opts, Windowed.Option()) 817 case system.ActionFullscreen: 818 opts = append(opts, Fullscreen.Option()) 819 default: 820 return 821 } 822 actions &^= action 823 }) 824 return opts, actions 825 } 826 827 // Perform the actions on the window. 828 func (w *Window) Perform(actions system.Action) { 829 opts, acts := splitActions(actions) 830 w.Option(opts...) 831 if acts == 0 { 832 return 833 } 834 w.Run(func() { 835 w.driver.Perform(actions) 836 }) 837 } 838 839 // Title sets the title of the window. 840 func Title(t string) Option { 841 return func(_ unit.Metric, cnf *Config) { 842 cnf.Title = t 843 } 844 } 845 846 // ************************************************************************** 847 // ************ RNW Added GetAbsClientPos (image.Point) to config 01.11.2023 ************ 848 // GetClientPos returns the position of the client window in device pixels. 849 func (w *Window) GetAbsClientPos() (xPx int, yPx int) { 850 pos := w.decorations.Config.Pos // deco.Config.Pos specified in screen pixels 851 pos.Y += w.metric.Dp(w.decorations.height) // convert deco.height to screen pixels specified in device pixels // deco.currentHeight specified in device pixels 852 return pos.X, pos.Y 853 } 854 855 // ************************************************************************** 856 // ************ RNW Added GetClientPos (image.Point) to config 01.11.2023 ************ 857 // GetClientPos returns the position of the client window in screen pixels. 858 func (w *Window) GetClientPos() (xPx int, yPx int) { 859 return 0, 0 860 } 861 // ************************************************************************** 862 863 // ************************************************************************** 864 // ************ RNW Added GetWindowSize (image.Point) to config 01.11.2023 ************ 865 // GetWindowSize returns the size of the window in screen pixels. 866 func (w *Window) GetSize() (widthPx int, heightPx int) { 867 size := w.decorations.Config.Size // deco.Config.Pos specified as image.Point in screen pixels 868 return size.X, size.Y 869 } 870 // ************************************************************************** 871 872 // ************************************************************************** 873 // ************ RNW Added GetPos (x, y) to config 18.04.2024 ************ 874 // GetPos returns the position of the client window in device pixels. 875 func (w *Window) GetPos() (xPx int, yPx int) { 876 //return w.XDp, w.YDp 877 pos := w.decorations.Config.Pos // deco.Config.Pos specified as image.Point in screen pixels 878 //xDp = w.metric.PxToDp(pos.X) 879 //yDp = w.metric.PxToDp(pos.Y) 880 //yDp += w.metric.Dp(w.decorations.height) // add deco.height 881 return pos.X, pos.Y 882 } 883 884 // ************************************************************************** 885 // ************ RNW Added GetWindowPos (int, int) to config 18.04.2024 ************ 886 // GetWindowPos returns the screen position of the window in screen pixels. 887 func (w *Window) GetWindowPos() (xPx int, yPx int) { 888 pos := w.decorations.Config.Pos // deco.Config.Pos specified in screen pixels 889 return pos.X, pos.Y 890 } 891 // ************************************************************************** 892 893 // ************************************************************************** 894 // ************ RNW Added GetClientSize (image.Point) to config 01.11.2023 ************ 895 // GetClientSize returns the size of the window client area in screen pixels. 896 func (w *Window) GetClientSize() (widthPx int, heightPx int) { 897 size := w.decorations.Config.Size // deco.Config.size specified in screen pixels 898 size.Y -= w.metric.Dp(w.decorations.height) // convert deco.height to screen pixels specified in device pixels 899 return size.X, size.Y 900 } 901 // ************************************************************************** 902 903 // ************************************************************************** 904 // ************ RNW Added GetWindowSize (image.Point) to config 01.11.2023 ************ 905 // GetWindowSize returns the size of the window in screen pixels. 906 func (w *Window) GetWindowSize() (widthPx int, heightPx int) { 907 size := w.decorations.Config.Size // deco.Config.size is specified in screen pixels 908 return size.X, size.Y 909 } 910 // ************************************************************************** 911 912 // ************************************************************************** 913 // ************ RNW Added Pos (image.Point) to config 01.11.2023 ************ 914 // Pos sets the position of the window, position specified in device pixels. The mode will be changed to Windowed. 915 func Pos(x, y int) Option { 916 return func(m unit.Metric, cnf *Config) { 917 cnf.Mode = Windowed 918 cnf.Pos = image.Point{ 919 X: x, // image.Point.X in pixels 920 Y: y, // image.Point.Y in pixels 921 } 922 } 923 } 924 // ************************************************************************** 925 926 // Size sets the size of the window, size specified in device pixels. The mode will be changed to Windowed. 927 func Size(w, h int) Option { 928 if w <= 0 { 929 panic("width must be larger than or equal to 0") 930 } 931 if h <= 0 { 932 panic("height must be larger than or equal to 0") 933 } 934 return func(m unit.Metric, cnf *Config) { 935 cnf.Mode = Windowed 936 cnf.Size = image.Point{ 937 X: w, // image.Point.X in pixels 938 Y: h, // image.Point.Y in pixels 939 } 940 } 941 } 942 943 // MaxSize sets the maximum size of the window. 944 func MaxSize(w, h unit.Dp) Option { 945 if w <= 0 { 946 panic("width must be larger than or equal to 0") 947 } 948 if h <= 0 { 949 panic("height must be larger than or equal to 0") 950 } 951 return func(m unit.Metric, cnf *Config) { 952 cnf.MaxSize = image.Point{ 953 X: m.Dp(w), 954 Y: m.Dp(h), 955 } 956 } 957 } 958 959 // MinSize sets the minimum size of the window. 960 func MinSize(w, h unit.Dp) Option { 961 if w <= 0 { 962 panic("width must be larger than or equal to 0") 963 } 964 if h <= 0 { 965 panic("height must be larger than or equal to 0") 966 } 967 return func(m unit.Metric, cnf *Config) { 968 cnf.MinSize = image.Point{ 969 X: m.Dp(w), 970 Y: m.Dp(h), 971 } 972 } 973 } 974 975 // StatusColor sets the color of the Android status bar. 976 func StatusColor(color color.NRGBA) Option { 977 return func(_ unit.Metric, cnf *Config) { 978 cnf.StatusColor = color 979 } 980 } 981 982 // NavigationColor sets the color of the navigation bar on Android, or the address bar in browsers. 983 func NavigationColor(color color.NRGBA) Option { 984 return func(_ unit.Metric, cnf *Config) { 985 cnf.NavigationColor = color 986 } 987 } 988 989 // CustomRenderer controls whether the window contents is 990 // rendered by the client. If true, no GPU context is created. 991 // 992 // Caller must assume responsibility for rendering which includes 993 // initializing the render backend, swapping the framebuffer and 994 // handling frame pacing. 995 func CustomRenderer(custom bool) Option { 996 return func(_ unit.Metric, cnf *Config) { 997 cnf.CustomRenderer = custom 998 } 999 } 1000 1001 // Decorated controls whether Gio and/or the platform are responsible 1002 // for drawing window decorations. Providing false indicates that 1003 // the application will either be undecorated or will draw its own decorations. 1004 func Decorated(enabled bool) Option { 1005 return func(_ unit.Metric, cnf *Config) { 1006 cnf.Decorated = enabled 1007 } 1008 } 1009 1010 // flushEvent is sent to detect when the user program 1011 // has completed processing of all prior events. Its an 1012 // [io/event.Event] but only for internal use. 1013 type flushEvent struct{} 1014 1015 func (t flushEvent) ImplementsEvent() {} 1016 1017 // theFlushEvent avoids allocating garbage when sending 1018 // flushEvents. 1019 var theFlushEvent flushEvent