app: add Window.Config, export app.Config

A Window configuration with its current option values can now be fetched during a FrameEvent.

The WindowMode and Orientation options have moved to methods on their corresponding types.

Fixes #260

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
This commit is contained in:
Pierre Curto
2021-09-07 07:47:32 +02:00
committed by Elias Naur
parent 30663a71c5
commit bdc2f3f4e8
10 changed files with 356 additions and 326 deletions
+65 -20
View File
@@ -6,6 +6,7 @@ package app
import (
"errors"
"image"
"image/color"
"gioui.org/io/key"
@@ -21,35 +22,76 @@ type size struct {
Height unit.Value
}
type config struct {
Size *size
MinSize *size
MaxSize *size
Title *string
WindowMode *windowMode
StatusColor *color.NRGBA
NavigationColor *color.NRGBA
Orientation *orientation
CustomRenderer bool
// Config describes a Window configuration.
type Config struct {
// Size is the window dimensions (Width, Height).
Size image.Point
// MaxSize is the window maximum allowed dimensions.
MaxSize image.Point
// MinSize is the window minimum allowed dimensions.
MinSize image.Point
// Title is the window title displayed in its decoration bar.
Title string
// WindowMode is the window mode.
Mode WindowMode
// StatusColor is the color of the Android status bar.
StatusColor color.NRGBA
// NavigationColor is the color of the navigation bar
// on Android, or the address bar in browsers.
NavigationColor color.NRGBA
// Orientation is the current window orientation.
Orientation Orientation
// CustomRenderer is true when the window content is rendered by the
// client.
CustomRenderer bool
}
func (c *Config) apply(m unit.Metric, options []Option) {
for _, o := range options {
o(m, c)
}
}
type wakeupEvent struct{}
type windowMode uint8
// WindowMode is the window mode (WindowMode.Option sets it).
//
// Supported platforms are macOS, X11, Windows, Android and JS.
type WindowMode uint8
const (
windowed windowMode = iota
fullscreen
// Windowed is the normal window mode with OS specific window decorations.
Windowed WindowMode = iota
// Fullscreen is the full screen window mode.
Fullscreen
)
type orientation uint8
func (m WindowMode) Option() Option {
return func(_ unit.Metric, cnf *Config) {
cnf.Mode = m
}
}
// Orientation is the orientation of the app (Orientation.Option sets it).
//
// Supported platforms are Android and JS.
type Orientation uint8
const (
anyOrientation orientation = iota
landscapeOrientation
portraitOrientation
// AnyOrientation allows the window to be freely orientated.
AnyOrientation Orientation = iota
// LandscapeOrientation constrains the window to landscape orientations.
LandscapeOrientation
// PortraitOrientation constrains the window to portrait orientations.
PortraitOrientation
)
func (o Orientation) Option() Option {
return func(_ unit.Metric, cnf *Config) {
cnf.Orientation = o
}
}
type frameEvent struct {
system.FrameEvent
@@ -91,7 +133,10 @@ type driver interface {
WriteClipboard(s string)
// Configure the window.
Configure(cnf *config)
Configure([]Option)
// Config returns the current configuration.
Config() Config
// SetCursor updates the current cursor to name.
SetCursor(name pointer.CursorName)
@@ -109,8 +154,8 @@ type windowRendezvous struct {
}
type windowAndConfig struct {
window *callbacks
cnf *config
window *callbacks
options []Option
}
func newWindowRendezvous() *windowRendezvous {
+35 -26
View File
@@ -144,7 +144,8 @@ type window struct {
started bool
animating bool
win *C.ANativeWindow
win *C.ANativeWindow
config Config
}
// gioView hold cached JNI methods for GioView.
@@ -337,7 +338,7 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j
handle := C.jlong(view)
views[handle] = w
w.loadConfig(env, class)
w.Configure(wopts.cnf)
w.Configure(wopts.options)
w.setStage(system.StagePaused)
w.callbacks.Event(ViewEvent{View: uintptr(view)})
return handle
@@ -762,8 +763,8 @@ func goString(env *C.JNIEnv, str C.jstring) string {
func osMain() {
}
func newWindow(window *callbacks, cnf *config) error {
mainWindow.in <- windowAndConfig{window, cnf}
func newWindow(window *callbacks, options []Option) error {
mainWindow.in <- windowAndConfig{window, options}
return <-mainWindow.errs
}
@@ -787,23 +788,40 @@ func (w *window) ReadClipboard() {
})
}
func (w *window) Configure(cnf *config) {
func (w *window) Configure(options []Option) {
runInJVM(javaVM(), func(env *C.JNIEnv) {
if o := cnf.Orientation; o != nil {
setOrientation(env, w.view, *o)
prev := w.config
cnf := w.config
cnf.apply(unit.Metric{}, options)
if prev.Orientation != cnf.Orientation {
w.config.Orientation = cnf.Orientation
setOrientation(env, w.view, cnf.Orientation)
}
if o := cnf.NavigationColor; o != nil {
setNavigationColor(env, w.view, *o)
if prev.NavigationColor != cnf.NavigationColor {
w.config.NavigationColor = cnf.NavigationColor
setNavigationColor(env, w.view, cnf.NavigationColor)
}
if o := cnf.StatusColor; o != nil {
setStatusColor(env, w.view, *o)
if prev.StatusColor != cnf.StatusColor {
w.config.StatusColor = cnf.StatusColor
setStatusColor(env, w.view, cnf.StatusColor)
}
if o := cnf.WindowMode; o != nil {
setWindowMode(env, w.view, *o)
if prev.Mode != cnf.Mode {
switch cnf.Mode {
case Fullscreen:
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE)
w.config.Mode = Fullscreen
case Windowed:
callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE)
w.config.Mode = Windowed
}
}
})
}
func (w *window) Config() Config {
return w.config
}
func (w *window) SetCursor(name pointer.CursorName) {
runInJVM(javaVM(), func(env *C.JNIEnv) {
setCursor(env, w.view, name)
@@ -839,18 +857,18 @@ func setCursor(env *C.JNIEnv, view C.jobject, name pointer.CursorName) {
callVoidMethod(env, view, gioView.setCursor, jvalue(curID))
}
func setOrientation(env *C.JNIEnv, view C.jobject, mode orientation) {
func setOrientation(env *C.JNIEnv, view C.jobject, mode Orientation) {
var (
id int
idFallback int // Used only for SDK 17 or older.
)
// Constants defined at https://developer.android.com/reference/android/content/pm/ActivityInfo.
switch mode {
case anyOrientation:
case AnyOrientation:
id, idFallback = 2, 2 // SCREEN_ORIENTATION_USER
case landscapeOrientation:
case LandscapeOrientation:
id, idFallback = 11, 0 // SCREEN_ORIENTATION_USER_LANDSCAPE (or SCREEN_ORIENTATION_LANDSCAPE)
case portraitOrientation:
case PortraitOrientation:
id, idFallback = 12, 1 // SCREEN_ORIENTATION_USER_PORTRAIT (or SCREEN_ORIENTATION_PORTRAIT)
}
callVoidMethod(env, view, gioView.setOrientation, jvalue(id), jvalue(idFallback))
@@ -870,15 +888,6 @@ func setNavigationColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) {
)
}
func setWindowMode(env *C.JNIEnv, view C.jobject, mode windowMode) {
switch mode {
case fullscreen:
callVoidMethod(env, view, gioView.setFullscreen, C.JNI_TRUE)
default:
callVoidMethod(env, view, gioView.setFullscreen, C.JNI_FALSE)
}
}
// Close the window. Not implemented for Android.
func (w *window) Close() {}
+8 -3
View File
@@ -95,6 +95,7 @@ type window struct {
visible bool
cursor pointer.CursorName
config Config
pointerMap []C.CFTypeRef
}
@@ -268,7 +269,11 @@ func (w *window) WriteClipboard(s string) {
C.writeClipboard(chars, C.NSUInteger(len(u16)))
}
func (w *window) Configure(cnf *config) {}
func (w *window) Configure([]Option) {}
func (w *window) Config() Config {
return w.config
}
func (w *window) SetAnimating(anim bool) {
v := w.view
@@ -329,8 +334,8 @@ func (w *window) SetInputHint(_ key.InputHint) {}
// Close the window. Not implemented for iOS.
func (w *window) Close() {}
func newWindow(win *callbacks, cnf *config) error {
mainWindow.in <- windowAndConfig{win, cnf}
func newWindow(win *callbacks, options []Option) error {
mainWindow.in <- windowAndConfig{win, options}
return <-mainWindow.errs
}
+41 -29
View File
@@ -46,7 +46,7 @@ type window struct {
chanAnimation chan struct{}
chanRedraw chan struct{}
size f32.Point
config Config
inset f32.Point
scale float32
animating bool
@@ -56,7 +56,7 @@ type window struct {
wakeups chan struct{}
}
func newWindow(win *callbacks, cnf *config) error {
func newWindow(win *callbacks, options []Option) error {
doc := js.Global().Get("document")
cont := getContainer(doc)
cnv := createCanvas(doc)
@@ -94,7 +94,7 @@ func newWindow(win *callbacks, cnf *config) error {
})
w.addEventListeners()
w.addHistory()
w.Configure(cnf)
w.Configure(options)
w.w = win
go func() {
@@ -509,21 +509,31 @@ func (w *window) WriteClipboard(s string) {
w.clipboard.Call("writeText", s)
}
func (w *window) Configure(cnf *config) {
if o := cnf.Title; o != nil {
w.document.Set("title", *o)
func (w *window) Configure(options []Option) {
prev := w.config
cnf := w.config
cnf.apply(unit.Metric{}, options)
if prev.Title != cnf.Title {
w.config.Title = cnf.Title
w.document.Set("title", cnf.Title)
}
if o := cnf.WindowMode; o != nil {
w.windowMode(*o)
if prev.Mode != cnf.Mode {
w.windowMode(cnf.Mode)
}
if o := cnf.NavigationColor; o != nil {
w.navigationColor(*o)
if prev.NavigationColor != cnf.NavigationColor {
w.config.NavigationColor = cnf.NavigationColor
w.navigationColor(cnf.NavigationColor)
}
if o := cnf.Orientation; o != nil {
w.orientation(*o)
if prev.Orientation != cnf.Orientation {
w.config.Orientation = cnf.Orientation
w.orientation(cnf.Orientation)
}
}
func (w *window) Config() Config {
return w.config
}
func (w *window) SetCursor(name pointer.CursorName) {
style := w.cnv.Get("style")
style.Set("cursor", string(name))
@@ -559,24 +569,24 @@ func (w *window) resize() {
w.scale = float32(w.window.Get("devicePixelRatio").Float())
rect := w.cnv.Call("getBoundingClientRect")
w.size.X = float32(rect.Get("width").Float()) * w.scale
w.size.Y = float32(rect.Get("height").Float()) * w.scale
w.config.Size.X = int(rect.Get("width").Float()) * int(w.scale)
w.config.Size.Y = int(rect.Get("height").Float()) * int(w.scale)
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
w.inset.X = w.size.X - float32(vx.Float())*w.scale
w.inset.Y = w.size.Y - float32(vy.Float())*w.scale
w.inset.X = float32(w.config.Size.X) - float32(vx.Float())*w.scale
w.inset.Y = float32(w.config.Size.Y) - float32(vy.Float())*w.scale
}
if w.size.X == 0 || w.size.Y == 0 {
if w.config.Size.X == 0 || w.config.Size.Y == 0 {
return
}
w.cnv.Set("width", int(w.size.X+.5))
w.cnv.Set("height", int(w.size.Y+.5))
w.cnv.Set("width", w.config.Size.X)
w.cnv.Set("height", w.config.Size.Y)
}
func (w *window) draw(sync bool) {
width, height, insets, metric := w.config()
width, height, insets, metric := w.getConfig()
if metric == (unit.Metric{}) || width == 0 || height == 0 {
return
}
@@ -595,8 +605,8 @@ func (w *window) draw(sync bool) {
})
}
func (w *window) config() (int, int, system.Insets, unit.Metric) {
return int(w.size.X + .5), int(w.size.Y + .5), system.Insets{
func (w *window) getConfig() (int, int, system.Insets, unit.Metric) {
return w.config.Size.X, w.config.Size.Y, system.Insets{
Bottom: unit.Px(w.inset.Y),
Right: unit.Px(w.inset.X),
}, unit.Metric{
@@ -605,9 +615,9 @@ func (w *window) config() (int, int, system.Insets, unit.Metric) {
}
}
func (w *window) windowMode(mode windowMode) {
func (w *window) windowMode(mode WindowMode) {
switch mode {
case windowed:
case Windowed:
if !w.document.Get("fullscreenElement").Truthy() {
return // Browser is already Windowed.
}
@@ -615,26 +625,28 @@ func (w *window) windowMode(mode windowMode) {
return // Browser doesn't support such feature.
}
w.document.Call("exitFullscreen")
case fullscreen:
w.config.Mode = Windowed
case Fullscreen:
elem := w.document.Get("documentElement")
if !elem.Get("requestFullscreen").Truthy() {
return // Browser doesn't support such feature.
}
elem.Call("requestFullscreen")
w.config.Mode = Fullscreen
}
}
func (w *window) orientation(mode orientation) {
func (w *window) orientation(mode Orientation) {
if j := w.screenOrientation; !j.Truthy() || !j.Get("unlock").Truthy() || !j.Get("lock").Truthy() {
return // Browser don't support Screen Orientation API.
}
switch mode {
case anyOrientation:
case AnyOrientation:
w.screenOrientation.Call("unlock")
case landscapeOrientation:
case LandscapeOrientation:
w.screenOrientation.Call("lock", "landscape").Call("then", w.redraw)
case portraitOrientation:
case PortraitOrientation:
w.screenOrientation.Call("lock", "portrait").Call("then", w.redraw)
}
}
+33 -37
View File
@@ -152,8 +152,8 @@ type window struct {
displayLink *displayLink
cursor pointer.CursorName
scale float32
mode windowMode
scale float32
config Config
}
// viewMap is the mapping from Cocoa NSViews to Go windows.
@@ -210,50 +210,46 @@ func (w *window) WriteClipboard(s string) {
C.writeClipboard(chars, C.NSUInteger(len(u16)))
}
func (w *window) Configure(cnf *config) {
func (w *window) Configure(options []Option) {
screenScale := float32(C.getScreenBackingScale())
cfg := configFor(screenScale)
val := func(v unit.Value) float32 {
return float32(cfg.Px(v)) / screenScale
prev := w.config
cnf := w.config
cnf.apply(cfg, options)
cnf.Size = cnf.Size.Div(int(screenScale))
cnf.MinSize = cnf.MinSize.Div(int(screenScale))
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
if prev.Size != cnf.Size {
w.config.Size = cnf.Size
C.setSize(w.window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
}
if o := cnf.Size; o != nil {
width := val(o.Width)
height := val(o.Height)
if width > 0 || height > 0 {
C.setSize(w.window, C.CGFloat(width), C.CGFloat(height))
}
if prev.MinSize != cnf.MinSize {
w.config.MinSize = cnf.MinSize
C.setMinSize(w.window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
}
if o := cnf.MinSize; o != nil {
width := val(o.Width)
height := val(o.Height)
if width > 0 || height > 0 {
C.setMinSize(w.window, C.CGFloat(width), C.CGFloat(height))
}
if prev.MaxSize != cnf.MaxSize {
w.config.MaxSize = cnf.MaxSize
C.setMaxSize(w.window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y))
}
if o := cnf.MaxSize; o != nil {
width := val(o.Width)
height := val(o.Height)
if width > 0 || height > 0 {
C.setMaxSize(w.window, C.CGFloat(width), C.CGFloat(height))
}
}
if o := cnf.Title; o != nil {
title := C.CString(*o)
if prev.Title != cnf.Title {
w.config.Title = cnf.Title
title := C.CString(cnf.Title)
defer C.free(unsafe.Pointer(title))
C.setTitle(w.window, title)
}
if o := cnf.WindowMode; o != nil {
w.SetWindowMode(*o)
if prev.Mode != cnf.Mode {
switch cnf.Mode {
case Windowed, Fullscreen:
w.config.Mode = cnf.Mode
C.toggleFullScreen(w.window)
}
}
}
func (w *window) SetWindowMode(mode windowMode) {
switch mode {
case w.mode:
case windowed, fullscreen:
C.toggleFullScreen(w.window)
w.mode = mode
}
func (w *window) Config() Config {
return w.config
}
func (w *window) SetCursor(name pointer.CursorName) {
@@ -455,7 +451,7 @@ func gio_onFinishLaunching() {
close(launched)
}
func newWindow(win *callbacks, cnf *config) error {
func newWindow(win *callbacks, options []Option) error {
<-launched
errch := make(chan error)
runOnMain(func() {
@@ -468,7 +464,7 @@ func newWindow(win *callbacks, cnf *config) error {
w.w = win
w.window = C.gio_createWindow(w.view, nil, 0, 0, 0, 0, 0, 0)
win.SetDriver(w)
w.Configure(cnf)
w.Configure(options)
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
// and just returns the offset we need for the first window.
+3 -3
View File
@@ -21,19 +21,19 @@ func osMain() {
select {}
}
type windowDriver func(*callbacks, *config) error
type windowDriver func(*callbacks, []Option) error
// Instead of creating files with build tags for each combination of wayland +/- x11
// let each driver initialize these variables with their own version of createWindow.
var wlDriver, x11Driver windowDriver
func newWindow(window *callbacks, cnf *config) error {
func newWindow(window *callbacks, options []Option) error {
var errFirst error
for _, d := range []windowDriver{x11Driver, wlDriver} {
if d == nil {
continue
}
err := d(window, cnf)
err := d(window, options)
if err == nil {
return nil
}
+27 -21
View File
@@ -185,10 +185,9 @@ type window struct {
needAck bool
// The most recent configure serial waiting to be ack'ed.
serial C.uint32_t
width int
height int
newScale bool
scale int
config Config
wakeups chan struct{}
}
@@ -223,12 +222,12 @@ func init() {
wlDriver = newWLWindow
}
func newWLWindow(window *callbacks, cnf *config) error {
func newWLWindow(window *callbacks, options []Option) error {
d, err := newWLDisplay()
if err != nil {
return err
}
w, err := d.createNativeWindow(cnf)
w, err := d.createNativeWindow(options)
if err != nil {
d.destroy()
return err
@@ -290,7 +289,7 @@ func (d *wlDisplay) readClipboard() (io.ReadCloser, error) {
return r, nil
}
func (d *wlDisplay) createNativeWindow(cnf *config) (*window, error) {
func (d *wlDisplay) createNativeWindow(options []Option) (*window, error) {
if d.compositor == nil {
return nil, errors.New("wayland: no compositor available")
}
@@ -357,7 +356,7 @@ func (d *wlDisplay) createNativeWindow(cnf *config) (*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(cnf)
w.Configure(options)
if d.decor != nil {
// Request server side decorations.
@@ -488,8 +487,8 @@ 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.width = int(width)
w.height = int(height)
w.config.Size.X = int(width)
w.config.Size.Y = int(height)
w.updateOpaqueRegion()
}
}
@@ -856,7 +855,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.config()
_, _, c := w.getConfig()
if !w.fling.anim.Start(c, time.Now(), vel) {
return
}
@@ -908,19 +907,26 @@ func (w *window) WriteClipboard(s string) {
w.disp.writeClipboard([]byte(s))
}
func (w *window) Configure(cnf *config) {
_, _, cfg := w.config()
if o := cnf.Size; o != nil {
w.width = cfg.Px(o.Width)
w.height = cfg.Px(o.Height)
func (w *window) Configure(options []Option) {
_, _, cfg := w.getConfig()
prev := w.config
cnf := w.config
cnf.apply(cfg, options)
if prev.Size != cnf.Size {
w.config.Size = cnf.Size
}
if o := cnf.Title; o != nil {
title := C.CString(*o)
if prev.Title != cnf.Title {
w.config.Title = cnf.Title
title := C.CString(cnf.Title)
C.xdg_toplevel_set_title(w.topLvl, title)
C.free(unsafe.Pointer(title))
}
}
func (w *window) Config() Config {
return w.config
}
func (w *window) SetCursor(name pointer.CursorName) {
if name == pointer.CursorNone {
C.wl_pointer_set_cursor(w.disp.seat.pointer, w.serial, nil, 0, 0)
@@ -1366,7 +1372,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.width), C.int32_t(w.height))
C.wl_region_add(reg, 0, 0, C.int32_t(w.config.Size.X), C.int32_t(w.config.Size.Y))
C.wl_surface_set_opaque_region(w.surf, reg)
C.wl_region_destroy(reg)
}
@@ -1396,8 +1402,8 @@ func (w *window) updateOutputs() {
}
}
func (w *window) config() (int, int, unit.Metric) {
width, height := w.width*w.scale, w.height*w.scale
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{
PxPerDp: w.ppdp * float32(w.scale),
PxPerSp: w.ppsp * float32(w.scale),
@@ -1411,7 +1417,7 @@ func (w *window) draw(sync bool) {
if dead || (!anim && !sync) {
return
}
width, height, cfg := w.config()
width, height, cfg := w.getConfig()
if cfg == (unit.Metric{}) {
return
}
@@ -1450,7 +1456,7 @@ 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.width, w.height, w.scale
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))
w.newScale = false
+47 -63
View File
@@ -32,11 +32,6 @@ type ViewEvent struct {
HWND uintptr
}
type winConstraints struct {
minWidth, minHeight int32
maxWidth, maxHeight int32
}
type winDeltas struct {
width int32
height int32
@@ -46,8 +41,6 @@ type window struct {
hwnd syscall.Handle
hdc syscall.Handle
w *callbacks
width int
height int
stage system.Stage
pointerBtns pointer.Buttons
@@ -61,9 +54,8 @@ type window struct {
animating bool
minmax winConstraints
deltas winDeltas
cnf *config
config Config
}
const _WM_WAKEUP = windows.WM_USER + iota
@@ -96,7 +88,7 @@ func osMain() {
select {}
}
func newWindow(window *callbacks, cnf *config) error {
func newWindow(window *callbacks, options []Option) error {
cerr := make(chan error)
go func() {
// GetMessage and PeekMessage can filter on a window HWND, but
@@ -104,7 +96,7 @@ func newWindow(window *callbacks, cnf *config) error {
// Instead lock the thread so window messages arrive through
// unfiltered GetMessage calls.
runtime.LockOSThread()
w, err := createNativeWindow(cnf)
w, err := createNativeWindow()
if err != nil {
cerr <- err
return
@@ -115,7 +107,7 @@ func newWindow(window *callbacks, cnf *config) error {
w.w = window
w.w.SetDriver(w)
w.w.Event(ViewEvent{HWND: uintptr(w.hwnd)})
w.Configure(cnf)
w.Configure(options)
windows.ShowWindow(w.hwnd, windows.SW_SHOWDEFAULT)
windows.SetForegroundWindow(w.hwnd)
windows.SetFocus(w.hwnd)
@@ -159,20 +151,7 @@ func initResources() error {
return nil
}
func getWindowConstraints(cfg unit.Metric, cnf *config) winConstraints {
var minmax winConstraints
if o := cnf.MinSize; o != nil {
minmax.minWidth = int32(cfg.Px(o.Width))
minmax.minHeight = int32(cfg.Px(o.Height))
}
if o := cnf.MaxSize; o != nil {
minmax.maxWidth = int32(cfg.Px(o.Width))
minmax.maxHeight = int32(cfg.Px(o.Height))
}
return minmax
}
func createNativeWindow(cnf *config) (*window, error) {
func createNativeWindow() (*window, error) {
var resErr error
resources.once.Do(func() {
resErr = initResources()
@@ -180,8 +159,6 @@ func createNativeWindow(cnf *config) (*window, error) {
if resErr != nil {
return nil, resErr
}
dpi := windows.GetSystemDPI()
cfg := configForDPI(dpi)
dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW)
dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE)
@@ -199,9 +176,7 @@ func createNativeWindow(cnf *config) (*window, error) {
return nil, err
}
w := &window{
hwnd: hwnd,
minmax: getWindowConstraints(cfg, cnf),
cnf: cnf,
hwnd: hwnd,
}
w.hdc, err = windows.GetDC(hwnd)
if err != nil {
@@ -311,16 +286,16 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
}
case windows.WM_GETMINMAXINFO:
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
if w.minmax.minWidth > 0 || w.minmax.minHeight > 0 {
if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
mm.PtMinTrackSize = windows.Point{
X: w.minmax.minWidth + w.deltas.width,
Y: w.minmax.minHeight + w.deltas.height,
X: int32(p.X) + w.deltas.width,
Y: int32(p.Y) + w.deltas.height,
}
}
if w.minmax.maxWidth > 0 || w.minmax.maxHeight > 0 {
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
mm.PtMaxTrackSize = windows.Point{
X: w.minmax.maxWidth + w.deltas.width,
Y: w.minmax.maxHeight + w.deltas.height,
X: int32(p.X) + w.deltas.width,
Y: int32(p.Y) + w.deltas.height,
}
}
case windows.WM_SETCURSOR:
@@ -453,20 +428,19 @@ func (w *window) setStage(s system.Stage) {
func (w *window) draw(sync bool) {
var r windows.Rect
windows.GetClientRect(w.hwnd, &r)
w.width = int(r.Right - r.Left)
w.height = int(r.Bottom - r.Top)
if w.width == 0 || w.height == 0 {
w.config.Size.X = int(r.Right - r.Left)
w.config.Size.Y = int(r.Bottom - r.Top)
if w.config.Size.X == 0 || w.config.Size.Y == 0 {
return
}
dpi := windows.GetWindowDPI(w.hwnd)
cfg := configForDPI(dpi)
w.minmax = getWindowConstraints(cfg, w.cnf)
w.w.Event(frameEvent{
FrameEvent: system.FrameEvent{
Now: time.Now(),
Size: image.Point{
X: w.width,
Y: w.height,
X: w.config.Size.X,
Y: w.config.Size.Y,
},
Metric: cfg,
},
@@ -515,13 +489,17 @@ func (w *window) readClipboard() error {
return nil
}
func (w *window) Configure(cnf *config) {
w.cnf = cnf
if o := cnf.Size; o != nil {
dpi := windows.GetSystemDPI()
cfg := configForDPI(dpi)
width := int32(cfg.Px(o.Width))
height := int32(cfg.Px(o.Height))
func (w *window) Configure(options []Option) {
dpi := windows.GetSystemDPI()
cfg := configForDPI(dpi)
prev := w.config
cnf := w.config
cnf.apply(cfg, options)
if prev.Size != cnf.Size {
width := int32(cnf.Size.X)
height := int32(cnf.Size.Y)
w.config.Size = cnf.Size
// Include the window decorations.
wr := windows.Rect{
@@ -538,30 +516,35 @@ func (w *window) Configure(cnf *config) {
w.deltas.width = width - dw
w.deltas.height = height - dh
w.cnf.Size = o
windows.MoveWindow(w.hwnd, 0, 0, width, height, true)
}
if o := cnf.MinSize; o != nil {
w.cnf.MinSize = o
if prev.MinSize != cnf.MinSize {
w.config.MinSize = cnf.MinSize
}
if o := cnf.MaxSize; o != nil {
w.cnf.MaxSize = o
if prev.MaxSize != cnf.MaxSize {
w.config.MaxSize = cnf.MaxSize
}
if o := cnf.Title; o != nil {
windows.SetWindowText(w.hwnd, *cnf.Title)
if prev.Title != cnf.Title {
w.config.Title = cnf.Title
windows.SetWindowText(w.hwnd, cnf.Title)
}
if o := cnf.WindowMode; o != nil {
w.SetWindowMode(*o)
if prev.Mode != cnf.Mode {
w.SetWindowMode(cnf.Mode)
}
}
func (w *window) SetWindowMode(mode windowMode) {
func (w *window) Config() Config {
return w.config
}
func (w *window) SetWindowMode(mode WindowMode) {
// https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353
switch mode {
case windowed:
case Windowed:
if w.placement == nil {
return
}
w.config.Mode = Windowed
windows.SetWindowPlacement(w.hwnd, w.placement)
w.placement = nil
style := windows.GetWindowLong(w.hwnd)
@@ -570,10 +553,11 @@ func (w *window) SetWindowMode(mode windowMode) {
0, 0, 0, 0,
windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED,
)
case fullscreen:
case Fullscreen:
if w.placement != nil {
return
}
w.config.Mode = Fullscreen
w.placement = windows.GetWindowPlacement(w.hwnd)
style := windows.GetWindowLong(w.hwnd)
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style&^windows.WS_OVERLAPPEDWINDOW)
@@ -672,7 +656,7 @@ func (w *window) HDC() syscall.Handle {
}
func (w *window) HWND() (syscall.Handle, int, int) {
return w.hwnd, w.width, w.height
return w.hwnd, w.config.Size.X, w.config.Size.Y
}
func (w *window) Close() {
+55 -54
View File
@@ -81,9 +81,7 @@ type x11Window struct {
wmStateFullscreen C.Atom
}
stage system.Stage
cfg unit.Metric
width int
height int
metric unit.Metric
notify struct {
read, write int
}
@@ -97,7 +95,7 @@ type x11Window struct {
content []byte
}
cursor pointer.CursorName
mode windowMode
config Config
wakeups chan struct{}
}
@@ -116,48 +114,57 @@ func (w *x11Window) WriteClipboard(s string) {
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
}
func (w *x11Window) Configure(cnf *config) {
func (w *x11Window) Configure(options []Option) {
var shints C.XSizeHints
if o := cnf.MinSize; o != nil {
shints.min_width = C.int(w.cfg.Px(o.Width))
shints.min_height = C.int(w.cfg.Px(o.Height))
prev := w.config
cnf := w.config
cnf.apply(w.metric, options)
if prev.MinSize != cnf.MinSize {
w.config.MinSize = cnf.MinSize
shints.min_width = C.int(cnf.MinSize.X)
shints.min_height = C.int(cnf.MinSize.Y)
shints.flags = C.PMinSize
}
if o := cnf.MaxSize; o != nil {
shints.max_width = C.int(w.cfg.Px(o.Width))
shints.max_height = C.int(w.cfg.Px(o.Height))
if prev.MaxSize != cnf.MaxSize {
w.config.MaxSize = cnf.MaxSize
shints.max_width = C.int(cnf.MaxSize.X)
shints.max_height = C.int(cnf.MaxSize.Y)
shints.flags = shints.flags | C.PMaxSize
}
if shints.flags != 0 {
C.XSetWMNormalHints(w.x, w.xw, &shints)
}
if o := cnf.Size; o != nil {
C.XResizeWindow(w.x, w.xw, C.uint(w.cfg.Px(o.Width)), C.uint(w.cfg.Px(o.Height)))
if prev.Size != cnf.Size {
w.config.Size = cnf.Size
C.XResizeWindow(w.x, w.xw, C.uint(cnf.Size.X), C.uint(cnf.Size.Y))
}
var title string
if o := cnf.Title; o != nil {
title = *o
if prev.Title != cnf.Title {
title := cnf.Title
ctitle := C.CString(title)
defer C.free(unsafe.Pointer(ctitle))
C.XStoreName(w.x, w.xw, ctitle)
// set _NET_WM_NAME as well for UTF-8 support in window title.
C.XSetTextProperty(w.x, w.xw,
&C.XTextProperty{
value: (*C.uchar)(unsafe.Pointer(ctitle)),
encoding: w.atoms.utf8string,
format: 8,
nitems: C.ulong(len(title)),
},
w.atoms.wmName)
}
ctitle := C.CString(title)
defer C.free(unsafe.Pointer(ctitle))
C.XStoreName(w.x, w.xw, ctitle)
// set _NET_WM_NAME as well for UTF-8 support in window title.
C.XSetTextProperty(w.x, w.xw,
&C.XTextProperty{
value: (*C.uchar)(unsafe.Pointer(ctitle)),
encoding: w.atoms.utf8string,
format: 8,
nitems: C.ulong(len(title)),
},
w.atoms.wmName)
if o := cnf.WindowMode; o != nil {
w.SetWindowMode(*o)
if prev.Mode != cnf.Mode {
w.SetWindowMode(cnf.Mode)
}
}
func (w *x11Window) Config() Config {
return w.config
}
func (w *x11Window) SetCursor(name pointer.CursorName) {
switch name {
case pointer.CursorNone:
@@ -182,13 +189,11 @@ func (w *x11Window) SetCursor(name pointer.CursorName) {
C.XDefineCursor(w.x, w.xw, c)
}
func (w *x11Window) SetWindowMode(mode windowMode) {
func (w *x11Window) SetWindowMode(mode WindowMode) {
switch mode {
case w.mode:
return
case windowed:
case Windowed:
C.XDeleteProperty(w.x, w.xw, w.atoms.wmStateFullscreen)
case fullscreen:
case Fullscreen:
C.XChangeProperty(w.x, w.xw, w.atoms.wmState, C.XA_ATOM,
32, C.PropModeReplace,
(*C.uchar)(unsafe.Pointer(&w.atoms.wmStateFullscreen)), 1,
@@ -196,7 +201,7 @@ func (w *x11Window) SetWindowMode(mode windowMode) {
default:
return
}
w.mode = mode
w.config.Mode = mode
// "A Client wishing to change the state of a window MUST send
// a _NET_WM_STATE client message to the root window (see below)."
var xev C.XEvent
@@ -261,7 +266,7 @@ func (w *x11Window) display() *C.Display {
}
func (w *x11Window) window() (C.Window, int, int) {
return w.xw, w.width, w.height
return w.xw, w.config.Size.X, w.config.Size.Y
}
func (w *x11Window) setStage(s system.Stage) {
@@ -327,15 +332,15 @@ loop:
default:
}
if (anim || syn) && w.width != 0 && w.height != 0 {
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.width,
Y: w.height,
X: w.config.Size.X,
Y: w.config.Size.Y,
},
Metric: w.cfg,
Metric: w.metric,
},
Sync: syn,
})
@@ -491,8 +496,7 @@ 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.width = int(cevt.width)
w.height = int(cevt.height)
w.config.Size = image.Pt(int(cevt.width), int(cevt.height))
// redraw will be done by a later expose event
case C.SelectionNotify:
cevt := (*C.XSelectionEvent)(unsafe.Pointer(xev))
@@ -583,7 +587,7 @@ func init() {
x11Driver = newX11Window
}
func newX11Window(gioWin *callbacks, cnf *config) error {
func newX11Window(gioWin *callbacks, options []Option) error {
var err error
pipe := make([]int, 2)
@@ -623,6 +627,9 @@ func newX11Window(gioWin *callbacks, cnf *config) error {
ppsp := x11DetectUIScale(dpy)
cfg := unit.Metric{PxPerDp: ppsp, PxPerSp: ppsp}
var cnf Config
cnf.apply(cfg, options)
swa := C.XSetWindowAttributes{
event_mask: C.ExposureMask | C.FocusChangeMask | // update
C.KeyPressMask | C.KeyReleaseMask | // keyboard
@@ -632,24 +639,18 @@ func newX11Window(gioWin *callbacks, cnf *config) error {
background_pixmap: C.None,
override_redirect: C.False,
}
var width, height int
if o := cnf.Size; o != nil {
width = cfg.Px(o.Width)
height = cfg.Px(o.Height)
}
win := C.XCreateWindow(dpy, C.XDefaultRootWindow(dpy),
0, 0, C.uint(width), C.uint(height),
0, 0, C.uint(cnf.Size.X), C.uint(cnf.Size.Y),
0, C.CopyFromParent, C.InputOutput, nil,
C.CWEventMask|C.CWBackPixmap|C.CWOverrideRedirect, &swa)
w := &x11Window{
w: gioWin, x: dpy, xw: win,
width: width,
height: height,
cfg: cfg,
metric: cfg,
xkb: xkb,
xkbEventBase: xkbEventBase,
wakeups: make(chan struct{}, 1),
config: cnf,
}
w.notify.read = pipe[0]
w.notify.write = pipe[1]
@@ -684,7 +685,7 @@ func newX11Window(gioWin *callbacks, cnf *config) error {
// extensions
C.XSetWMProtocols(dpy, win, &w.atoms.evDelWindow, 1)
w.Configure(cnf)
w.Configure(options)
// make the window visible on the screen
C.XMapWindow(dpy, win)
+42 -70
View File
@@ -23,7 +23,7 @@ import (
)
// Option configures a window.
type Option func(cnf *config)
type Option func(unit.Metric, *Config)
// Window represents an operating system window.
type Window struct {
@@ -93,14 +93,13 @@ var ackEvent event.Event
// Calling NewWindow more than once is not supported on
// iOS, Android, WebAssembly.
func NewWindow(options ...Option) *Window {
cnf := new(config)
// Default options.
Size(unit.Dp(800), unit.Dp(600))(cnf)
Title("Gio")(cnf)
for _, o := range options {
o(cnf)
defaultOptions := []Option{
Size(unit.Dp(800), unit.Dp(600)),
Title("Gio"),
}
options = append(defaultOptions, options...)
var cnf Config
cnf.apply(unit.Metric{}, options)
w := &Window{
in: make(chan event.Event),
@@ -116,7 +115,7 @@ func NewWindow(options ...Option) *Window {
nocontext: cnf.CustomRenderer,
}
w.callbacks.w = w
go w.run(cnf)
go w.run(options)
return w
}
@@ -260,14 +259,21 @@ func (w *Window) Invalidate() {
// Option applies the options to the window.
func (w *Window) Option(opts ...Option) {
w.driverDefer(func(d driver) {
c := new(config)
for _, opt := range opts {
opt(c)
}
d.Configure(c)
d.Configure(opts)
})
}
// 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.
@@ -491,7 +497,7 @@ func (w *Window) waitFrame() (*op.Ops, bool) {
}
}
func (w *Window) run(cnf *config) {
func (w *Window) run(options []Option) {
// Some OpenGL drivers don't like being made current on many different
// OS threads. Force the Go runtime to map the event loop goroutine to
// only one thread.
@@ -499,7 +505,7 @@ func (w *Window) run(cnf *config) {
defer close(w.out)
defer close(w.dead)
if err := newWindow(&w.callbacks, cnf); err != nil {
if err := newWindow(&w.callbacks, options); err != nil {
w.out <- system.DestroyEvent{Err: err}
return
}
@@ -602,44 +608,10 @@ func (q *queue) Events(k event.Tag) []event.Event {
return q.q.Events(k)
}
var (
// Windowed is the normal window mode with OS specific window decorations.
Windowed Option = modeOption(windowed)
// Fullscreen is the full screen window mode.
Fullscreen Option = modeOption(fullscreen)
)
// WindowMode sets the window mode.
//
// Supported platforms are macOS, X11, Windows and JS.
func modeOption(mode windowMode) Option {
return func(cnf *config) {
cnf.WindowMode = &mode
}
}
var (
// AnyOrientation allows the window to be freely orientated.
AnyOrientation Option = orientationOption(anyOrientation)
// LandscapeOrientation constrains the window to landscape orientations.
LandscapeOrientation Option = orientationOption(landscapeOrientation)
// PortraitOrientation constrains the window to portrait orientations.
PortraitOrientation Option = orientationOption(portraitOrientation)
)
// orientation sets the orientation of the app.
//
// Supported platforms are Android and JS.
func orientationOption(mode orientation) Option {
return func(cnf *config) {
cnf.Orientation = &mode
}
}
// Title sets the title of the window.
func Title(t string) Option {
return func(cnf *config) {
cnf.Title = &t
return func(_ unit.Metric, cnf *Config) {
cnf.Title = t
}
}
@@ -651,10 +623,10 @@ func Size(w, h unit.Value) Option {
if h.V <= 0 {
panic("height must be larger than or equal to 0")
}
return func(cnf *config) {
cnf.Size = &size{
Width: w,
Height: h,
return func(m unit.Metric, cnf *Config) {
cnf.Size = image.Point{
X: m.Px(w),
Y: m.Px(h),
}
}
}
@@ -667,10 +639,10 @@ func MaxSize(w, h unit.Value) Option {
if h.V <= 0 {
panic("height must be larger than or equal to 0")
}
return func(cnf *config) {
cnf.MaxSize = &size{
Width: w,
Height: h,
return func(m unit.Metric, cnf *Config) {
cnf.MaxSize = image.Point{
X: m.Px(w),
Y: m.Px(h),
}
}
}
@@ -683,32 +655,32 @@ func MinSize(w, h unit.Value) Option {
if h.V <= 0 {
panic("height must be larger than or equal to 0")
}
return func(cnf *config) {
cnf.MinSize = &size{
Width: w,
Height: h,
return func(m unit.Metric, cnf *Config) {
cnf.MinSize = image.Point{
X: m.Px(w),
Y: m.Px(h),
}
}
}
// StatusColor sets the color of the Android status bar.
func StatusColor(color color.NRGBA) Option {
return func(cnf *config) {
cnf.StatusColor = &color
return func(_ unit.Metric, cnf *Config) {
cnf.StatusColor = color
}
}
// NavigationColor sets the color of the navigation bar on Android, or the address bar in browsers.
func NavigationColor(color color.NRGBA) Option {
return func(cnf *config) {
cnf.NavigationColor = &color
return func(_ unit.Metric, cnf *Config) {
cnf.NavigationColor = color
}
}
// CustomRenderer controls whether the the window contents is
// CustomRenderer controls whether the window contents is
// rendered by the client. If true, no GPU context is created.
func CustomRenderer(custom bool) Option {
return func(cnf *config) {
return func(_ unit.Metric, cnf *Config) {
cnf.CustomRenderer = custom
}
}