app: defer window creation until Window.Event is called

We're moving towards making Window.Event, and in the future, Window.Events
create the window and drive the event loop to completion. In that model,
the other Window methods shouldn't create the window.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2024-05-30 09:14:48 +02:00
parent 24b0c2a4a1
commit a394b330e8
+55 -50
View File
@@ -9,7 +9,6 @@ import (
"image/color" "image/color"
"reflect" "reflect"
"runtime" "runtime"
"sync"
"time" "time"
"unicode/utf8" "unicode/utf8"
@@ -36,14 +35,14 @@ type Option func(unit.Metric, *Config)
// Window represents an operating system window. // Window represents an operating system window.
// //
// The zero-value Window is useful, and calling any method on // The zero-value Window is useful; the GUI window is created and shown the first
// it creates and shows a new GUI window. On iOS or Android, // time the [Event] method is called. On iOS or Android, the first Window represents
// the first Window represents the the window previously // the window previously created by the platform.
// created by the platform.
// //
// More than one Window is not supported on iOS, Android, // More than one Window is not supported on iOS, Android, WebAssembly.
// WebAssembly.
type Window struct { type Window struct {
initialOpts []Option
ctx context ctx context
gpu gpu.GPU gpu gpu.GPU
// timer tracks the delayed invalidate goroutine. // timer tracks the delayed invalidate goroutine.
@@ -91,7 +90,6 @@ type Window struct {
driver driver driver driver
// basic is the driver interface that is needed even after the window is gone. // basic is the driver interface that is needed even after the window is gone.
basic basicDriver basic basicDriver
once sync.Once
// coalesced tracks the most recent events waiting to be delivered // coalesced tracks the most recent events waiting to be delivered
// to the client. // to the client.
coalesced eventSummary coalesced eventSummary
@@ -273,8 +271,9 @@ func (w *Window) updateState() {
// //
// Invalidate is safe for concurrent use. // Invalidate is safe for concurrent use.
func (w *Window) Invalidate() { func (w *Window) Invalidate() {
w.init() if w.basic != nil {
w.basic.Invalidate() w.basic.Invalidate()
}
} }
// Option applies the options to the window. The options are hints; the platform is // Option applies the options to the window. The options are hints; the platform is
@@ -283,7 +282,10 @@ func (w *Window) Option(opts ...Option) {
if len(opts) == 0 { if len(opts) == 0 {
return return
} }
w.init(opts...) if w.basic == nil {
w.initialOpts = append(w.initialOpts, opts...)
return
}
w.Run(func() { w.Run(func() {
cnf := Config{Decorated: w.decorations.enabled} cnf := Config{Decorated: w.decorations.enabled}
for _, opt := range opts { for _, opt := range opts {
@@ -302,13 +304,14 @@ func (w *Window) Option(opts ...Option) {
} }
// Run f in the same thread as the native window event loop, and wait for f to // Run f in the same thread as the native window event loop, and wait for f to
// return or the window to close. // return or the window to close. If the window has not yet been created,
// Run calls f directly.
// //
// Note that most programs should not call Run; configuring a Window with // Note that most programs should not call Run; configuring a Window with
// [CustomRenderer] is a notable exception. // [CustomRenderer] is a notable exception.
func (w *Window) Run(f func()) { func (w *Window) Run(f func()) {
w.init()
if w.driver == nil { if w.driver == nil {
f()
return return
} }
done := make(chan struct{}) done := make(chan struct{})
@@ -680,48 +683,50 @@ func (w *Window) processEvent(e event.Event) bool {
} }
// Event blocks until an event is received from the window, such as // Event blocks until an event is received from the window, such as
// [FrameEvent], or until [Invalidate] is called. // [FrameEvent], or until [Invalidate] is called. The window is created
// and shown the first time Event is called.
func (w *Window) Event() event.Event { func (w *Window) Event() event.Event {
w.init() if w.basic == nil {
w.init()
}
return w.basic.Event() return w.basic.Event()
} }
func (w *Window) init(initial ...Option) { func (w *Window) init() {
w.once.Do(func() { debug.Parse()
debug.Parse() // Measure decoration height.
// Measure decoration height. deco := new(widget.Decorations)
deco := new(widget.Decorations) theme := material.NewTheme()
theme := material.NewTheme() theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular()))
theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular())) decoStyle := material.Decorations(theme, deco, 0, "")
decoStyle := material.Decorations(theme, deco, 0, "") gtx := layout.Context{
gtx := layout.Context{ Ops: new(op.Ops),
Ops: new(op.Ops), // Measure in Dp.
// Measure in Dp. Metric: unit.Metric{},
Metric: unit.Metric{}, }
} // Allow plenty of space.
// Allow plenty of space. gtx.Constraints.Max.Y = 200
gtx.Constraints.Max.Y = 200 dims := decoStyle.Layout(gtx)
dims := decoStyle.Layout(gtx) decoHeight := unit.Dp(dims.Size.Y)
decoHeight := unit.Dp(dims.Size.Y) defaultOptions := []Option{
defaultOptions := []Option{ Size(800, 600),
Size(800, 600), Title("Gio"),
Title("Gio"), Decorated(true),
Decorated(true), decoHeightOpt(decoHeight),
decoHeightOpt(decoHeight), }
} options := append(defaultOptions, w.initialOpts...)
options := append(defaultOptions, initial...) w.initialOpts = nil
var cnf Config var cnf Config
cnf.apply(unit.Metric{}, options) cnf.apply(unit.Metric{}, options)
w.nocontext = cnf.CustomRenderer w.nocontext = cnf.CustomRenderer
w.decorations.Theme = theme w.decorations.Theme = theme
w.decorations.Decorations = deco w.decorations.Decorations = deco
w.decorations.enabled = cnf.Decorated w.decorations.enabled = cnf.Decorated
w.decorations.height = decoHeight w.decorations.height = decoHeight
w.imeState.compose = key.Range{Start: -1, End: -1} w.imeState.compose = key.Range{Start: -1, End: -1}
w.semantic.ids = make(map[input.SemanticID]input.SemanticNode) w.semantic.ids = make(map[input.SemanticID]input.SemanticNode)
newWindow(&callbacks{w}, options) newWindow(&callbacks{w}, options)
})
} }
func (w *Window) updateCursor() { func (w *Window) updateCursor() {