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
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
}
+11 -8
View File
@@ -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
}
+9 -6
View File
@@ -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")
}
+3 -3
View File
@@ -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)
+9 -21
View File
@@ -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)
+4 -6
View File
@@ -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
+5 -5
View File
@@ -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
}
+37 -6
View File
@@ -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()