diff --git a/ui/app/app.go b/ui/app/app.go index 456dfeac..499e0d64 100644 --- a/ui/app/app.go +++ b/ui/app/app.go @@ -17,6 +17,8 @@ type Event interface { ImplementsEvent() } +// DrawEvent is sent when a Window's Draw +// method must be called. type DrawEvent struct { Config Config Size image.Point @@ -27,6 +29,14 @@ type DrawEvent struct { sync bool } +// DestroyEvent is the last event sent through +// a window event channel. +type DestroyEvent struct { + // Err is nil for normal window closures. If a + // window is prematurely closed, Err is the cause. + Err error +} + // Insets is the space taken up by // system decoration such as translucent // system bars and software keyboards. @@ -60,8 +70,7 @@ type windowAndOptions struct { } const ( - StageDead Stage = iota - StagePaused + StagePaused Stage = iota StageRunning ) @@ -93,8 +102,6 @@ var extraArgs string func (l Stage) String() string { switch l { - case StageDead: - return "StageDead" case StagePaused: return "StagePaused" case StageRunning: @@ -104,10 +111,6 @@ func (l Stage) String() string { } } -func (_ DrawEvent) ImplementsEvent() {} -func (_ StageEvent) ImplementsEvent() {} -func (_ *CommandEvent) ImplementsEvent() {} - func init() { args := strings.Split(extraArgs, "|") os.Args = append(os.Args, args...) @@ -178,3 +181,8 @@ func newWindowRendezvous() *windowRendezvous { }() return wr } + +func (_ DrawEvent) ImplementsEvent() {} +func (_ StageEvent) ImplementsEvent() {} +func (_ *CommandEvent) ImplementsEvent() {} +func (_ DestroyEvent) ImplementsEvent() {} diff --git a/ui/app/os_ios.go b/ui/app/os_ios.go index 2b86eccc..9ef63132 100644 --- a/ui/app/os_ios.go +++ b/ui/app/os_ios.go @@ -111,7 +111,7 @@ func onStop(view C.CFTypeRef) { func onDestroy(view C.CFTypeRef) { w := views[view] delete(views, view) - w.w.event(StageEvent{StageDead}) + w.w.event(DestroyEvent{}) C.gio_removeLayer(w.layer) C.CFRelease(w.layer) w.layer = 0 diff --git a/ui/app/os_macos.go b/ui/app/os_macos.go index f310197f..c9345c64 100644 --- a/ui/app/os_macos.go +++ b/ui/app/os_macos.go @@ -163,7 +163,7 @@ func getConfig() Config { func gio_onTerminate(view C.CFTypeRef) { w := views[view] delete(views, view) - w.setStage(StageDead) + w.w.event(DestroyEvent{}) } //export gio_onHide diff --git a/ui/app/os_wayland.go b/ui/app/os_wayland.go index 2a2f3696..fd9043e3 100644 --- a/ui/app/os_wayland.go +++ b/ui/app/os_wayland.go @@ -113,6 +113,7 @@ type window struct { lastTouch f32.Point stage Stage + dead bool lastFrameCallback *C.struct_wl_callback mu sync.Mutex @@ -308,7 +309,8 @@ func gio_onXdgSurfaceConfigure(data unsafe.Pointer, wmSurf *C.struct_xdg_surface //export gio_onToplevelClose func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) { w := winMap[topLvl] - w.setStage(StageDead) + w.dead = true + w.w.event(DestroyEvent{}) } //export gio_onToplevelConfigure @@ -769,7 +771,7 @@ loop: if ret := C.wl_display_flush(conn.disp); ret < 0 { break } - if w.stage == StageDead { + if w.dead { break } // Clear poll events. diff --git a/ui/app/os_windows.go b/ui/app/os_windows.go index a673b6bf..3527ff6e 100644 --- a/ui/app/os_windows.go +++ b/ui/app/os_windows.go @@ -61,6 +61,7 @@ type window struct { width int height int stage Stage + dead bool mu sync.Mutex animating bool @@ -319,7 +320,8 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr w.scrollEvent(wParam, lParam) case _WM_DESTROY: delete(winMap, hwnd) - w.setStage(StageDead) + w.dead = true + w.w.event(DestroyEvent{}) case _WM_REDRAW: w.mu.Lock() anim := w.animating @@ -368,7 +370,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr) { // Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/ func (w *window) loop() error { loop: - for w.stage > StageDead { + for !w.dead { var msg msg // Since posted messages are always returned before system messages, // but we want our WM_REDRAW to always come last, just like WM_PAINT. diff --git a/ui/app/window.go b/ui/app/window.go index 902af0d5..06fee21e 100644 --- a/ui/app/window.go +++ b/ui/app/window.go @@ -29,7 +29,6 @@ type Window struct { drawStart time.Time gpu *gpu.GPU inputState key.TextInputState - err error events chan Event @@ -70,7 +69,7 @@ var ackEvent Event // ignore or adjust them. // If the current program is running on iOS and Android, // NewWindow returns the window previously by the platform. -func NewWindow(opts *WindowOptions) (*Window, error) { +func NewWindow(opts *WindowOptions) *Window { if opts == nil { opts = &WindowOptions{ Width: ui.Dp(800), @@ -87,9 +86,11 @@ func NewWindow(opts *WindowOptions) (*Window, error) { stage: StagePaused, } if err := createWindow(w, opts); err != nil { - return nil, err + // For simplicity, NewWindow always succeeds. Send + // an immediate DestroyEvent instead of returning the error. + w.destroy(err) } - return w, nil + return w } func (w *Window) Events() <-chan Event { @@ -107,10 +108,6 @@ func (w *Window) setTextInput(s key.TextInputState) { w.inputState = s } -func (w *Window) Err() error { - return w.err -} - func (w *Window) Queue() input.Queue { return &w.router } @@ -144,12 +141,12 @@ func (w *Window) Draw(root *ui.Ops) { if w.gpu == nil { ctx, err := newContext(driver) if err != nil { - w.err = err + w.destroy(err) return } w.gpu, err = gpu.NewGPU(ctx) if err != nil { - w.err = err + w.destroy(err) return } } @@ -190,6 +187,9 @@ func (w *Window) updateAnimation() { w.delayedDraw.Stop() w.delayedDraw = nil } + if !w.isAlive() { + return + } if w.stage >= StageRunning && w.hasNextFrame { if dt := time.Until(w.nextFrame); dt <= 0 { animate = true @@ -223,7 +223,7 @@ func (w *Window) Stage() Stage { } func (w *Window) isAlive() bool { - return w.stage > StageDead && w.err == nil + return w.driver != nil } func (w *Window) contextDriver() interface{} { @@ -236,10 +236,18 @@ func (w *Window) setDriver(d *window) { w.driver = d } +func (w *Window) destroy(err error) { + w.setDriver(nil) + go func() { + w.event(DestroyEvent{err}) + }() +} + func (w *Window) event(e Event) { w.eventLock.Lock() defer w.eventLock.Unlock() w.mu.Lock() + died := false needAck := false switch e := e.(type) { case input.Event: @@ -248,12 +256,13 @@ func (w *Window) event(e Event) { } case *CommandEvent: needAck = true + case DestroyEvent: + w.driver = nil + died = true case StageEvent: w.stage = e.Stage - if w.stage > StageDead { - needAck = true - w.syncGPU = true - } + needAck = true + w.syncGPU = true case DrawEvent: if e.Size == (image.Point{}) { panic(errors.New("internal error: zero-sized Draw")) @@ -290,7 +299,7 @@ func (w *Window) event(e Event) { w.gpu.Refresh() } } - if stage == StageDead { + if died { close(w.events) } }