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