mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
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:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user