// SPDX-License-Identifier: Unlicense OR MIT package app import ( "errors" "image" "image/color" "gioui.org/io/event" "gioui.org/io/key" "gioui.org/op" "gioui.org/gpu" "gioui.org/io/pointer" "gioui.org/io/system" "gioui.org/unit" ) // errOutOfDate is reported when the GPU surface dimensions or properties no // longer match the window. var errOutOfDate = errors.New("app: GPU surface out of date") // Config describes a Window configuration. type Config struct { // Size is the window dimensions (Width, Height). Size image.Point // MaxSize is the window maximum allowed dimensions. MaxSize image.Point // MinSize is the window minimum allowed dimensions. MinSize image.Point // Title is the window title displayed in its decoration bar. Title string // WindowMode is the window mode. Mode WindowMode // StatusColor is the color of the Android status bar. StatusColor color.NRGBA // NavigationColor is the color of the navigation bar // on Android, or the address bar in browsers. NavigationColor color.NRGBA // Orientation is the current window orientation. Orientation Orientation // CustomRenderer is true when the window content is rendered by the // client. CustomRenderer bool // Decorated reports whether window decorations are provided automatically. Decorated bool // Focused reports whether has the keyboard focus. Focused bool // decoHeight is the height of the fallback decoration for platforms such // as Wayland that may need fallback client-side decorations. decoHeight unit.Dp } // ConfigEvent is sent whenever the configuration of a Window changes. type ConfigEvent struct { Config Config } func (c *Config) apply(m unit.Metric, options []Option) { for _, o := range options { o(m, c) } } type wakeupEvent struct{} // WindowMode is the window mode (WindowMode.Option sets it). // Note that mode can be changed programatically as well as by the user // clicking on the minimize/maximize buttons on the window's title bar. type WindowMode uint8 const ( // Windowed is the normal window mode with OS specific window decorations. Windowed WindowMode = iota // Fullscreen is the full screen window mode. Fullscreen // Minimized is for systems where the window can be minimized to an icon. Minimized // Maximized is for systems where the window can be made to fill the available monitor area. Maximized ) // Option changes the mode of a Window. func (m WindowMode) Option() Option { return func(_ unit.Metric, cnf *Config) { cnf.Mode = m } } // String returns the mode name. func (m WindowMode) String() string { switch m { case Windowed: return "windowed" case Fullscreen: return "fullscreen" case Minimized: return "minimized" case Maximized: return "maximized" } return "" } // Orientation is the orientation of the app (Orientation.Option sets it). // // Supported platforms are Android and JS. type Orientation uint8 const ( // AnyOrientation allows the window to be freely orientated. AnyOrientation Orientation = iota // LandscapeOrientation constrains the window to landscape orientations. LandscapeOrientation // PortraitOrientation constrains the window to portrait orientations. PortraitOrientation ) func (o Orientation) Option() Option { return func(_ unit.Metric, cnf *Config) { cnf.Orientation = o } } func (o Orientation) String() string { switch o { case AnyOrientation: return "any" case LandscapeOrientation: return "landscape" case PortraitOrientation: return "portrait" } return "" } // eventLoop implements the functionality required for drivers where // window event loops must run on a separate thread. type eventLoop struct { win *callbacks // wakeup is the callback to wake up the event loop. wakeup func() // driverFuncs is a channel of functions to run the next // time the window loop waits for events. driverFuncs chan func() // invalidates is notified when an invalidate is requested by the client. invalidates chan struct{} // immediateInvalidates is an optimistic invalidates that doesn't require a wakeup. immediateInvalidates chan struct{} // events is where the platform backend delivers events bound for the // user program. events chan event.Event frames chan *op.Ops frameAck chan struct{} // delivering avoids re-entrant event delivery. delivering bool } type frameEvent struct { FrameEvent Sync bool } type context interface { API() gpu.API RenderTarget() (gpu.RenderTarget, error) Present() error Refresh() error Release() Lock() error Unlock() } // driver is the interface for the platform implementation // of a window. type driver interface { // Event blocks until an event is available and returns it. Event() event.Event // Invalidate requests a FrameEvent. Invalidate() // SetAnimating sets the animation flag. When the window is animating, // FrameEvents are delivered as fast as the display can handle them. SetAnimating(anim bool) // ShowTextInput updates the virtual keyboard state. ShowTextInput(show bool) SetInputHint(mode key.InputHint) NewContext() (context, error) // ReadClipboard requests the clipboard content. ReadClipboard() // WriteClipboard requests a clipboard write. WriteClipboard(mime string, s []byte) // Configure the window. Configure([]Option) // SetCursor updates the current cursor to name. SetCursor(cursor pointer.Cursor) // Perform actions on the window. Perform(system.Action) // EditorStateChanged notifies the driver that the editor state changed. EditorStateChanged(old, new editorState) // Run a function on the window thread. Run(f func()) // Frame receives a frame. Frame(frame *op.Ops) // ProcessEvent processes an event. ProcessEvent(e event.Event) } type windowRendezvous struct { in chan windowAndConfig out chan windowAndConfig windows chan struct{} } type windowAndConfig struct { window *callbacks options []Option } func newWindowRendezvous() *windowRendezvous { wr := &windowRendezvous{ in: make(chan windowAndConfig), out: make(chan windowAndConfig), windows: make(chan struct{}), } go func() { in := wr.in var window windowAndConfig var out chan windowAndConfig for { select { case w := <-in: window = w out = wr.out case out <- window: } } }() return wr } func newEventLoop(w *callbacks, wakeup func()) *eventLoop { return &eventLoop{ win: w, wakeup: wakeup, events: make(chan event.Event), invalidates: make(chan struct{}, 1), immediateInvalidates: make(chan struct{}), frames: make(chan *op.Ops), frameAck: make(chan struct{}), driverFuncs: make(chan func(), 1), } } // Frame receives a frame and waits for its processing. It is called by // the client goroutine. func (e *eventLoop) Frame(frame *op.Ops) { e.frames <- frame <-e.frameAck } // Event returns the next available event. It is called by the client // goroutine. func (e *eventLoop) Event() event.Event { for { evt := <-e.events // Receiving a flushEvent indicates to the platform backend that // all previous events have been processed by the user program. if _, ok := evt.(flushEvent); ok { continue } return evt } } // Invalidate requests invalidation of the window. It is called by the client // goroutine. func (e *eventLoop) Invalidate() { select { case e.immediateInvalidates <- struct{}{}: // The event loop was waiting, no need for a wakeup. case e.invalidates <- struct{}{}: // The event loop is sleeping, wake it up. e.wakeup() default: // A redraw is pending. } } // Run f in the window loop thread. It is called by the client goroutine. func (e *eventLoop) Run(f func()) { e.driverFuncs <- f e.wakeup() } // FlushEvents delivers pending events to the client. func (e *eventLoop) FlushEvents() { if e.delivering { return } e.delivering = true defer func() { e.delivering = false }() for { evt, ok := e.win.nextEvent() if !ok { break } e.deliverEvent(evt) } } func (e *eventLoop) deliverEvent(evt event.Event) { var frames <-chan *op.Ops for { select { case f := <-e.driverFuncs: f() case frame := <-frames: // The client called FrameEvent.Frame. frames = nil e.win.ProcessFrame(frame, e.frameAck) case e.events <- evt: switch evt.(type) { case flushEvent, DestroyEvent: // DestroyEvents are not flushed. return case FrameEvent: frames = e.frames } evt = theFlushEvent case <-e.invalidates: e.win.Invalidate() case <-e.immediateInvalidates: e.win.Invalidate() } } } func (e *eventLoop) Wakeup() { for { select { case f := <-e.driverFuncs: f() case <-e.invalidates: e.win.Invalidate() case <-e.immediateInvalidates: e.win.Invalidate() default: return } } } func walkActions(actions system.Action, do func(system.Action)) { for a := system.Action(1); actions != 0; a <<= 1 { if actions&a != 0 { actions &^= a do(a) } } } func (wakeupEvent) ImplementsEvent() {} func (ConfigEvent) ImplementsEvent() {}