diff --git a/ui/app/app.go b/ui/app/app.go index 8fe686c8..456dfeac 100644 --- a/ui/app/app.go +++ b/ui/app/app.go @@ -3,6 +3,7 @@ package app import ( + "errors" "image" "math" "os" @@ -47,6 +48,17 @@ type CommandEvent struct { type Stage uint8 type CommandType uint8 +type windowRendezvous struct { + in chan windowAndOptions + out chan windowAndOptions + errs chan error +} + +type windowAndOptions struct { + window *Window + opts *WindowOptions +} + const ( StageDead Stage = iota StagePaused @@ -79,30 +91,6 @@ const ( // Set with the go linker flag -X. var extraArgs string -var windows = make(chan *Window) - -// CreateWindow creates a new window for a set of window -// options. The options are hints; the platform is free to -// ignore or adjust them. -// CreateWindow is not supported on iOS and Android. -func CreateWindow(opts *WindowOptions) error { - if opts == nil { - opts = &WindowOptions{ - Width: ui.Dp(800), - Height: ui.Dp(600), - Title: "Gio program", - } - } - if opts.Width.V <= 0 || opts.Height.V <= 0 { - panic("window width and height must be larger than 0") - } - return createWindow(opts) -} - -func Windows() <-chan *Window { - return windows -} - func (l Stage) String() string { switch l { case StageDead: @@ -164,3 +152,29 @@ func (c *Config) Px(v ui.Value) int { } return int(math.Round(float64(r))) } + +func newWindowRendezvous() *windowRendezvous { + wr := &windowRendezvous{ + in: make(chan windowAndOptions), + out: make(chan windowAndOptions), + errs: make(chan error), + } + go func() { + var main windowAndOptions + var out chan windowAndOptions + for { + select { + case w := <-wr.in: + var err error + if main.window != nil { + err = errors.New("multiple windows are not supported") + } + wr.errs <- err + main = w + out = wr.out + case out <- main: + } + } + }() + return wr +} diff --git a/ui/app/os_android.go b/ui/app/os_android.go index 70b51f30..0742c965 100644 --- a/ui/app/os_android.go +++ b/ui/app/os_android.go @@ -58,6 +58,8 @@ var theJVM *C.JavaVM var views = make(map[C.jlong]*window) +var mainWindow = newWindowRendezvous() + func jniGetMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID { m := C.CString(method) defer C.free(unsafe.Pointer(m)) @@ -91,12 +93,12 @@ func onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong { mpostFrameCallback: jniGetMethodID(env, class, "postFrameCallback", "()V"), mpostFrameCallbackOnMainThread: jniGetMethodID(env, class, "postFrameCallbackOnMainThread", "()V"), } - ow := newWindow(w) - w.Window = ow + wopts := <-mainWindow.out + w.Window = wopts.window + w.Window.setDriver(w) handle := C.jlong(view) views[handle] = w w.loadConfig(env, class) - windows <- ow w.setStage(StagePaused) return handle } @@ -104,8 +106,8 @@ func onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong { //export onDestroyView func onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) { w := views[handle] + w.setDriver(nil) delete(views, handle) - w.setStage(StageDead) C.gio_jni_DeleteGlobalRef(env, w.view) w.view = 0 } @@ -408,10 +410,11 @@ func (w *window) setTextInput(s key.TextInputState) { } func Main() { - // Android runs in c-shared mode where is never reached. - panic("unreachable") + // Android runs in c-shared mode where main is never reached. + panic("call to Main from outside main") } -func createWindow(opts *WindowOptions) error { - return errors.New("createWindow not supported") +func createWindow(window *Window, opts *WindowOptions) error { + mainWindow.in <- windowAndOptions{window, opts} + return <-mainWindow.errs } diff --git a/ui/app/os_ios.go b/ui/app/os_ios.go index baa42f31..2b86eccc 100644 --- a/ui/app/os_ios.go +++ b/ui/app/os_ios.go @@ -38,6 +38,8 @@ type window struct { pointerMap []C.CFTypeRef } +var mainWindow = newWindowRendezvous() + var layerFactory func() uintptr var views = make(map[C.CFTypeRef]*window) @@ -52,13 +54,13 @@ func onCreate(view C.CFTypeRef) { w := &window{ view: view, } - ow := newWindow(w) - w.w = ow + wopts := <-mainWindow.out + w.w = wopts.window + w.w.setDriver(w) w.visible.Store(false) w.layer = C.CFTypeRef(layerFactory()) C.gio_addLayerToView(view, w.layer) views[view] = w - windows <- ow w.w.event(StageEvent{StagePaused}) } @@ -245,11 +247,12 @@ func (w *window) setTextInput(s key.TextInputState) { } } -func createWindow(opts *WindowOptions) error { - panic("unsupported") +func createWindow(win *Window, opts *WindowOptions) error { + mainWindow.in <- windowAndOptions{win, opts} + return <-mainWindow.errs } func Main() { // iOS runs in c-archive mode, so this is never reached. - panic("unreachable") + panic("call to Main from outside main") } diff --git a/ui/app/os_js.go b/ui/app/os_js.go index 5e3a087e..b622bdfd 100644 --- a/ui/app/os_js.go +++ b/ui/app/os_js.go @@ -31,7 +31,7 @@ type window struct { var mainDone = make(chan struct{}) -func createWindow(opts *WindowOptions) error { +func createWindow(win *Window, opts *WindowOptions) error { doc := js.Global().Get("document") parent := doc.Call("getElementById", "giowindow") if parent == js.Null() { @@ -52,9 +52,9 @@ func createWindow(opts *WindowOptions) error { return nil }) w.addEventListeners() - w.w = newWindow(w) + w.w = win go func() { - windows <- w.w + w.w.setDriver(w) w.focus() w.w.event(StageEvent{StageRunning}) w.draw(true) diff --git a/ui/app/os_macos.go b/ui/app/os_macos.go index 928d273a..f310197f 100644 --- a/ui/app/os_macos.go +++ b/ui/app/os_macos.go @@ -15,7 +15,6 @@ import ( "errors" "image" "runtime" - "sync" "time" "unsafe" @@ -35,12 +34,7 @@ type window struct { stage Stage } -// Only support one main window for now. -var singleWindow struct { - mu sync.Mutex - hasOpts bool - opts *WindowOptions -} +var mainWindow = newWindowRendezvous() var viewFactory func() C.CFTypeRef @@ -170,7 +164,6 @@ func gio_onTerminate(view C.CFTypeRef) { w := views[view] delete(views, view) w.setStage(StageDead) - close(windows) } //export gio_onHide @@ -190,31 +183,26 @@ func gio_onCreate(view C.CFTypeRef) { w := &window{ view: view, } - ow := newWindow(w) - w.w = ow + wopts := <-mainWindow.out + w.w = wopts.window + w.w.setDriver(w) views[view] = w - windows <- ow } -func createWindow(opts *WindowOptions) error { - singleWindow.mu.Lock() - defer singleWindow.mu.Unlock() - if singleWindow.hasOpts { - panic("only one window supported") - } - singleWindow.opts = opts - singleWindow.hasOpts = true - return nil +func createWindow(win *Window, opts *WindowOptions) error { + mainWindow.in <- windowAndOptions{win, opts} + return <-mainWindow.errs } func Main() { + wopts := <-mainWindow.out view := viewFactory() if view == 0 { // TODO: return this error from CreateWindow. panic(errors.New("CreateWindow: failed to create view")) } cfg := getConfig() - opts := singleWindow.opts + opts := wopts.opts w := cfg.Px(opts.Width) h := cfg.Px(opts.Height) title := C.CString(opts.Title) diff --git a/ui/app/os_wayland.go b/ui/app/os_wayland.go index 2ae854a0..2a2f3696 100644 --- a/ui/app/os_wayland.go +++ b/ui/app/os_wayland.go @@ -155,11 +155,11 @@ func Main() { <-mainDone } -func createWindow(opts *WindowOptions) error { +func createWindow(window *Window, opts *WindowOptions) error { connMu.Lock() defer connMu.Unlock() if len(winMap) > 0 { - panic("multiple windows are not supported") + return errors.New("multiple windows are not supported") } if err := waylandConnect(); err != nil { return err @@ -169,13 +169,13 @@ func createWindow(opts *WindowOptions) error { conn.destroy() return err } + w.w = window go func() { - windows <- w.w + w.w.setDriver(w) w.setStage(StageRunning) w.loop() w.destroy() conn.destroy() - close(windows) close(mainDone) }() return nil @@ -250,8 +250,6 @@ func createNativeWindow(opts *WindowOptions) (*window, error) { } w.updateOpaqueRegion() C.wl_surface_commit(w.surf) - ow := newWindow(w) - w.w = ow winMap[w.topLvl] = w winMap[w.surf] = w winMap[w.wmSurf] = w diff --git a/ui/app/os_windows.go b/ui/app/os_windows.go index 152fa7be..a673b6bf 100644 --- a/ui/app/os_windows.go +++ b/ui/app/os_windows.go @@ -3,6 +3,7 @@ package app import ( + "errors" "fmt" "image" "runtime" @@ -159,11 +160,11 @@ func Main() { <-mainDone } -func createWindow(opts *WindowOptions) error { +func createWindow(window *Window, opts *WindowOptions) error { onceMu.Lock() defer onceMu.Unlock() if len(winMap) > 0 { - panic("multiple windows are not supported") + return errors.New("multiple windows are not supported") } cerr := make(chan error) go func() { @@ -176,14 +177,14 @@ func createWindow(opts *WindowOptions) error { } defer w.destroy() cerr <- nil - windows <- w.w + w.w = window + w.w.setDriver(w) showWindow(w.hwnd, _SW_SHOWDEFAULT) setForegroundWindow(w.hwnd) setFocus(w.hwnd) if err := w.loop(); err != nil { panic(err) } - close(windows) close(mainDone) }() return <-cerr @@ -248,7 +249,6 @@ func createNativeWindow(opts *WindowOptions) (*window, error) { if err != nil { return nil, err } - w.w = newWindow(w) return w, nil } diff --git a/ui/app/window.go b/ui/app/window.go index 9e30e563..902af0d5 100644 --- a/ui/app/window.go +++ b/ui/app/window.go @@ -13,8 +13,8 @@ import ( "gioui.org/ui/app/internal/gpu" iinput "gioui.org/ui/app/internal/input" "gioui.org/ui/input" - "gioui.org/ui/system" "gioui.org/ui/key" + "gioui.org/ui/system" ) type WindowOptions struct { @@ -47,6 +47,12 @@ type Window struct { router iinput.Router } +// driverEvent is sent when a new native driver +// is available for the Window. +type driverEvent struct { + driver *window +} + // driver is the interface for the platform implementation // of a Window. var _ interface { @@ -59,13 +65,31 @@ var _ interface { var ackEvent Event -func newWindow(nw *window) *Window { +// NewWindow creates a new window for a set of window +// options. The options are hints; the platform is free to +// 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) { + if opts == nil { + opts = &WindowOptions{ + Width: ui.Dp(800), + Height: ui.Dp(600), + Title: "Gio program", + } + } + if opts.Width.V <= 0 || opts.Height.V <= 0 { + panic("window width and height must be larger than 0") + } + w := &Window{ - driver: nw, events: make(chan Event), stage: StagePaused, } - return w + if err := createWindow(w, opts); err != nil { + return nil, err + } + return w, nil } func (w *Window) Events() <-chan Event { @@ -103,8 +127,9 @@ func (w *Window) Draw(root *ui.Ops) { w.syncGPU = false alive := w.isAlive() size := w.size + driver := w.driver w.mu.Unlock() - if !alive || stage < StageRunning { + if !alive || stage < StageRunning || driver == nil { return } if w.gpu != nil { @@ -117,7 +142,7 @@ func (w *Window) Draw(root *ui.Ops) { } } if w.gpu == nil { - ctx, err := newContext(w.driver) + ctx, err := newContext(driver) if err != nil { w.err = err return @@ -205,6 +230,12 @@ func (w *Window) contextDriver() interface{} { return w.driver } +func (w *Window) setDriver(d *window) { + w.mu.Lock() + defer w.mu.Unlock() + w.driver = d +} + func (w *Window) event(e Event) { w.eventLock.Lock() defer w.eventLock.Unlock()