~eliasnaur/gio

18c2ba8e203c4871d14b5cacdd9397ad967a8ded — Elias Naur 2 months ago 64bcb1c
app: replace Window.Config with ConfigEvent

Unlike Raise, Close and other fire-and-forget methods on Window,
Config calls driverRun because it needs to wait for the result.
However, driverRun isn't guaranteed to block in all contexts.

This change avoids the synchronization dance altogether by removing the
Config method and introducing a ConfigEvent event. The event also makes
it clear when the configuration changes.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
M app/os.go => app/os.go +7 -4
@@ 46,6 46,11 @@ type Config struct {
	CustomRenderer bool
}

// ConfigEvent is sent whenever the configuration of a Window changes.
type ConfigEvent struct {
	Config Config
}

func (c *Config) apply(m unit.Metric, options []Option) {
	for _, o := range options {
		o(m, c)


@@ 135,9 140,6 @@ type driver interface {
	// Configure the window.
	Configure([]Option)

	// Config returns the current configuration.
	Config() Config

	// SetCursor updates the current cursor to name.
	SetCursor(name pointer.CursorName)



@@ 187,4 189,5 @@ func newWindowRendezvous() *windowRendezvous {
	return wr
}

func (_ wakeupEvent) ImplementsEvent() {}
func (wakeupEvent) ImplementsEvent() {}
func (ConfigEvent) ImplementsEvent() {}

M app/os_android.go => app/os_android.go +11 -11
@@ 507,19 507,20 @@ func (w *window) SetAnimating(anim bool) {
}

func (w *window) draw(sync bool) {
	width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win)
	if width == 0 || height == 0 {
	size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win)))
	if size != w.config.Size {
		w.config.Size = size
		w.callbacks.Event(ConfigEvent{Config: w.config})
	}
	if size.X == 0 || size.Y == 0 {
		return
	}
	const inchPrDp = 1.0 / 160
	ppdp := float32(w.dpi) * inchPrDp
	w.callbacks.Event(frameEvent{
		FrameEvent: system.FrameEvent{
			Now: time.Now(),
			Size: image.Point{
				X: int(width),
				Y: int(height),
			},
			Now:    time.Now(),
			Size:   w.config.Size,
			Insets: w.insets,
			Metric: unit.Metric{
				PxPerDp: ppdp,


@@ 815,13 816,12 @@ func (w *window) Configure(options []Option) {
				w.config.Mode = Windowed
			}
		}
		if w.config != prev {
			w.callbacks.Event(ConfigEvent{Config: w.config})
		}
	})
}

func (w *window) Config() Config {
	return w.config
}

func (w *window) Raise() {}

func (w *window) SetCursor(name pointer.CursorName) {

M app/os_ios.go => app/os_ios.go +0 -5
@@ 95,7 95,6 @@ type window struct {

	visible bool
	cursor  pointer.CursorName
	config  Config

	pointerMap []C.CFTypeRef
}


@@ 271,10 270,6 @@ func (w *window) WriteClipboard(s string) {

func (w *window) Configure([]Option) {}

func (w *window) Config() Config {
	return w.config
}

func (w *window) Raise() {}

func (w *window) SetAnimating(anim bool) {

M app/os_js.go => app/os_js.go +18 -17
@@ 94,12 94,12 @@ func newWindow(win *callbacks, options []Option) error {
	})
	w.addEventListeners()
	w.addHistory()
	w.Configure(options)
	w.w = win

	go func() {
		defer w.cleanup()
		w.w.SetDriver(w)
		w.Configure(options)
		w.blur()
		w.w.Event(system.StageEvent{Stage: system.StageRunning})
		w.resize()


@@ 528,10 528,9 @@ func (w *window) Configure(options []Option) {
		w.config.Orientation = cnf.Orientation
		w.orientation(cnf.Orientation)
	}
}

func (w *window) Config() Config {
	return w.config
	if w.config != prev {
		w.w.Event(ConfigEvent{Config: w.config})
	}
}

func (w *window) Raise() {}


@@ 571,8 570,14 @@ func (w *window) resize() {
	w.scale = float32(w.window.Get("devicePixelRatio").Float())

	rect := w.cnv.Call("getBoundingClientRect")
	w.config.Size.X = int(rect.Get("width").Float()) * int(w.scale)
	w.config.Size.Y = int(rect.Get("height").Float()) * int(w.scale)
	size := image.Point{
		X: int(float32(rect.Get("width").Float()) * w.scale),
		Y: int(float32(rect.Get("height").Float()) * w.scale),
	}
	if size != w.config.Size {
		w.config.Size = size
		w.w.Event(ConfigEvent{Config: w.config})
	}

	if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
		w.inset.X = float32(w.config.Size.X) - float32(vx.Float())*w.scale


@@ 588,18 593,14 @@ func (w *window) resize() {
}

func (w *window) draw(sync bool) {
	width, height, insets, metric := w.getConfig()
	if metric == (unit.Metric{}) || width == 0 || height == 0 {
	size, insets, metric := w.getConfig()
	if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
		return
	}

	w.w.Event(frameEvent{
		FrameEvent: system.FrameEvent{
			Now: time.Now(),
			Size: image.Point{
				X: width,
				Y: height,
			},
			Now:    time.Now(),
			Size:   size,
			Insets: insets,
			Metric: metric,
		},


@@ 607,8 608,8 @@ func (w *window) draw(sync bool) {
	})
}

func (w *window) getConfig() (int, int, system.Insets, unit.Metric) {
	return w.config.Size.X, w.config.Size.Y, system.Insets{
func (w *window) getConfig() (image.Point, system.Insets, unit.Metric) {
	return image.Pt(w.config.Size.X, w.config.Size.Y), system.Insets{
			Bottom: unit.Px(w.inset.Y),
			Right:  unit.Px(w.inset.X),
		}, unit.Metric{

M app/os_macos.go => app/os_macos.go +14 -12
@@ 252,10 252,9 @@ func (w *window) Configure(options []Option) {
			C.toggleFullScreen(w.window)
		}
	}
}

func (w *window) Config() Config {
	return w.config
	if w.config != prev {
		w.w.Event(ConfigEvent{Config: w.config})
	}
}

func (w *window) SetCursor(name pointer.CursorName) {


@@ 389,20 388,23 @@ func gio_onChangeScreen(view C.CFTypeRef, did uint64) {
func (w *window) draw() {
	w.scale = float32(C.getViewBackingScale(w.view))
	wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
	if wf == 0 || hf == 0 {
	sz := image.Point{
		X: int(wf*w.scale + .5),
		Y: int(hf*w.scale + .5),
	}
	if sz != w.config.Size {
		w.config.Size = sz
		w.w.Event(ConfigEvent{Config: w.config})
	}
	if sz.X == 0 || sz.Y == 0 {
		return
	}
	width := int(wf*w.scale + .5)
	height := int(hf*w.scale + .5)
	cfg := configFor(w.scale)
	w.setStage(system.StageRunning)
	w.w.Event(frameEvent{
		FrameEvent: system.FrameEvent{
			Now: time.Now(),
			Size: image.Point{
				X: width,
				Y: height,
			},
			Now:    time.Now(),
			Size:   w.config.Size,
			Metric: cfg,
		},
		Sync: true,

M app/os_wayland.go => app/os_wayland.go +30 -27
@@ 187,7 187,9 @@ type window struct {
	serial   C.uint32_t
	newScale bool
	scale    int
	config   Config
	// size is the unscaled window size (unlike config.Size which is scaled).
	size   image.Point
	config Config

	wakeups chan struct{}
}


@@ 222,7 224,7 @@ func init() {
	wlDriver = newWLWindow
}

func newWLWindow(window *callbacks, options []Option) error {
func newWLWindow(callbacks *callbacks, options []Option) error {
	d, err := newWLDisplay()
	if err != nil {
		return err


@@ 232,10 234,14 @@ func newWLWindow(window *callbacks, options []Option) error {
		d.destroy()
		return err
	}
	w.w = window
	w.w = callbacks
	go func() {
		defer d.destroy()
		defer w.destroy()
		// Finish and commit setup from createNativeWindow.
		w.Configure(options)
		C.wl_surface_commit(w.surf)

		w.w.SetDriver(w)
		if err := w.loop(); err != nil {
			panic(err)


@@ 356,15 362,12 @@ func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) {
	C.xdg_surface_add_listener(w.wmSurf, &C.gio_xdg_surface_listener, unsafe.Pointer(w.surf))
	C.xdg_toplevel_add_listener(w.topLvl, &C.gio_xdg_toplevel_listener, unsafe.Pointer(w.surf))

	w.Configure(options)

	if d.decor != nil {
		// Request server side decorations.
		w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(d.decor, w.topLvl)
		C.zxdg_toplevel_decoration_v1_set_mode(w.decor, C.ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE)
	}
	w.updateOpaqueRegion()
	C.wl_surface_commit(w.surf)
	return w, nil
}



@@ 487,8 490,7 @@ func gio_onToplevelClose(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel) {
func gio_onToplevelConfigure(data unsafe.Pointer, topLvl *C.struct_xdg_toplevel, width, height C.int32_t, states *C.struct_wl_array) {
	w := callbackLoad(data).(*window)
	if width != 0 && height != 0 {
		w.config.Size.X = int(width)
		w.config.Size.Y = int(height)
		w.size = image.Pt(int(width), int(height))
		w.updateOpaqueRegion()
	}
}


@@ 855,7 857,7 @@ func (w *window) flushFling() {
	w.fling.xExtrapolation = fling.Extrapolation{}
	w.fling.yExtrapolation = fling.Extrapolation{}
	vel := float32(math.Sqrt(float64(estx.Velocity*estx.Velocity + esty.Velocity*esty.Velocity)))
	_, _, c := w.getConfig()
	_, c := w.getConfig()
	if !w.fling.anim.Start(c, time.Now(), vel) {
		return
	}


@@ 908,11 910,12 @@ func (w *window) WriteClipboard(s string) {
}

func (w *window) Configure(options []Option) {
	_, _, cfg := w.getConfig()
	_, cfg := w.getConfig()
	prev := w.config
	cnf := w.config
	cnf.apply(cfg, options)
	if prev.Size != cnf.Size {
		w.size = image.Pt(cnf.Size.X/w.scale, cnf.Size.Y/w.scale)
		w.config.Size = cnf.Size
	}
	if prev.Title != cnf.Title {


@@ 921,10 924,9 @@ func (w *window) Configure(options []Option) {
		C.xdg_toplevel_set_title(w.topLvl, title)
		C.free(unsafe.Pointer(title))
	}
}

func (w *window) Config() Config {
	return w.config
	if w.config != prev {
		w.w.Event(ConfigEvent{Config: w.config})
	}
}

func (w *window) Raise() {}


@@ 1374,7 1376,7 @@ func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {

func (w *window) updateOpaqueRegion() {
	reg := C.wl_compositor_create_region(w.disp.compositor)
	C.wl_region_add(reg, 0, 0, C.int32_t(w.config.Size.X), C.int32_t(w.config.Size.Y))
	C.wl_region_add(reg, 0, 0, C.int32_t(w.size.X), C.int32_t(w.size.Y))
	C.wl_surface_set_opaque_region(w.surf, reg)
	C.wl_region_destroy(reg)
}


@@ 1404,9 1406,9 @@ func (w *window) updateOutputs() {
	}
}

func (w *window) getConfig() (int, int, unit.Metric) {
	width, height := w.config.Size.X*w.scale, w.config.Size.Y*w.scale
	return width, height, unit.Metric{
func (w *window) getConfig() (image.Point, unit.Metric) {
	size := w.size.Mul(w.scale)
	return size, unit.Metric{
		PxPerDp: w.ppdp * float32(w.scale),
		PxPerSp: w.ppsp * float32(w.scale),
	}


@@ 1419,7 1421,11 @@ func (w *window) draw(sync bool) {
	if dead || (!anim && !sync) {
		return
	}
	width, height, cfg := w.getConfig()
	size, cfg := w.getConfig()
	if size != w.config.Size {
		w.config.Size = size
		w.w.Event(ConfigEvent{Config: w.config})
	}
	if cfg == (unit.Metric{}) {
		return
	}


@@ 1430,11 1436,8 @@ func (w *window) draw(sync bool) {
	}
	w.w.Event(frameEvent{
		FrameEvent: system.FrameEvent{
			Now: time.Now(),
			Size: image.Point{
				X: width,
				Y: height,
			},
			Now:    time.Now(),
			Size:   w.config.Size,
			Metric: cfg,
		},
		Sync: sync,


@@ 1458,12 1461,12 @@ func (w *window) surface() (*C.struct_wl_surface, int, int) {
		C.xdg_surface_ack_configure(w.wmSurf, w.serial)
		w.needAck = false
	}
	width, height, scale := w.config.Size.X, w.config.Size.Y, w.scale
	if w.newScale {
		C.wl_surface_set_buffer_scale(w.surf, C.int32_t(scale))
		C.wl_surface_set_buffer_scale(w.surf, C.int32_t(w.scale))
		w.newScale = false
	}
	return w.surf, width * scale, height * scale
	sz, _ := w.getConfig()
	return w.surf, sz.X, sz.Y
}

func (w *window) ShowTextInput(show bool) {}

M app/os_windows.go => app/os_windows.go +13 -11
@@ 428,20 428,23 @@ func (w *window) setStage(s system.Stage) {
func (w *window) draw(sync bool) {
	var r windows.Rect
	windows.GetClientRect(w.hwnd, &r)
	w.config.Size.X = int(r.Right - r.Left)
	w.config.Size.Y = int(r.Bottom - r.Top)
	size := image.Point{
		X: int(r.Right - r.Left),
		Y: int(r.Bottom - r.Top),
	}
	if w.config.Size.X == 0 || w.config.Size.Y == 0 {
		return
	}
	if size != w.config.Size {
		w.config.Size = size
		w.w.Event(ConfigEvent{Config: w.config})
	}
	dpi := windows.GetWindowDPI(w.hwnd)
	cfg := configForDPI(dpi)
	w.w.Event(frameEvent{
		FrameEvent: system.FrameEvent{
			Now: time.Now(),
			Size: image.Point{
				X: w.config.Size.X,
				Y: w.config.Size.Y,
			},
			Now:    time.Now(),
			Size:   w.config.Size,
			Metric: cfg,
		},
		Sync: sync,


@@ 531,10 534,9 @@ func (w *window) Configure(options []Option) {
	if prev.Mode != cnf.Mode {
		w.SetWindowMode(cnf.Mode)
	}
}

func (w *window) Config() Config {
	return w.config
	if w.config != prev {
		w.w.Event(ConfigEvent{Config: w.config})
	}
}

func (w *window) SetWindowMode(mode WindowMode) {

M app/os_x11.go => app/os_x11.go +9 -10
@@ 161,10 161,9 @@ func (w *x11Window) Configure(options []Option) {
	if prev.Mode != cnf.Mode {
		w.SetWindowMode(cnf.Mode)
	}
}

func (w *x11Window) Config() Config {
	return w.config
	if w.config != prev {
		w.w.Event(ConfigEvent{Config: w.config})
	}
}

func (w *x11Window) Raise() {


@@ 355,11 354,8 @@ loop:
		if (anim || syn) && w.config.Size.X != 0 && w.config.Size.Y != 0 {
			w.w.Event(frameEvent{
				FrameEvent: system.FrameEvent{
					Now: time.Now(),
					Size: image.Point{
						X: w.config.Size.X,
						Y: w.config.Size.Y,
					},
					Now:    time.Now(),
					Size:   w.config.Size,
					Metric: w.metric,
				},
				Sync: syn,


@@ 516,7 512,10 @@ func (h *x11EventHandler) handleEvents() bool {
			w.w.Event(key.FocusEvent{Focus: false})
		case C.ConfigureNotify: // window configuration change
			cevt := (*C.XConfigureEvent)(unsafe.Pointer(xev))
			w.config.Size = image.Pt(int(cevt.width), int(cevt.height))
			if sz := image.Pt(int(cevt.width), int(cevt.height)); sz != w.config.Size {
				w.config.Size = sz
				w.w.Event(ConfigEvent{Config: w.config})
			}
			// redraw will be done by a later expose event
		case C.SelectionNotify:
			cevt := (*C.XSelectionEvent)(unsafe.Pointer(xev))

M app/window.go => app/window.go +0 -11
@@ 263,17 263,6 @@ func (w *Window) Option(opts ...Option) {
	})
}

// Config returns the Window configuration.
//
// A FrameEvent will occur whenever the configuration changes.
func (w *Window) Config() Config {
	var cnf Config
	w.driverRun(func(d driver) {
		cnf = d.Config()
	})
	return cnf
}

// ReadClipboard initiates a read of the clipboard in the form
// of a clipboard.Event. Multiple reads may be coalesced
// to a single event.