github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/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 "github.com/gop9/olt/gio/app/internal/input" 12 "github.com/gop9/olt/gio/app/internal/window" 13 "github.com/gop9/olt/gio/io/event" 14 "github.com/gop9/olt/gio/io/profile" 15 "github.com/gop9/olt/gio/io/system" 16 "github.com/gop9/olt/gio/op" 17 "github.com/gop9/olt/gio/unit" 18 19 _ "github.com/gop9/olt/gio/app/internal/log" 20 ) 21 22 // WindowOption configures a Window. 23 type Option func(opts *window.Options) 24 25 // Window represents an operating system window. 26 type Window struct { 27 driver window.Driver 28 loop *renderLoop 29 30 // driverFuncs is a channel of functions to run when 31 // the Window has a valid driver. 32 driverFuncs chan func() 33 34 out chan event.Event 35 in chan event.Event 36 ack chan struct{} 37 invalidates chan struct{} 38 frames chan *op.Ops 39 frameAck chan struct{} 40 41 stage system.Stage 42 animating bool 43 hasNextFrame bool 44 nextFrame time.Time 45 delayedDraw *time.Timer 46 47 queue Queue 48 49 callbacks callbacks 50 } 51 52 type callbacks struct { 53 w *Window 54 } 55 56 // Queue is an event.Queue implementation that distributes system events 57 // to the input handlers declared in the most recent frame. 58 type Queue struct { 59 q input.Router 60 } 61 62 // driverEvent is sent when a new native driver 63 // is available for the Window. 64 type driverEvent struct { 65 driver window.Driver 66 } 67 68 // Pre-allocate the ack event to avoid garbage. 69 var ackEvent event.Event 70 71 // NewWindow creates a new window for a set of window 72 // options. The options are hints; the platform is free to 73 // ignore or adjust them. 74 // 75 // If opts are nil, a set of sensible defaults are used. 76 // 77 // If the current program is running on iOS and Android, 78 // NewWindow returns the window previously created by the 79 // platform. 80 // 81 // BUG: Calling NewWindow more than once is not yet supported. 82 func NewWindow(options ...Option) *Window { 83 opts := &window.Options{ 84 Width: unit.Dp(800), 85 Height: unit.Dp(600), 86 Title: "Gio", 87 } 88 89 for _, o := range options { 90 o(opts) 91 } 92 93 w := &Window{ 94 in: make(chan event.Event), 95 out: make(chan event.Event), 96 ack: make(chan struct{}), 97 invalidates: make(chan struct{}, 1), 98 frames: make(chan *op.Ops), 99 frameAck: make(chan struct{}), 100 driverFuncs: make(chan func()), 101 } 102 w.callbacks.w = w 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 event.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 frame. 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 *op.Ops) { 123 w.frames <- frame 124 <-w.frameAck 125 } 126 127 func (w *Window) draw(frameStart time.Time, size image.Point, frame *op.Ops) { 128 sync := w.loop.Draw(w.queue.q.Profiling(), size, frame) 129 w.queue.q.Frame(frame) 130 switch w.queue.q.TextInputState() { 131 case input.TextInputOpen: 132 w.driver.ShowTextInput(true) 133 case input.TextInputClose: 134 w.driver.ShowTextInput(false) 135 } 136 if w.queue.q.Profiling() { 137 frameDur := time.Since(frameStart) 138 frameDur = frameDur.Truncate(100 * time.Microsecond) 139 q := 100 * time.Microsecond 140 timings := fmt.Sprintf("tot:%7s %s", frameDur.Round(q), w.loop.Summary()) 141 w.queue.q.AddProfile(profile.Event{Timings: timings}) 142 } 143 if t, ok := w.queue.q.WakeupTime(); ok { 144 w.setNextFrame(t) 145 } 146 w.updateAnimation() 147 // Wait for the GPU goroutine to finish processing frame. 148 <-sync 149 } 150 151 // Invalidate the window such that a FrameEvent will be generated 152 // immediately. If the window is inactive, the event is sent when the 153 // window becomes active. 154 // Invalidate is safe for concurrent use. 155 func (w *Window) Invalidate() { 156 select { 157 case w.invalidates <- struct{}{}: 158 default: 159 } 160 } 161 162 func (w *Window) updateAnimation() { 163 animate := false 164 if w.delayedDraw != nil { 165 w.delayedDraw.Stop() 166 w.delayedDraw = nil 167 } 168 if w.stage >= system.StageRunning && w.hasNextFrame { 169 if dt := time.Until(w.nextFrame); dt <= 0 { 170 animate = true 171 } else { 172 w.delayedDraw = time.NewTimer(dt) 173 } 174 } 175 if animate != w.animating { 176 w.animating = animate 177 w.driver.SetAnimating(animate) 178 } 179 } 180 181 func (w *Window) setNextFrame(at time.Time) { 182 if !w.hasNextFrame || at.Before(w.nextFrame) { 183 w.hasNextFrame = true 184 w.nextFrame = at 185 } 186 } 187 188 func (c *callbacks) SetDriver(d window.Driver) { 189 c.Event(driverEvent{d}) 190 } 191 192 func (c *callbacks) Event(e event.Event) { 193 c.w.in <- e 194 <-c.w.ack 195 } 196 197 func (w *Window) waitAck() { 198 // Send a dummy event; when it gets through we 199 // know the application has processed the previous event. 200 w.out <- ackEvent 201 } 202 203 // Prematurely destroy the window and wait for the native window 204 // destroy event. 205 func (w *Window) destroy(err error) { 206 w.destroyGPU() 207 // Ack the current event. 208 w.ack <- struct{}{} 209 w.out <- system.DestroyEvent{Err: err} 210 for e := range w.in { 211 w.ack <- struct{}{} 212 if _, ok := e.(system.DestroyEvent); ok { 213 return 214 } 215 } 216 } 217 218 func (w *Window) destroyGPU() { 219 if w.loop != nil { 220 w.loop.Release() 221 w.loop = nil 222 } 223 } 224 225 func (w *Window) run(opts *window.Options) { 226 defer close(w.in) 227 defer close(w.out) 228 if err := window.NewWindow(&w.callbacks, opts); err != nil { 229 w.out <- system.DestroyEvent{Err: err} 230 return 231 } 232 for { 233 var driverFuncs chan func() = nil 234 if w.driver != nil { 235 driverFuncs = w.driverFuncs 236 } 237 var timer <-chan time.Time 238 if w.delayedDraw != nil { 239 timer = w.delayedDraw.C 240 } 241 select { 242 case <-timer: 243 w.setNextFrame(time.Time{}) 244 w.updateAnimation() 245 case <-w.invalidates: 246 w.setNextFrame(time.Time{}) 247 w.updateAnimation() 248 case f := <-driverFuncs: 249 f() 250 case e := <-w.in: 251 switch e2 := e.(type) { 252 case system.StageEvent: 253 if w.loop != nil { 254 if e2.Stage < system.StageRunning { 255 w.loop.Release() 256 w.loop = nil 257 } else { 258 w.loop.Refresh() 259 } 260 } 261 w.stage = e2.Stage 262 w.updateAnimation() 263 w.out <- e 264 w.waitAck() 265 case window.FrameEvent: 266 if e2.Size == (image.Point{}) { 267 panic(errors.New("internal error: zero-sized Draw")) 268 } 269 if w.stage < system.StageRunning { 270 // No drawing if not visible. 271 break 272 } 273 frameStart := time.Now() 274 w.hasNextFrame = false 275 e2.Frame = w.update 276 w.out <- e2.FrameEvent 277 var err error 278 if w.loop != nil { 279 if e2.Sync { 280 w.loop.Refresh() 281 } 282 if err = w.loop.Flush(); err != nil { 283 w.loop.Release() 284 w.loop = nil 285 } 286 } else { 287 var ctx window.Context 288 ctx, err = w.driver.NewContext() 289 if err == nil { 290 w.loop, err = newLoop(ctx) 291 if err != nil { 292 ctx.Release() 293 } 294 } 295 } 296 var frame *op.Ops 297 // Wait for either a frame or an ack event to go 298 // through, which means that the client didn't give us 299 // a frame. 300 gotFrame := false 301 select { 302 case frame = <-w.frames: 303 gotFrame = true 304 case w.out <- ackEvent: 305 } 306 if err != nil { 307 if gotFrame { 308 w.frameAck <- struct{}{} 309 } 310 w.destroy(err) 311 return 312 } 313 w.draw(frameStart, e2.Size, frame) 314 if gotFrame { 315 // We're done with frame, let the client continue. 316 w.frameAck <- struct{}{} 317 } 318 if e2.Sync { 319 if err := w.loop.Flush(); err != nil { 320 w.loop.Release() 321 w.loop = nil 322 w.destroy(err) 323 return 324 } 325 } 326 case *system.CommandEvent: 327 w.out <- e 328 w.waitAck() 329 case driverEvent: 330 w.driver = e2.driver 331 case system.DestroyEvent: 332 w.destroyGPU() 333 w.out <- e2 334 w.ack <- struct{}{} 335 return 336 case event.Event: 337 if w.queue.q.Add(e2) { 338 w.setNextFrame(time.Time{}) 339 w.updateAnimation() 340 } 341 w.out <- e 342 } 343 w.ack <- struct{}{} 344 } 345 } 346 } 347 348 func (q *Queue) Events(k event.Key) []event.Event { 349 return q.q.Events(k) 350 } 351 352 // Title sets the title of the window. 353 func Title(t string) Option { 354 return func(opts *window.Options) { 355 opts.Title = t 356 } 357 } 358 359 // Size sets the size of the window. 360 func Size(w, h unit.Value) Option { 361 if w.V <= 0 { 362 panic("width must be larger than or equal to 0") 363 } 364 if h.V <= 0 { 365 panic("height must be larger than or equal to 0") 366 } 367 return func(opts *window.Options) { 368 opts.Width = w 369 opts.Height = h 370 } 371 } 372 373 func (driverEvent) ImplementsEvent() {}