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() {}