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