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