github.com/Seikaijyu/gio@v0.0.1/app/window.go (about)

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