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