gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/window.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package app 4 5 import ( 6 "errors" 7 "fmt" 8 "image" 9 "time" 10 11 "gioui.org/ui" 12 "gioui.org/ui/app/internal/gpu" 13 "gioui.org/ui/app/internal/input" 14 "gioui.org/ui/system" 15 ) 16 17 // WindowOption configures a Window. 18 type WindowOption struct { 19 apply func(opts *windowOptions) 20 } 21 22 type windowOptions struct { 23 Width, Height ui.Value 24 Title string 25 } 26 27 // Window represents an operating system window. 28 type Window struct { 29 driver *window 30 lastFrame time.Time 31 drawStart time.Time 32 gpu *gpu.GPU 33 34 out chan ui.Event 35 in chan ui.Event 36 ack chan struct{} 37 invalidates chan struct{} 38 frames chan *ui.Ops 39 40 stage Stage 41 animating bool 42 hasNextFrame bool 43 nextFrame time.Time 44 delayedDraw *time.Timer 45 46 queue Queue 47 } 48 49 // Queue is an ui.Queue implementation that distributes system events 50 // to the input handlers declared in the most recent call to Update. 51 type Queue struct { 52 q input.Router 53 } 54 55 // driverEvent is sent when a new native driver 56 // is available for the Window. 57 type driverEvent struct { 58 driver *window 59 } 60 61 // driver is the interface for the platform implementation 62 // of a Window. 63 var _ interface { 64 // setAnimating sets the animation flag. When the window is animating, 65 // UpdateEvents are delivered as fast as the display can handle them. 66 setAnimating(anim bool) 67 // showTextInput updates the virtual keyboard state. 68 showTextInput(show bool) 69 } = (*window)(nil) 70 71 // Pre-allocate the ack event to avoid garbage. 72 var ackEvent ui.Event 73 74 // NewWindow creates a new window for a set of window 75 // options. The options are hints; the platform is free to 76 // ignore or adjust them. 77 // 78 // If opts are nil, a set of sensible defaults are used. 79 // 80 // If the current program is running on iOS and Android, 81 // NewWindow returns the window previously created by the 82 // platform. 83 // 84 // BUG: Calling NewWindow more than once is not yet supported. 85 func NewWindow(options ...WindowOption) *Window { 86 opts := &windowOptions{ 87 Width: ui.Dp(800), 88 Height: ui.Dp(600), 89 Title: "Gio", 90 } 91 92 for _, o := range options { 93 o.apply(opts) 94 } 95 96 w := &Window{ 97 in: make(chan ui.Event), 98 out: make(chan ui.Event), 99 ack: make(chan struct{}), 100 invalidates: make(chan struct{}, 1), 101 frames: make(chan *ui.Ops), 102 } 103 go w.run(opts) 104 return w 105 } 106 107 // Events returns the channel where events are delivered. 108 func (w *Window) Events() <-chan ui.Event { 109 return w.out 110 } 111 112 // Queue returns the Window's event queue. The queue contains 113 // the events received since the last UpdateEvent. 114 func (w *Window) Queue() *Queue { 115 return &w.queue 116 } 117 118 // Update updates the Window. Paint operations updates the 119 // window contents, input operations declare input handlers, 120 // and so on. The supplied operations list completely replaces 121 // the window state from previous calls. 122 func (w *Window) Update(frame *ui.Ops) { 123 w.frames <- frame 124 } 125 126 func (w *Window) draw(size image.Point, frame *ui.Ops) { 127 var drawDur time.Duration 128 if !w.drawStart.IsZero() { 129 drawDur = time.Since(w.drawStart) 130 w.drawStart = time.Time{} 131 } 132 w.gpu.Draw(w.queue.q.Profiling(), size, frame) 133 w.queue.q.Frame(frame) 134 now := time.Now() 135 switch w.queue.q.TextInputState() { 136 case input.TextInputOpen: 137 w.driver.showTextInput(true) 138 case input.TextInputClose: 139 w.driver.showTextInput(false) 140 } 141 frameDur := now.Sub(w.lastFrame) 142 frameDur = frameDur.Truncate(100 * time.Microsecond) 143 w.lastFrame = now 144 if w.queue.q.Profiling() { 145 q := 100 * time.Microsecond 146 timings := fmt.Sprintf("tot:%7s cpu:%7s %s", frameDur.Round(q), drawDur.Round(q), w.gpu.Timings()) 147 w.queue.q.AddProfile(system.ProfileEvent{Timings: timings}) 148 w.setNextFrame(time.Time{}) 149 } 150 if t, ok := w.queue.q.WakeupTime(); ok { 151 w.setNextFrame(t) 152 } 153 w.updateAnimation() 154 } 155 156 // Invalidate the window such that a UpdateEvent will be generated 157 // immediately. If the window is inactive, the event is sent when the 158 // window becomes active. 159 // Invalidate is safe for concurrent use. 160 func (w *Window) Invalidate() { 161 select { 162 case w.invalidates <- struct{}{}: 163 default: 164 } 165 } 166 167 func (w *Window) updateAnimation() { 168 animate := false 169 if w.delayedDraw != nil { 170 w.delayedDraw.Stop() 171 w.delayedDraw = nil 172 } 173 if w.stage >= StageRunning && w.hasNextFrame { 174 if dt := time.Until(w.nextFrame); dt <= 0 { 175 animate = true 176 } else { 177 w.delayedDraw = time.NewTimer(dt) 178 } 179 } 180 if animate != w.animating { 181 w.animating = animate 182 w.driver.setAnimating(animate) 183 } 184 } 185 186 func (w *Window) setNextFrame(at time.Time) { 187 if !w.hasNextFrame || at.Before(w.nextFrame) { 188 w.hasNextFrame = true 189 w.nextFrame = at 190 } 191 } 192 193 func (w *Window) setDriver(d *window) { 194 w.event(driverEvent{d}) 195 } 196 197 func (w *Window) event(e ui.Event) { 198 w.in <- e 199 <-w.ack 200 } 201 202 func (w *Window) waitAck() { 203 // Send a dummy event; when it gets through we 204 // know the application has processed the previous event. 205 w.out <- ackEvent 206 } 207 208 // Prematurely destroy the window and wait for the native window 209 // destroy event. 210 func (w *Window) destroy(err error) { 211 // Ack the current event. 212 w.ack <- struct{}{} 213 w.out <- DestroyEvent{err} 214 for e := range w.in { 215 w.ack <- struct{}{} 216 if _, ok := e.(DestroyEvent); ok { 217 return 218 } 219 } 220 } 221 222 func (w *Window) run(opts *windowOptions) { 223 defer close(w.in) 224 defer close(w.out) 225 if err := createWindow(w, opts); err != nil { 226 w.out <- DestroyEvent{err} 227 return 228 } 229 for { 230 var timer <-chan time.Time 231 if w.delayedDraw != nil { 232 timer = w.delayedDraw.C 233 } 234 select { 235 case <-timer: 236 w.setNextFrame(time.Time{}) 237 w.updateAnimation() 238 case <-w.invalidates: 239 w.setNextFrame(time.Time{}) 240 w.updateAnimation() 241 case e := <-w.in: 242 switch e2 := e.(type) { 243 case StageEvent: 244 if w.gpu != nil { 245 if e2.Stage < StageRunning { 246 w.gpu.Release() 247 w.gpu = nil 248 } else { 249 w.gpu.Refresh() 250 } 251 } 252 w.stage = e2.Stage 253 w.updateAnimation() 254 w.out <- e 255 w.waitAck() 256 case UpdateEvent: 257 if e2.Size == (image.Point{}) { 258 panic(errors.New("internal error: zero-sized Draw")) 259 } 260 if w.stage < StageRunning { 261 // No drawing if not visible. 262 break 263 } 264 w.drawStart = time.Now() 265 w.hasNextFrame = false 266 w.out <- e 267 var frame *ui.Ops 268 // Wait for either a frame or the ack event, 269 // which meant that the client didn't draw. 270 select { 271 case frame = <-w.frames: 272 case w.out <- ackEvent: 273 } 274 if w.gpu != nil { 275 if e2.sync { 276 w.gpu.Refresh() 277 } 278 if err := w.gpu.Flush(); err != nil { 279 w.gpu.Release() 280 w.gpu = nil 281 w.destroy(err) 282 return 283 } 284 } else { 285 ctx, err := newContext(w.driver) 286 if err != nil { 287 w.destroy(err) 288 return 289 } 290 w.gpu, err = gpu.NewGPU(ctx) 291 if err != nil { 292 w.destroy(err) 293 return 294 } 295 } 296 w.draw(e2.Size, frame) 297 if e2.sync { 298 if err := w.gpu.Flush(); err != nil { 299 w.gpu.Release() 300 w.gpu = nil 301 w.destroy(err) 302 return 303 } 304 } 305 case *CommandEvent: 306 w.out <- e 307 w.waitAck() 308 case driverEvent: 309 w.driver = e2.driver 310 case DestroyEvent: 311 w.out <- e2 312 w.ack <- struct{}{} 313 return 314 case ui.Event: 315 if w.queue.q.Add(e2) { 316 w.setNextFrame(time.Time{}) 317 w.updateAnimation() 318 } 319 w.out <- e 320 } 321 w.ack <- struct{}{} 322 } 323 } 324 } 325 326 func (q *Queue) Events(k ui.Key) []ui.Event { 327 return q.q.Events(k) 328 } 329 330 // WithTitle returns an option that sets the window title. 331 func WithTitle(t string) WindowOption { 332 return WindowOption{ 333 apply: func(opts *windowOptions) { 334 opts.Title = t 335 }, 336 } 337 } 338 339 // WithWidth returns an option that sets the window width. 340 func WithWidth(w ui.Value) WindowOption { 341 if w.V <= 0 { 342 panic("width must be larger than or equal to 0") 343 } 344 return WindowOption{ 345 apply: func(opts *windowOptions) { 346 opts.Width = w 347 }, 348 } 349 } 350 351 // WithHeight returns an option that sets the window height. 352 func WithHeight(h ui.Value) WindowOption { 353 if h.V <= 0 { 354 panic("height must be larger than or equal to 0") 355 } 356 return WindowOption{ 357 apply: func(opts *windowOptions) { 358 opts.Height = h 359 }, 360 } 361 } 362 363 func (driverEvent) ImplementsEvent() {}