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