ref: 5eef85f5cd1be18c1308f6726886bb9c61ff9601 gio/ui/app/window.go -rw-r--r-- 8.0 KiB View raw
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
// SPDX-License-Identifier: Unlicense OR MIT

package app

import (
	"errors"
	"fmt"
	"image"
	"time"

	"gioui.org/ui"
	"gioui.org/ui/app/internal/gpu"
	iinput "gioui.org/ui/app/internal/input"
	"gioui.org/ui/input"
	"gioui.org/ui/system"
)

// WindowOption configures a Window.
type WindowOption struct {
	apply func(opts *windowOptions)
}

type windowOptions struct {
	Width, Height ui.Value
	Title         string
}

// Window represents an operating system window.
type Window struct {
	driver    *window
	lastFrame time.Time
	drawStart time.Time
	gpu       *gpu.GPU

	out         chan input.Event
	in          chan input.Event
	ack         chan struct{}
	invalidates chan struct{}
	frames      chan *ui.Ops

	stage        Stage
	animating    bool
	hasNextFrame bool
	nextFrame    time.Time
	delayedDraw  *time.Timer

	queue Queue
}

// Queue is an input.Queue implementation that distributes
// system input events to the input handlers declared in the
// most recent call to Update.
type Queue struct {
	q iinput.Router
}

// driverEvent is sent when a new native driver
// is available for the Window.
type driverEvent struct {
	driver *window
}

// driver is the interface for the platform implementation
// of a Window.
var _ interface {
	// setAnimating sets the animation flag. When the window is animating,
	// UpdateEvents are delivered as fast as the display can handle them.
	setAnimating(anim bool)
	// showTextInput updates the virtual keyboard state.
	showTextInput(show bool)
} = (*window)(nil)

// Pre-allocate the ack event to avoid garbage.
var ackEvent input.Event

// NewWindow creates a new window for a set of window
// options. The options are hints; the platform is free to
// ignore or adjust them.
//
// If opts are nil, a set of sensible defaults are used.
//
// If the current program is running on iOS and Android,
// NewWindow returns the window previously created by the
// platform.
//
// BUG: Calling NewWindow more than once is not yet supported.
func NewWindow(options ...WindowOption) *Window {
	opts := &windowOptions{
		Width:  ui.Dp(800),
		Height: ui.Dp(600),
		Title:  "Gio",
	}

	for _, o := range options {
		o.apply(opts)
	}

	w := &Window{
		in:          make(chan input.Event),
		out:         make(chan input.Event),
		ack:         make(chan struct{}),
		invalidates: make(chan struct{}, 1),
		frames:      make(chan *ui.Ops),
	}
	go w.run(opts)
	return w
}

// Events returns the channel where events are delivered.
func (w *Window) Events() <-chan input.Event {
	return w.out
}

// Queue returns the Window's event queue. The queue contains
// the events received since the last UpdateEvent.
func (w *Window) Queue() *Queue {
	return &w.queue
}

// Update updates the Window. Paint operations updates the
// window contents, input operations declare input handlers,
// and so on. The supplied operations list completely replaces
// the window state from previous calls.
func (w *Window) Update(frame *ui.Ops) {
	w.frames <- frame
}

func (w *Window) draw(size image.Point, frame *ui.Ops) {
	var drawDur time.Duration
	if !w.drawStart.IsZero() {
		drawDur = time.Since(w.drawStart)
		w.drawStart = time.Time{}
	}
	w.gpu.Draw(w.queue.q.Profiling(), size, frame)
	w.queue.q.Frame(frame)
	now := time.Now()
	switch w.queue.q.TextInputState() {
	case iinput.TextInputOpen:
		w.driver.showTextInput(true)
	case iinput.TextInputClose:
		w.driver.showTextInput(false)
	}
	frameDur := now.Sub(w.lastFrame)
	frameDur = frameDur.Truncate(100 * time.Microsecond)
	w.lastFrame = now
	if w.queue.q.Profiling() {
		q := 100 * time.Microsecond
		timings := fmt.Sprintf("tot:%7s cpu:%7s %s", frameDur.Round(q), drawDur.Round(q), w.gpu.Timings())
		w.queue.q.AddProfile(system.ProfileEvent{Timings: timings})
		w.setNextFrame(time.Time{})
	}
	if t, ok := w.queue.q.WakeupTime(); ok {
		w.setNextFrame(t)
	}
	w.updateAnimation()
}

// Invalidate the window such that a UpdateEvent will be generated
// immediately. If the window is inactive, the event is sent when the
// window becomes active.
// Invalidate is safe for concurrent use.
func (w *Window) Invalidate() {
	select {
	case w.invalidates <- struct{}{}:
	default:
	}
}

