mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
d4c5e54375
Signed-off-by: Elias Naur <mail@eliasnaur.com>
366 lines
8.9 KiB
Go
366 lines
8.9 KiB
Go
// 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() {}
|