gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/app/os.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package app
     4  
     5  import (
     6  	"errors"
     7  	"image"
     8  	"image/color"
     9  
    10  	"gioui.org/io/event"
    11  	"gioui.org/io/key"
    12  	"gioui.org/op"
    13  
    14  	"gioui.org/gpu"
    15  	"gioui.org/io/pointer"
    16  	"gioui.org/io/system"
    17  	"gioui.org/unit"
    18  )
    19  
    20  // errOutOfDate is reported when the GPU surface dimensions or properties no
    21  // longer match the window.
    22  var errOutOfDate = errors.New("app: GPU surface out of date")
    23  
    24  // Config describes a Window configuration.
    25  type Config struct {
    26  	// Size is the window dimensions (Width, Height).
    27  	Size image.Point
    28  	// MaxSize is the window maximum allowed dimensions.
    29  	MaxSize image.Point
    30  	// MinSize is the window minimum allowed dimensions.
    31  	MinSize image.Point
    32  	// Title is the window title displayed in its decoration bar.
    33  	Title string
    34  	// WindowMode is the window mode.
    35  	Mode WindowMode
    36  	// StatusColor is the color of the Android status bar.
    37  	StatusColor color.NRGBA
    38  	// NavigationColor is the color of the navigation bar
    39  	// on Android, or the address bar in browsers.
    40  	NavigationColor color.NRGBA
    41  	// Orientation is the current window orientation.
    42  	Orientation Orientation
    43  	// CustomRenderer is true when the window content is rendered by the
    44  	// client.
    45  	CustomRenderer bool
    46  	// Decorated reports whether window decorations are provided automatically.
    47  	Decorated bool
    48  	// Focused reports whether has the keyboard focus.
    49  	Focused bool
    50  	// decoHeight is the height of the fallback decoration for platforms such
    51  	// as Wayland that may need fallback client-side decorations.
    52  	decoHeight unit.Dp
    53  }
    54  
    55  // ConfigEvent is sent whenever the configuration of a Window changes.
    56  type ConfigEvent struct {
    57  	Config Config
    58  }
    59  
    60  func (c *Config) apply(m unit.Metric, options []Option) {
    61  	for _, o := range options {
    62  		o(m, c)
    63  	}
    64  }
    65  
    66  type wakeupEvent struct{}
    67  
    68  // WindowMode is the window mode (WindowMode.Option sets it).
    69  // Note that mode can be changed programatically as well as by the user
    70  // clicking on the minimize/maximize buttons on the window's title bar.
    71  type WindowMode uint8
    72  
    73  const (
    74  	// Windowed is the normal window mode with OS specific window decorations.
    75  	Windowed WindowMode = iota
    76  	// Fullscreen is the full screen window mode.
    77  	Fullscreen
    78  	// Minimized is for systems where the window can be minimized to an icon.
    79  	Minimized
    80  	// Maximized is for systems where the window can be made to fill the available monitor area.
    81  	Maximized
    82  )
    83  
    84  // Option changes the mode of a Window.
    85  func (m WindowMode) Option() Option {
    86  	return func(_ unit.Metric, cnf *Config) {
    87  		cnf.Mode = m
    88  	}
    89  }
    90  
    91  // String returns the mode name.
    92  func (m WindowMode) String() string {
    93  	switch m {
    94  	case Windowed:
    95  		return "windowed"
    96  	case Fullscreen:
    97  		return "fullscreen"
    98  	case Minimized:
    99  		return "minimized"
   100  	case Maximized:
   101  		return "maximized"
   102  	}
   103  	return ""
   104  }
   105  
   106  // Orientation is the orientation of the app (Orientation.Option sets it).
   107  //
   108  // Supported platforms are Android and JS.
   109  type Orientation uint8
   110  
   111  const (
   112  	// AnyOrientation allows the window to be freely orientated.
   113  	AnyOrientation Orientation = iota
   114  	// LandscapeOrientation constrains the window to landscape orientations.
   115  	LandscapeOrientation
   116  	// PortraitOrientation constrains the window to portrait orientations.
   117  	PortraitOrientation
   118  )
   119  
   120  func (o Orientation) Option() Option {
   121  	return func(_ unit.Metric, cnf *Config) {
   122  		cnf.Orientation = o
   123  	}
   124  }
   125  
   126  func (o Orientation) String() string {
   127  	switch o {
   128  	case AnyOrientation:
   129  		return "any"
   130  	case LandscapeOrientation:
   131  		return "landscape"
   132  	case PortraitOrientation:
   133  		return "portrait"
   134  	}
   135  	return ""
   136  }
   137  
   138  // eventLoop implements the functionality required for drivers where
   139  // window event loops must run on a separate thread.
   140  type eventLoop struct {
   141  	win *callbacks
   142  	// wakeup is the callback to wake up the event loop.
   143  	wakeup func()
   144  	// driverFuncs is a channel of functions to run the next
   145  	// time the window loop waits for events.
   146  	driverFuncs chan func()
   147  	// invalidates is notified when an invalidate is requested by the client.
   148  	invalidates chan struct{}
   149  	// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
   150  	immediateInvalidates chan struct{}
   151  	// events is where the platform backend delivers events bound for the
   152  	// user program.
   153  	events   chan event.Event
   154  	frames   chan *op.Ops
   155  	frameAck chan struct{}
   156  	// delivering avoids re-entrant event delivery.
   157  	delivering bool
   158  }
   159  
   160  type frameEvent struct {
   161  	FrameEvent
   162  
   163  	Sync bool
   164  }
   165  
   166  type context interface {
   167  	API() gpu.API
   168  	RenderTarget() (gpu.RenderTarget, error)
   169  	Present() error
   170  	Refresh() error
   171  	Release()
   172  	Lock() error
   173  	Unlock()
   174  }
   175  
   176  // basicDriver is the subset of [driver] that may be called even after
   177  // a window is destroyed.
   178  type basicDriver interface {
   179  	// Event blocks until an even is available and returns it.
   180  	Event() event.Event
   181  	// Invalidate requests a FrameEvent.
   182  	Invalidate()
   183  }
   184  
   185  // driver is the interface for the platform implementation
   186  // of a window.
   187  type driver interface {
   188  	basicDriver
   189  	// SetAnimating sets the animation flag. When the window is animating,
   190  	// FrameEvents are delivered as fast as the display can handle them.
   191  	SetAnimating(anim bool)
   192  	// ShowTextInput updates the virtual keyboard state.
   193  	ShowTextInput(show bool)
   194  	SetInputHint(mode key.InputHint)
   195  	NewContext() (context, error)
   196  	// ReadClipboard requests the clipboard content.
   197  	ReadClipboard()
   198  	// WriteClipboard requests a clipboard write.
   199  	WriteClipboard(mime string, s []byte)
   200  	// Configure the window.
   201  	Configure([]Option)
   202  	// SetCursor updates the current cursor to name.
   203  	SetCursor(cursor pointer.Cursor)
   204  	// Wakeup wakes up the event loop and sends a WakeupEvent.
   205  	// Wakeup()
   206  	// Perform actions on the window.
   207  	Perform(system.Action)
   208  	// EditorStateChanged notifies the driver that the editor state changed.
   209  	EditorStateChanged(old, new editorState)
   210  	// Run a function on the window thread.
   211  	Run(f func())
   212  	// Frame receives a frame.
   213  	Frame(frame *op.Ops)
   214  	// ProcessEvent processes an event.
   215  	ProcessEvent(e event.Event)
   216  }
   217  
   218  type windowRendezvous struct {
   219  	in      chan windowAndConfig
   220  	out     chan windowAndConfig
   221  	windows chan struct{}
   222  }
   223  
   224  type windowAndConfig struct {
   225  	window  *callbacks
   226  	options []Option
   227  }
   228  
   229  func newWindowRendezvous() *windowRendezvous {
   230  	wr := &windowRendezvous{
   231  		in:      make(chan windowAndConfig),
   232  		out:     make(chan windowAndConfig),
   233  		windows: make(chan struct{}),
   234  	}
   235  	go func() {
   236  		in := wr.in
   237  		var window windowAndConfig
   238  		var out chan windowAndConfig
   239  		for {
   240  			select {
   241  			case w := <-in:
   242  				window = w
   243  				out = wr.out
   244  			case out <- window:
   245  			}
   246  		}
   247  	}()
   248  	return wr
   249  }
   250  
   251  func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
   252  	return &eventLoop{
   253  		win:                  w,
   254  		wakeup:               wakeup,
   255  		events:               make(chan event.Event),
   256  		invalidates:          make(chan struct{}, 1),
   257  		immediateInvalidates: make(chan struct{}),
   258  		frames:               make(chan *op.Ops),
   259  		frameAck:             make(chan struct{}),
   260  		driverFuncs:          make(chan func(), 1),
   261  	}
   262  }
   263  
   264  // Frame receives a frame and waits for its processing. It is called by
   265  // the client goroutine.
   266  func (e *eventLoop) Frame(frame *op.Ops) {
   267  	e.frames <- frame
   268  	<-e.frameAck
   269  }
   270  
   271  // Event returns the next available event. It is called by the client
   272  // goroutine.
   273  func (e *eventLoop) Event() event.Event {
   274  	for {
   275  		evt := <-e.events
   276  		// Receiving a flushEvent indicates to the platform backend that
   277  		// all previous events have been processed by the user program.
   278  		if _, ok := evt.(flushEvent); ok {
   279  			continue
   280  		}
   281  		return evt
   282  	}
   283  }
   284  
   285  // Invalidate requests invalidation of the window. It is called by the client
   286  // goroutine.
   287  func (e *eventLoop) Invalidate() {
   288  	select {
   289  	case e.immediateInvalidates <- struct{}{}:
   290  		// The event loop was waiting, no need for a wakeup.
   291  	case e.invalidates <- struct{}{}:
   292  		// The event loop is sleeping, wake it up.
   293  		e.wakeup()
   294  	default:
   295  		// A redraw is pending.
   296  	}
   297  }
   298  
   299  // Run f in the window loop thread. It is called by the client goroutine.
   300  func (e *eventLoop) Run(f func()) {
   301  	e.driverFuncs <- f
   302  	e.wakeup()
   303  }
   304  
   305  // FlushEvents delivers pending events to the client.
   306  func (e *eventLoop) FlushEvents() {
   307  	if e.delivering {
   308  		return
   309  	}
   310  	e.delivering = true
   311  	defer func() { e.delivering = false }()
   312  	for {
   313  		evt, ok := e.win.nextEvent()
   314  		if !ok {
   315  			break
   316  		}
   317  		e.deliverEvent(evt)
   318  	}
   319  }
   320  
   321  func (e *eventLoop) deliverEvent(evt event.Event) {
   322  	var frames <-chan *op.Ops
   323  	for {
   324  		select {
   325  		case f := <-e.driverFuncs:
   326  			f()
   327  		case frame := <-frames:
   328  			// The client called FrameEvent.Frame.
   329  			frames = nil
   330  			e.win.ProcessFrame(frame, e.frameAck)
   331  		case e.events <- evt:
   332  			switch evt.(type) {
   333  			case flushEvent, DestroyEvent:
   334  				// DestroyEvents are not flushed.
   335  				return
   336  			case FrameEvent:
   337  				frames = e.frames
   338  			}
   339  			evt = theFlushEvent
   340  		case <-e.invalidates:
   341  			e.win.Invalidate()
   342  		case <-e.immediateInvalidates:
   343  			e.win.Invalidate()
   344  		}
   345  	}
   346  }
   347  
   348  func (e *eventLoop) Wakeup() {
   349  	for {
   350  		select {
   351  		case f := <-e.driverFuncs:
   352  			f()
   353  		case <-e.invalidates:
   354  			e.win.Invalidate()
   355  		case <-e.immediateInvalidates:
   356  			e.win.Invalidate()
   357  		default:
   358  			return
   359  		}
   360  	}
   361  }
   362  
   363  func walkActions(actions system.Action, do func(system.Action)) {
   364  	for a := system.Action(1); actions != 0; a <<= 1 {
   365  		if actions&a != 0 {
   366  			actions &^= a
   367  			do(a)
   368  		}
   369  	}
   370  }
   371  
   372  func (wakeupEvent) ImplementsEvent() {}
   373  func (ConfigEvent) ImplementsEvent() {}