func (w *Window) updateAnimation() {
	animate := false
	if w.delayedDraw != nil {
		w.delayedDraw.Stop()
		w.delayedDraw = nil
	}
	if w.stage >= StageRunning && w.hasNextFrame {
		if dt := time.Until(w.nextFrame); dt <= 0 {
			animate = true
		} else {
			w.delayedDraw = time.NewTimer(dt)
		}
	}
	if animate != w.animating {
		w.animating = animate
		w.driver.setAnimating(animate)
	}
}

func (w *Window) setNextFrame(at time.Time) {
	if !w.hasNextFrame || at.Before(w.nextFrame) {
		w.hasNextFrame = true
		w.nextFrame = at
	}
}

func (w *Window) setDriver(d *window) {
	w.event(driverEvent{d})
}

func (w *Window) event(e input.Event) {
	w.in <- e
	<-w.ack
}

func (w *Window) waitAck() {
	// Send a dummy event; when it gets through we
	// know the application has processed the previous event.
	w.out <- ackEvent
}

// Prematurely destroy the window and wait for the native window
// destroy event.
func (w *Window) destroy(err error) {
	// Ack the current event.
	w.ack <- struct{}{}
	w.out <- DestroyEvent{err}
	for e := range w.in {
		w.ack <- struct{}{}
		if _, ok := e.(DestroyEvent); ok {
			return
		}
	}
}

func (w *Window) run(opts *windowOptions) {
	defer close(w.in)
	defer close(w.out)
	if err := createWindow(w, opts); err != nil {
		w.out <- DestroyEvent{err}
		return
	}
	for {
		var timer <-chan time.Time
		if w.delayedDraw != nil {
			timer = w.delayedDraw.C
		}
		select {
		case <-timer:
			w.setNextFrame(time.Time{})
			w.updateAnimation()
		case <-w.invalidates:
			w.setNextFrame(time.Time{})
			w.updateAnimation()
		case e := <-w.in:
			switch e2 := e.(type) {
			case StageEvent:
				if w.gpu != nil {
					if e2.Stage < StageRunning {
						w.gpu.Release()
						w.gpu = nil
					} else {
						w.gpu.Refresh()
					}
				}
				w.stage = e2.Stage
				w.updateAnimation()
				w.out <- e
				w.waitAck()
			case UpdateEvent:
				if e2.Size == (image.Point{}) {
					panic(errors.New("internal error: zero-sized Draw"))
				}
				if w.stage < StageRunning {
					// No drawing if not visible.
					break
				}
				w.drawStart = time.Now()
				w.hasNextFrame = false
				w.out <- e
				var frame *ui.Ops
				// Wait for either a frame or the ack event,
				// which meant that the client didn't draw.
				select {
				case frame = <-w.frames:
				case w.out <- ackEvent:
				}
				if w.gpu != nil {
					if e2.sync {
						w.gpu.Refresh()
					}
					if err := w.gpu.Flush(); err != nil {
						w.gpu.Release()
						w.gpu = nil
						w.destroy(err)
						return
					}
				} else {
					ctx, err := newContext(w.driver)
					if err != nil {
						w.destroy(err)
						return
					}
					w.gpu, err = gpu.NewGPU(ctx)
					if err != nil {
						w.destroy(err)
						return
					}
				}
				w.draw(e2.Size, frame)
				if e2.sync {
					if err := w.gpu.Flush(); err != nil {
						w.gpu.Release()
						w.gpu = nil
						w.destroy(err)
						return
					}
				}
			case *CommandEvent:
				w.out <- e
				w.waitAck()
			case driverEvent:
				w.driver = e2.driver
			case DestroyEvent:
				w.out <- e2
				w.ack <- struct{}{}
				return
			case input.Event:
				if w.queue.q.Add(e2) {
					w.setNextFrame(time.Time{})
					w.updateAnimation()
				}
				w.out <- e
			}
			w.ack <- struct{}{}
		}
	}
}

func (q *Queue) Next(k input.Key) (input.Event, bool) {
	return q.q.Next(k)
}

// WithTitle returns an option that sets the window title.
func WithTitle(t string) WindowOption {
	return WindowOption{
		apply: func(opts *windowOptions) {
			opts.Title = t
		},
	}
}

// WithWidth returns an option that sets the window width.
func WithWidth(w ui.Value) WindowOption {
	if w.V <= 0 {
		panic("width must be larger than or equal to 0")
	}
	return WindowOption{
		apply: func(opts *windowOptions) {
			opts.Width = w
		},
	}
}

// WithHeight returns an option that sets the window height.
func WithHeight(h ui.Value) WindowOption {
	if h.V <= 0 {
		panic("height must be larger than or equal to 0")
	}
	return WindowOption{
		apply: func(opts *windowOptions) {
			opts.Height = h
		},
	}
}

func (driverEvent) ImplementsEvent() {}