ui/app: create windows directly

Replace CreateWindow with NewWindow that immediately creates a Window
ready to use.

Drop the Windows channel of windows created by the system. For iOS
and Android where the system creates the windows, let them rendezvous
with the window created in the first NewWindow call.

Android is further changed so that destroying and re-creating the
Java Activity simply reconnects with the original Window.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-07-12 19:21:02 +02:00
parent 46cee54dd6
commit a3b9c7818f
8 changed files with 116 additions and 79 deletions
+38 -24
View File
@@ -3,6 +3,7 @@
package app package app
import ( import (
"errors"
"image" "image"
"math" "math"
"os" "os"
@@ -47,6 +48,17 @@ type CommandEvent struct {
type Stage uint8 type Stage uint8
type CommandType uint8 type CommandType uint8
type windowRendezvous struct {
in chan windowAndOptions
out chan windowAndOptions
errs chan error
}
type windowAndOptions struct {
window *Window
opts *WindowOptions
}
const ( const (
StageDead Stage = iota StageDead Stage = iota
StagePaused StagePaused
@@ -79,30 +91,6 @@ const (
// Set with the go linker flag -X. // Set with the go linker flag -X.
var extraArgs string 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 { func (l Stage) String() string {
switch l { switch l {
case StageDead: case StageDead:
@@ -164,3 +152,29 @@ func (c *Config) Px(v ui.Value) int {
} }
return int(math.Round(float64(r))) 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
}
+11 -8
View File
@@ -58,6 +58,8 @@ var theJVM *C.JavaVM
var views = make(map[C.jlong]*window) var views = make(map[C.jlong]*window)
var mainWindow = newWindowRendezvous()
func jniGetMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID { func jniGetMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
m := C.CString(method) m := C.CString(method)
defer C.free(unsafe.Pointer(m)) 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"), mpostFrameCallback: jniGetMethodID(env, class, "postFrameCallback", "()V"),
mpostFrameCallbackOnMainThread: jniGetMethodID(env, class, "postFrameCallbackOnMainThread", "()V"), mpostFrameCallbackOnMainThread: jniGetMethodID(env, class, "postFrameCallbackOnMainThread", "()V"),
} }
ow := newWindow(w) wopts := <-mainWindow.out
w.Window = ow w.Window = wopts.window
w.Window.setDriver(w)
handle := C.jlong(view) handle := C.jlong(view)
views[handle] = w views[handle] = w
w.loadConfig(env, class) w.loadConfig(env, class)
windows <- ow
w.setStage(StagePaused) w.setStage(StagePaused)
return handle return handle
} }
@@ -104,8 +106,8 @@ func onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
//export onDestroyView //export onDestroyView
func onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) { func onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
w := views[handle] w := views[handle]
w.setDriver(nil)
delete(views, handle) delete(views, handle)
w.setStage(StageDead)
C.gio_jni_DeleteGlobalRef(env, w.view) C.gio_jni_DeleteGlobalRef(env, w.view)
w.view = 0 w.view = 0
} }
@@ -408,10 +410,11 @@ func (w *window) setTextInput(s key.TextInputState) {
} }
func Main() { func Main() {
// Android runs in c-shared mode where is never reached. // Android runs in c-shared mode where main is never reached.
panic("unreachable") panic("call to Main from outside main")
} }
func createWindow(opts *WindowOptions) error { func createWindow(window *Window, opts *WindowOptions) error {
return errors.New("createWindow not supported") mainWindow.in <- windowAndOptions{window, opts}
return <-mainWindow.errs
} }
+9 -6
View File
@@ -38,6 +38,8 @@ type window struct {
pointerMap []C.CFTypeRef pointerMap []C.CFTypeRef
} }
var mainWindow = newWindowRendezvous()
var layerFactory func() uintptr var layerFactory func() uintptr
var views = make(map[C.CFTypeRef]*window) var views = make(map[C.CFTypeRef]*window)
@@ -52,13 +54,13 @@ func onCreate(view C.CFTypeRef) {
w := &window{ w := &window{
view: view, view: view,
} }
ow := newWindow(w) wopts := <-mainWindow.out
w.w = ow w.w = wopts.window
w.w.setDriver(w)
w.visible.Store(false) w.visible.Store(false)
w.layer = C.CFTypeRef(layerFactory()) w.layer = C.CFTypeRef(layerFactory())
C.gio_addLayerToView(view, w.layer) C.gio_addLayerToView(view, w.layer)
views[view] = w views[view] = w
windows <- ow
w.w.event(StageEvent{StagePaused}) w.w.event(StageEvent{StagePaused})
} }
@@ -245,11 +247,12 @@ func (w *window) setTextInput(s key.TextInputState) {
} }
} }
func createWindow(opts *WindowOptions) error { func createWindow(win *Window, opts *WindowOptions) error {
panic("unsupported") mainWindow.in <- windowAndOptions{win, opts}
return <-mainWindow.errs
} }
func Main() { func Main() {
// iOS runs in c-archive mode, so this is never reached. // iOS runs in c-archive mode, so this is never reached.
panic("unreachable") panic("call to Main from outside main")
} }
+3 -3
View File
@@ -31,7 +31,7 @@ type window struct {
var mainDone = make(chan struct{}) var mainDone = make(chan struct{})
func createWindow(opts *WindowOptions) error { func createWindow(win *Window, opts *WindowOptions) error {
doc := js.Global().Get("document") doc := js.Global().Get("document")
parent := doc.Call("getElementById", "giowindow") parent := doc.Call("getElementById", "giowindow")
if parent == js.Null() { if parent == js.Null() {
@@ -52,9 +52,9 @@ func createWindow(opts *WindowOptions) error {
return nil return nil
}) })
w.addEventListeners() w.addEventListeners()
w.w = newWindow(w) w.w = win
go func() { go func() {
windows <- w.w w.w.setDriver(w)
w.focus() w.focus()
w.w.event(StageEvent{StageRunning}) w.w.event(StageEvent{StageRunning})
w.draw(true) w.draw(true)
+9 -21
View File
@@ -15,7 +15,6 @@ import (
"errors" "errors"
"image" "image"
"runtime" "runtime"
"sync"
"time" "time"
"unsafe" "unsafe"
@@ -35,12 +34,7 @@ type window struct {
stage Stage stage Stage
} }
// Only support one main window for now. var mainWindow = newWindowRendezvous()
var singleWindow struct {
mu sync.Mutex
hasOpts bool
opts *WindowOptions
}
var viewFactory func() C.CFTypeRef var viewFactory func() C.CFTypeRef
@@ -170,7 +164,6 @@ func gio_onTerminate(view C.CFTypeRef) {
w := views[view] w := views[view]
delete(views, view) delete(views, view)
w.setStage(StageDead) w.setStage(StageDead)
close(windows)
} }
//export gio_onHide //export gio_onHide
@@ -190,31 +183,26 @@ func gio_onCreate(view C.CFTypeRef) {
w := &window{ w := &window{
view: view, view: view,
} }
ow := newWindow(w) wopts := <-mainWindow.out
w.w = ow w.w = wopts.window
w.w.setDriver(w)
views[view] = w views[view] = w
windows <- ow
} }
func createWindow(opts *WindowOptions) error { func createWindow(win *Window, opts *WindowOptions) error {
singleWindow.mu.Lock() mainWindow.in <- windowAndOptions{win, opts}
defer singleWindow.mu.Unlock() return <-mainWindow.errs
if singleWindow.hasOpts {
panic("only one window supported")
}
singleWindow.opts = opts
singleWindow.hasOpts = true
return nil
} }
func Main() { func Main() {
wopts := <-mainWindow.out
view := viewFactory() view := viewFactory()
if view == 0 { if view == 0 {
// TODO: return this error from CreateWindow. // TODO: return this error from CreateWindow.
panic(errors.New("CreateWindow: failed to create view")) panic(errors.New("CreateWindow: failed to create view"))
} }
cfg := getConfig() cfg := getConfig()
opts := singleWindow.opts opts := wopts.opts
w := cfg.Px(opts.Width) w := cfg.Px(opts.Width)
h := cfg.Px(opts.Height) h := cfg.Px(opts.Height)
title := C.CString(opts.Title) title := C.CString(opts.Title)
+4 -6
View File
@@ -155,11 +155,11 @@ func Main() {
<-mainDone <-mainDone
} }
func createWindow(opts *WindowOptions) error { func createWindow(window *Window, opts *WindowOptions) error {
connMu.Lock() connMu.Lock()
defer connMu.Unlock() defer connMu.Unlock()
if len(winMap) > 0 { if len(winMap) > 0 {
panic("multiple windows are not supported") return errors.New("multiple windows are not supported")
} }
if err := waylandConnect(); err != nil { if err := waylandConnect(); err != nil {
return err return err
@@ -169,13 +169,13 @@ func createWindow(opts *WindowOptions) error {
conn.destroy() conn.destroy()
return err return err
} }
w.w = window
go func() { go func() {
windows <- w.w w.w.setDriver(w)
w.setStage(StageRunning) w.setStage(StageRunning)
w.loop() w.loop()
w.destroy() w.destroy()
conn.destroy() conn.destroy()
close(windows)
close(mainDone) close(mainDone)
}() }()
return nil return nil
@@ -250,8 +250,6 @@ func createNativeWindow(opts *WindowOptions) (*window, error) {
} }
w.updateOpaqueRegion() w.updateOpaqueRegion()
C.wl_surface_commit(w.surf) C.wl_surface_commit(w.surf)
ow := newWindow(w)
w.w = ow
winMap[w.topLvl] = w winMap[w.topLvl] = w
winMap[w.surf] = w winMap[w.surf] = w
winMap[w.wmSurf] = w winMap[w.wmSurf] = w
+5 -5
View File
@@ -3,6 +3,7 @@
package app package app
import ( import (
"errors"
"fmt" "fmt"
"image" "image"
"runtime" "runtime"
@@ -159,11 +160,11 @@ func Main() {
<-mainDone <-mainDone
} }
func createWindow(opts *WindowOptions) error { func createWindow(window *Window, opts *WindowOptions) error {
onceMu.Lock() onceMu.Lock()
defer onceMu.Unlock() defer onceMu.Unlock()
if len(winMap) > 0 { if len(winMap) > 0 {
panic("multiple windows are not supported") return errors.New("multiple windows are not supported")
} }
cerr := make(chan error) cerr := make(chan error)
go func() { go func() {
@@ -176,14 +177,14 @@ func createWindow(opts *WindowOptions) error {
} }
defer w.destroy() defer w.destroy()
cerr <- nil cerr <- nil
windows <- w.w w.w = window
w.w.setDriver(w)
showWindow(w.hwnd, _SW_SHOWDEFAULT) showWindow(w.hwnd, _SW_SHOWDEFAULT)
setForegroundWindow(w.hwnd) setForegroundWindow(w.hwnd)
setFocus(w.hwnd) setFocus(w.hwnd)
if err := w.loop(); err != nil { if err := w.loop(); err != nil {
panic(err) panic(err)
} }
close(windows)
close(mainDone) close(mainDone)
}() }()
return <-cerr return <-cerr
@@ -248,7 +249,6 @@ func createNativeWindow(opts *WindowOptions) (*window, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
w.w = newWindow(w)
return w, nil return w, nil
} }
+37 -6
View File
@@ -13,8 +13,8 @@ import (
"gioui.org/ui/app/internal/gpu" "gioui.org/ui/app/internal/gpu"
iinput "gioui.org/ui/app/internal/input" iinput "gioui.org/ui/app/internal/input"
"gioui.org/ui/input" "gioui.org/ui/input"
"gioui.org/ui/system"
"gioui.org/ui/key" "gioui.org/ui/key"
"gioui.org/ui/system"
) )
type WindowOptions struct { type WindowOptions struct {
@@ -47,6 +47,12 @@ type Window struct {
router iinput.Router 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 // driver is the interface for the platform implementation
// of a Window. // of a Window.
var _ interface { var _ interface {
@@ -59,13 +65,31 @@ var _ interface {
var ackEvent Event 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{ w := &Window{
driver: nw,
events: make(chan Event), events: make(chan Event),
stage: StagePaused, stage: StagePaused,
} }
return w if err := createWindow(w, opts); err != nil {
return nil, err
}
return w, nil
} }
func (w *Window) Events() <-chan Event { func (w *Window) Events() <-chan Event {
@@ -103,8 +127,9 @@ func (w *Window) Draw(root *ui.Ops) {
w.syncGPU = false w.syncGPU = false
alive := w.isAlive() alive := w.isAlive()
size := w.size size := w.size
driver := w.driver
w.mu.Unlock() w.mu.Unlock()
if !alive || stage < StageRunning { if !alive || stage < StageRunning || driver == nil {
return return
} }
if w.gpu != nil { if w.gpu != nil {
@@ -117,7 +142,7 @@ func (w *Window) Draw(root *ui.Ops) {
} }
} }
if w.gpu == nil { if w.gpu == nil {
ctx, err := newContext(w.driver) ctx, err := newContext(driver)
if err != nil { if err != nil {
w.err = err w.err = err
return return
@@ -205,6 +230,12 @@ func (w *Window) contextDriver() interface{} {
return w.driver 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) { func (w *Window) event(e Event) {
w.eventLock.Lock() w.eventLock.Lock()
defer w.eventLock.Unlock() defer w.eventLock.Unlock()