ui/app: introduce DestroyEvent for ending the event loop

Replace the StageDead stage with DestroyEvent dedicated to ending
the event loop and, for premature window closes, the error.

Drop the error return from NewWindow; any errors in window creation
will appear as an immediate DestroyEvent with its Err field set.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-07-13 11:41:12 +02:00
parent 8e307b40a6
commit 59e92e8233
6 changed files with 51 additions and 30 deletions
+16 -8
View File
@@ -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() {}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+4 -2
View File
@@ -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.
+4 -2
View File
@@ -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.
+25 -16
View File
@@ -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)
}
}