aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/app/window.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/app/window.go')
-rw-r--r--vendor/gioui.org/app/window.go734
1 files changed, 516 insertions, 218 deletions
diff --git a/vendor/gioui.org/app/window.go b/vendor/gioui.org/app/window.go
index 2c9860c..ada92fb 100644
--- a/vendor/gioui.org/app/window.go
+++ b/vendor/gioui.org/app/window.go
@@ -6,10 +6,14 @@ import (
"errors"
"fmt"
"image"
+ "image/color"
+ "runtime"
"time"
- "gioui.org/app/internal/window"
+ "gioui.org/f32"
+ "gioui.org/gpu"
"gioui.org/io/event"
+ "gioui.org/io/pointer"
"gioui.org/io/profile"
"gioui.org/io/router"
"gioui.org/io/system"
@@ -19,24 +23,32 @@ import (
_ "gioui.org/app/internal/log"
)
-// WindowOption configures a Window.
-type Option func(opts *window.Options)
+// Option configures a window.
+type Option func(unit.Metric, *Config)
// Window represents an operating system window.
type Window struct {
- driver window.Driver
- loop *renderLoop
+ ctx context
+ gpu gpu.GPU
// driverFuncs is a channel of functions to run when
// the Window has a valid driver.
- driverFuncs chan func()
-
- out chan event.Event
- in chan event.Event
- ack chan struct{}
- invalidates chan struct{}
- frames chan *op.Ops
- frameAck chan struct{}
+ driverFuncs chan func(d driver)
+ // wakeups wakes up the native event loop to send a
+ // WakeupEvent that flushes driverFuncs.
+ wakeups chan struct{}
+ // wakeupFuncs is sent wakeup functions when the driver changes.
+ wakeupFuncs chan func()
+ // redraws is notified when a redraw is requested by the client.
+ redraws chan struct{}
+ // immediateRedraws is like redraw but doesn't need a wakeup.
+ immediateRedraws chan struct{}
+ // scheduledRedraws is sent the most recent delayed redraw time.
+ scheduledRedraws chan time.Time
+
+ out chan event.Event
+ frames chan *op.Ops
+ frameAck chan struct{}
// dead is closed when the window is destroyed.
dead chan struct{}
@@ -46,13 +58,33 @@ type Window struct {
nextFrame time.Time
delayedDraw *time.Timer
- queue queue
+ queue queue
+ cursor pointer.CursorName
callbacks callbacks
+
+ nocontext bool
+
+ // semantic data, lazily evaluated if requested by a backend to speed up
+ // the cases where semantic data is not needed.
+ semantic struct {
+ // uptodate tracks whether the fields below are up to date.
+ uptodate bool
+ root router.SemanticID
+ prevTree []router.SemanticNode
+ tree []router.SemanticNode
+ ids map[router.SemanticID]router.SemanticNode
+ }
+}
+
+type semanticResult struct {
+ found bool
+ node router.SemanticNode
}
type callbacks struct {
w *Window
+ d driver
}
// queue is an event.Queue implementation that distributes system events
@@ -61,12 +93,6 @@ type queue struct {
q router.Router
}
-// driverEvent is sent when a new native driver
-// is available for the Window.
-type driverEvent struct {
- driver window.Driver
-}
-
// Pre-allocate the ack event to avoid garbage.
var ackEvent event.Event
@@ -74,35 +100,37 @@ var ackEvent event.Event
// options. The options are hints; the platform is free to
// ignore or adjust them.
//
-// If the current program is running on iOS and Android,
+// If the current program is running on iOS or Android,
// NewWindow returns the window previously created by the
// platform.
//
// Calling NewWindow more than once is not supported on
// iOS, Android, WebAssembly.
func NewWindow(options ...Option) *Window {
- opts := &window.Options{
- Width: unit.Dp(800),
- Height: unit.Dp(600),
- Title: "Gio",
- }
-
- for _, o := range options {
- o(opts)
+ 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),
- out: make(chan event.Event),
- ack: make(chan struct{}),
- invalidates: make(chan struct{}, 1),
- frames: make(chan *op.Ops),
- frameAck: make(chan struct{}),
- driverFuncs: make(chan func()),
- dead: make(chan struct{}),
+ out: make(chan event.Event),
+ immediateRedraws: make(chan struct{}, 0),
+ redraws: make(chan struct{}, 1),
+ scheduledRedraws: make(chan time.Time, 1),
+ frames: make(chan *op.Ops),
+ frameAck: make(chan struct{}),
+ driverFuncs: make(chan func(d driver), 1),
+ wakeups: make(chan struct{}, 1),
+ wakeupFuncs: make(chan func()),
+ dead: make(chan struct{}),
+ nocontext: cnf.CustomRenderer,
}
+ w.semantic.ids = make(map[router.SemanticID]router.SemanticNode)
w.callbacks.w = w
- go w.run(opts)
+ go w.run(options)
return w
}
@@ -111,141 +139,260 @@ func (w *Window) Events() <-chan event.Event {
return w.out
}
-// 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.
+// update 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 *op.Ops) {
w.frames <- frame
<-w.frameAck
}
-func (w *Window) validateAndProcess(frameStart time.Time, size image.Point, sync bool, frame *op.Ops) error {
+func (w *Window) validateAndProcess(d driver, frameStart time.Time, size image.Point, sync bool, frame *op.Ops) error {
for {
- if w.loop != nil {
- if err := w.loop.Flush(); err != nil {
+ if w.gpu == nil && !w.nocontext {
+ var err error
+ if w.ctx == nil {
+ w.ctx, err = d.NewContext()
+ if err != nil {
+ return err
+ }
+ sync = true
+ }
+ }
+ if sync && w.ctx != nil {
+ if err := w.ctx.Refresh(); err != nil {
+ if errors.Is(err, errOutOfDate) {
+ // Surface couldn't be created for transient reasons. Skip
+ // this frame and wait for the next.
+ return nil
+ }
w.destroyGPU()
- if err == window.ErrDeviceLost {
+ if errors.Is(err, gpu.ErrDeviceLost) {
continue
}
return err
}
}
- if w.loop == nil {
- var ctx window.Context
- ctx, err := w.driver.NewContext()
- if err != nil {
+ if w.gpu == nil && !w.nocontext {
+ if err := w.ctx.Lock(); err != nil {
+ w.destroyGPU()
return err
}
- w.loop, err = newLoop(ctx)
+ gpu, err := gpu.New(w.ctx.API())
+ w.ctx.Unlock()
if err != nil {
- ctx.Release()
+ w.destroyGPU()
return err
}
+ w.gpu = gpu
}
- w.processFrame(frameStart, size, frame)
- if sync {
- if err := w.loop.Flush(); err != nil {
+ if w.gpu != nil {
+ if err := w.render(frame, size); err != nil {
+ if errors.Is(err, errOutOfDate) {
+ // GPU surface needs refreshing.
+ sync = true
+ continue
+ }
w.destroyGPU()
- if err == window.ErrDeviceLost {
+ if errors.Is(err, gpu.ErrDeviceLost) {
continue
}
return err
}
}
+ w.processFrame(d, frameStart, frame)
return nil
}
}
-func (w *Window) processFrame(frameStart time.Time, size image.Point, frame *op.Ops) {
- sync := w.loop.Draw(size, frame)
+func (w *Window) render(frame *op.Ops, viewport image.Point) error {
+ if err := w.ctx.Lock(); err != nil {
+ return err
+ }
+ defer w.ctx.Unlock()
+ if runtime.GOOS == "js" {
+ // Use transparent black when Gio is embedded, to allow mixing of Gio and
+ // foreign content below.
+ w.gpu.Clear(color.NRGBA{A: 0x00, R: 0x00, G: 0x00, B: 0x00})
+ } else {
+ w.gpu.Clear(color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff})
+ }
+ target, err := w.ctx.RenderTarget()
+ if err != nil {
+ return err
+ }
+ if err := w.gpu.Frame(frame, target, viewport); err != nil {
+ return err
+ }
+ return w.ctx.Present()
+}
+
+func (w *Window) processFrame(d driver, frameStart time.Time, frame *op.Ops) {
w.queue.q.Frame(frame)
+ for k := range w.semantic.ids {
+ delete(w.semantic.ids, k)
+ }
+ w.semantic.uptodate = false
switch w.queue.q.TextInputState() {
case router.TextInputOpen:
- w.driver.ShowTextInput(true)
+ d.ShowTextInput(true)
case router.TextInputClose:
- w.driver.ShowTextInput(false)
+ d.ShowTextInput(false)
+ }
+ if hint, ok := w.queue.q.TextInputHint(); ok {
+ d.SetInputHint(hint)
+ }
+ if txt, ok := w.queue.q.WriteClipboard(); ok {
+ w.WriteClipboard(txt)
+ }
+ if w.queue.q.ReadClipboard() {
+ w.ReadClipboard()
}
- if w.queue.q.Profiling() {
+ if w.queue.q.Profiling() && w.gpu != nil {
frameDur := time.Since(frameStart)
frameDur = frameDur.Truncate(100 * time.Microsecond)
q := 100 * time.Microsecond
- timings := fmt.Sprintf("tot:%7s %s", frameDur.Round(q), w.loop.Summary())
- w.queue.q.Add(profile.Event{Timings: timings})
+ timings := fmt.Sprintf("tot:%7s %s", frameDur.Round(q), w.gpu.Profile())
+ w.queue.q.Queue(profile.Event{Timings: timings})
}
if t, ok := w.queue.q.WakeupTime(); ok {
w.setNextFrame(t)
}
- w.updateAnimation()
- // Wait for the GPU goroutine to finish processing frame.
- <-sync
+ w.updateAnimation(d)
}
-// Invalidate the window such that a FrameEvent will be generated
-// immediately. If the window is inactive, the event is sent when the
-// window becomes active.
+// Invalidate the window such that a FrameEvent will be generated immediately.
+// If the window is inactive, the event is sent when the window becomes active.
+//
+// Note that Invalidate is intended for externally triggered updates, such as a
+// response from a network request. InvalidateOp is more efficient for animation
+// and similar internal updates.
+//
// Invalidate is safe for concurrent use.
func (w *Window) Invalidate() {
select {
- case w.invalidates <- struct{}{}:
+ case w.immediateRedraws <- struct{}{}:
+ return
default:
}
+ select {
+ case w.redraws <- struct{}{}:
+ w.wakeup()
+ default:
+ }
+}
+
+// Option applies the options to the window.
+func (w *Window) Option(opts ...Option) {
+ w.driverDefer(func(d driver) {
+ d.Configure(opts)
+ })
}
// ReadClipboard initiates a read of the clipboard in the form
-// of a system.ClipboardEvent. Multiple reads may be coalescedd
+// of a clipboard.Event. Multiple reads may be coalesced
// to a single event.
func (w *Window) ReadClipboard() {
- w.driverDo(func() {
- w.driver.ReadClipboard()
+ w.driverDefer(func(d driver) {
+ d.ReadClipboard()
})
}
// WriteClipboard writes a string to the clipboard.
func (w *Window) WriteClipboard(s string) {
- w.driverDo(func() {
- w.driver.WriteClipboard(s)
+ w.driverDefer(func(d driver) {
+ d.WriteClipboard(s)
+ })
+}
+
+// SetCursorName changes the current window cursor to name.
+func (w *Window) SetCursorName(name pointer.CursorName) {
+ w.driverDefer(func(d driver) {
+ d.SetCursor(name)
})
}
// Close the window. The window's event loop should exit when it receives
// system.DestroyEvent.
//
-// Currently, only macOS, Windows and X11 drivers implement this functionality,
+// Currently, only macOS, Windows, X11 and Wayland drivers implement this functionality,
// all others are stubbed.
func (w *Window) Close() {
- w.driverDo(func() {
- w.driver.Close()
+ w.driverDefer(func(d driver) {
+ d.Close()
})
}
-// driverDo calls f as soon as the window has a valid driver attached,
-// or does nothing if the window is destroyed while waiting.
-func (w *Window) driverDo(f func()) {
- go func() {
- select {
- case w.driverFuncs <- f:
- case <-w.dead:
- }
- }()
+// Maximize the window.
+// Note: only implemented on Windows, macOS and X11.
+func (w *Window) Maximize() {
+ w.driverDefer(func(d driver) {
+ d.Maximize()
+ })
}
-func (w *Window) updateAnimation() {
- animate := false
- if w.delayedDraw != nil {
- w.delayedDraw.Stop()
- w.delayedDraw = nil
+// Center the window.
+// Note: only implemented on Windows, macOS and X11.
+func (w *Window) Center() {
+ w.driverDefer(func(d driver) {
+ d.Center()
+ })
+}
+
+// Run f in the same thread as the native window event loop, and wait for f to
+// return or the window to close. Run is guaranteed not to deadlock if it is
+// invoked during the handling of a ViewEvent, system.FrameEvent,
+// system.StageEvent; call Run in a separate goroutine to avoid deadlock in all
+// other cases.
+//
+// Note that most programs should not call Run; configuring a Window with
+// CustomRenderer is a notable exception.
+func (w *Window) Run(f func()) {
+ done := make(chan struct{})
+ w.driverDefer(func(d driver) {
+ defer close(done)
+ f()
+ })
+ select {
+ case <-done:
+ case <-w.dead:
}
+}
+
+// driverDefer is like Run but can be run from any context. It doesn't wait
+// for f to return.
+func (w *Window) driverDefer(f func(d driver)) {
+ select {
+ case w.driverFuncs <- f:
+ w.wakeup()
+ case <-w.dead:
+ }
+}
+
+func (w *Window) updateAnimation(d driver) {
+ animate := false
if w.stage >= system.StageRunning && w.hasNextFrame {
if dt := time.Until(w.nextFrame); dt <= 0 {
animate = true
} else {
- w.delayedDraw = time.NewTimer(dt)
+ // Schedule redraw.
+ select {
+ case <-w.scheduledRedraws:
+ default:
+ }
+ w.scheduledRedraws <- w.nextFrame
}
}
if animate != w.animating {
w.animating = animate
- w.driver.SetAnimating(animate)
+ d.SetAnimating(animate)
+ }
+}
+
+func (w *Window) wakeup() {
+ select {
+ case w.wakeups <- struct{}{}:
+ default:
}
}
@@ -256,164 +403,289 @@ func (w *Window) setNextFrame(at time.Time) {
}
}
-func (c *callbacks) SetDriver(d window.Driver) {
- c.Event(driverEvent{d})
+func (c *callbacks) SetDriver(d driver) {
+ c.d = d
+ var wakeup func()
+ if d != nil {
+ wakeup = d.Wakeup
+ }
+ c.w.wakeupFuncs <- wakeup
}
func (c *callbacks) Event(e event.Event) {
- select {
- case c.w.in <- e:
- <-c.w.ack
- case <-c.w.dead:
+ if c.d == nil {
+ panic("event while no driver active")
}
+ c.w.processEvent(c.d, e)
+ c.w.updateState(c.d)
+}
+
+// SemanticRoot returns the ID of the semantic root.
+func (c *callbacks) SemanticRoot() router.SemanticID {
+ c.w.updateSemantics()
+ return c.w.semantic.root
}
-func (w *Window) waitAck() {
- // Send a dummy event; when it gets through we
- // know the application has processed the previous event.
- w.out <- ackEvent
+// LookupSemantic looks up a semantic node from an ID. The zero ID denotes the root.
+func (c *callbacks) LookupSemantic(semID router.SemanticID) (router.SemanticNode, bool) {
+ c.w.updateSemantics()
+ n, found := c.w.semantic.ids[semID]
+ return n, found
}
-// Prematurely destroy the window and wait for the native window
-// destroy event.
-func (w *Window) destroy(err error) {
- w.destroyGPU()
- // Ack the current event.
- w.ack <- struct{}{}
- w.out <- system.DestroyEvent{Err: err}
- close(w.dead)
- for e := range w.in {
- w.ack <- struct{}{}
- if _, ok := e.(system.DestroyEvent); ok {
+func (c *callbacks) AppendSemanticDiffs(diffs []router.SemanticID) []router.SemanticID {
+ c.w.updateSemantics()
+ if tree := c.w.semantic.prevTree; len(tree) > 0 {
+ c.w.collectSemanticDiffs(&diffs, c.w.semantic.prevTree[0])
+ }
+ return diffs
+}
+
+func (c *callbacks) SemanticAt(pos f32.Point) (router.SemanticID, bool) {
+ c.w.updateSemantics()
+ return c.w.queue.q.SemanticAt(pos)
+}
+
+func (w *Window) waitAck(d driver) {
+ for {
+ select {
+ case f := <-w.driverFuncs:
+ f(d)
+ case w.out <- ackEvent:
+ // A dummy event went through, so we know the application has processed the previous event.
return
+ case <-w.immediateRedraws:
+ // Invalidate was called during frame processing.
+ w.setNextFrame(time.Time{})
}
}
}
func (w *Window) destroyGPU() {
- if w.loop != nil {
- w.loop.Release()
- w.loop = nil
+ if w.gpu != nil {
+ w.ctx.Lock()
+ w.gpu.Release()
+ w.ctx.Unlock()
+ w.gpu = nil
+ }
+ if w.ctx != nil {
+ w.ctx.Release()
+ w.ctx = nil
}
}
// waitFrame waits for the client to either call FrameEvent.Frame
// or to continue event handling. It returns whether the client
// called Frame or not.
-func (w *Window) waitFrame() (*op.Ops, bool) {
+func (w *Window) waitFrame(d driver) (*op.Ops, bool) {
+ for {
+ select {
+ case f := <-w.driverFuncs:
+ f(d)
+ case frame := <-w.frames:
+ // The client called FrameEvent.Frame.
+ return frame, true
+ case w.out <- ackEvent:
+ // The client ignored FrameEvent and continued processing
+ // events.
+ return nil, false
+ case <-w.immediateRedraws:
+ // Invalidate was called during frame processing.
+ w.setNextFrame(time.Time{})
+ }
+ }
+}
+
+// updateSemantics refreshes the semantics tree, the id to node map and the ids of
+// updated nodes.
+func (w *Window) updateSemantics() {
+ if w.semantic.uptodate {
+ return
+ }
+ w.semantic.uptodate = true
+ w.semantic.prevTree, w.semantic.tree = w.semantic.tree, w.semantic.prevTree
+ w.semantic.tree = w.queue.q.AppendSemantics(w.semantic.tree[:0])
+ w.semantic.root = w.semantic.tree[0].ID
+ for _, n := range w.semantic.tree {
+ w.semantic.ids[n.ID] = n
+ }
+}
+
+// collectSemanticDiffs traverses the previous semantic tree, noting changed nodes.
+func (w *Window) collectSemanticDiffs(diffs *[]router.SemanticID, n router.SemanticNode) {
+ newNode, exists := w.semantic.ids[n.ID]
+ // Ignore deleted nodes, as their disappearance will be reported through an
+ // ancestor node.
+ if !exists {
+ return
+ }
+ diff := newNode.Desc != n.Desc || len(n.Children) != len(newNode.Children)
+ for i, ch := range n.Children {
+ if !diff {
+ newCh := newNode.Children[i]
+ diff = ch.ID != newCh.ID
+ }
+ w.collectSemanticDiffs(diffs, ch)
+ }
+ if diff {
+ *diffs = append(*diffs, n.ID)
+ }
+}
+
+func (w *Window) updateState(d driver) {
+ for {
+ select {
+ case f := <-w.driverFuncs:
+ f(d)
+ case <-w.redraws:
+ w.setNextFrame(time.Time{})
+ w.updateAnimation(d)
+ default:
+ return
+ }
+ }
+}
+
+func (w *Window) processEvent(d driver, e event.Event) {
select {
- case frame := <-w.frames:
- // The client called FrameEvent.Frame.
- return frame, true
- case w.out <- ackEvent:
- // The client ignored FrameEvent and continued processing
- // events.
- return nil, false
+ case <-w.dead:
+ return
+ default:
+ }
+ switch e2 := e.(type) {
+ case system.StageEvent:
+ if e2.Stage < system.StageRunning {
+ if w.gpu != nil {
+ w.ctx.Lock()
+ w.gpu.Release()
+ w.gpu = nil
+ w.ctx.Unlock()
+ }
+ }
+ w.stage = e2.Stage
+ w.updateAnimation(d)
+ w.out <- e
+ w.waitAck(d)
+ case frameEvent:
+ if e2.Size == (image.Point{}) {
+ panic(errors.New("internal error: zero-sized Draw"))
+ }
+ if w.stage < system.StageRunning {
+ // No drawing if not visible.
+ break
+ }
+ frameStart := time.Now()
+ w.hasNextFrame = false
+ e2.Frame = w.update
+ e2.Queue = &w.queue
+ w.out <- e2.FrameEvent
+ frame, gotFrame := w.waitFrame(d)
+ err := w.validateAndProcess(d, frameStart, e2.Size, e2.Sync, frame)
+ if gotFrame {
+ // We're done with frame, let the client continue.
+ w.frameAck <- struct{}{}
+ }
+ if err != nil {
+ w.destroyGPU()
+ w.out <- system.DestroyEvent{Err: err}
+ close(w.dead)
+ close(w.out)
+ break
+ }
+ w.updateCursor()
+ case *system.CommandEvent:
+ w.out <- e
+ w.waitAck(d)
+ case system.DestroyEvent:
+ w.destroyGPU()
+ w.out <- e2
+ close(w.dead)
+ close(w.out)
+ case ViewEvent:
+ w.out <- e2
+ w.waitAck(d)
+ case wakeupEvent:
+ case event.Event:
+ if w.queue.q.Queue(e2) {
+ w.setNextFrame(time.Time{})
+ w.updateAnimation(d)
+ }
+ w.updateCursor()
+ w.out <- e
}
}
-func (w *Window) run(opts *window.Options) {
- defer close(w.in)
- defer close(w.out)
- if err := window.NewWindow(&w.callbacks, opts); err != nil {
+func (w *Window) run(options []Option) {
+ if err := newWindow(&w.callbacks, options); err != nil {
w.out <- system.DestroyEvent{Err: err}
+ close(w.dead)
+ close(w.out)
return
}
+ var wakeup func()
+ var timer *time.Timer
for {
- var driverFuncs chan func()
- if w.driver != nil {
- driverFuncs = w.driverFuncs
- }
- var timer <-chan time.Time
- if w.delayedDraw != nil {
- timer = w.delayedDraw.C
+ var (
+ wakeups <-chan struct{}
+ timeC <-chan time.Time
+ )
+ if wakeup != nil {
+ wakeups = w.wakeups
+ if timer != nil {
+ timeC = timer.C
+ }
}
select {
- case <-timer:
- w.setNextFrame(time.Time{})
- w.updateAnimation()
- case <-w.invalidates:
- w.setNextFrame(time.Time{})
- w.updateAnimation()
- case f := <-driverFuncs:
- f()
- case e := <-w.in:
- switch e2 := e.(type) {
- case system.StageEvent:
- if w.loop != nil {
- if e2.Stage < system.StageRunning {
- w.destroyGPU()
- } else {
- w.loop.Refresh()
- }
- }
- w.stage = e2.Stage
- w.updateAnimation()
- w.out <- e
- w.waitAck()
- case window.FrameEvent:
- if e2.Size == (image.Point{}) {
- panic(errors.New("internal error: zero-sized Draw"))
- }
- if w.stage < system.StageRunning {
- // No drawing if not visible.
- break
- }
- frameStart := time.Now()
- w.hasNextFrame = false
- e2.Frame = w.update
- e2.Queue = &w.queue
- w.out <- e2.FrameEvent
- if w.loop != nil {
- if e2.Sync {
- w.loop.Refresh()
- }
- }
- frame, gotFrame := w.waitFrame()
- err := w.validateAndProcess(frameStart, e2.Size, e2.Sync, frame)
- if gotFrame {
- // We're done with frame, let the client continue.
- w.frameAck <- struct{}{}
- }
- if err != nil {
- w.destroyGPU()
- w.destroy(err)
- return
- }
- case *system.CommandEvent:
- w.out <- e
- w.waitAck()
- case driverEvent:
- w.driver = e2.driver
- case system.DestroyEvent:
- w.destroyGPU()
- w.out <- e2
- w.ack <- struct{}{}
- return
- case event.Event:
- if w.queue.q.Add(e2) {
- w.setNextFrame(time.Time{})
- w.updateAnimation()
- }
- w.out <- e
+ case t := <-w.scheduledRedraws:
+ if timer != nil {
+ timer.Stop()
}
- w.ack <- struct{}{}
+ timer = time.NewTimer(time.Until(t))
+ case <-w.dead:
+ return
+ case <-timeC:
+ select {
+ case w.redraws <- struct{}{}:
+ wakeup()
+ default:
+ }
+ case <-wakeups:
+ wakeup()
+ case wakeup = <-w.wakeupFuncs:
}
}
}
+func (w *Window) updateCursor() {
+ if c := w.queue.q.Cursor(); c != w.cursor {
+ w.cursor = c
+ w.SetCursorName(c)
+ }
+}
+
+// Raise requests that the platform bring this window to the top of all open windows.
+// Some platforms do not allow this except under certain circumstances, such as when
+// a window from the same application already has focus. If the platform does not
+// support it, this method will do nothing.
+func (w *Window) Raise() {
+ w.driverDefer(func(d driver) {
+ d.Raise()
+ })
+}
+
func (q *queue) Events(k event.Tag) []event.Event {
return q.q.Events(k)
}
// Title sets the title of the window.
func Title(t string) Option {
- return func(opts *window.Options) {
- opts.Title = t
+ return func(_ unit.Metric, cnf *Config) {
+ cnf.Title = t
}
}
-// Size sets the size of the window.
+// Size sets the size of the window. The option is ignored
+// in Fullscreen mode.
func Size(w, h unit.Value) Option {
if w.V <= 0 {
panic("width must be larger than or equal to 0")
@@ -421,9 +693,11 @@ func Size(w, h unit.Value) Option {
if h.V <= 0 {
panic("height must be larger than or equal to 0")
}
- return func(opts *window.Options) {
- opts.Width = w
- opts.Height = h
+ return func(m unit.Metric, cnf *Config) {
+ cnf.Size = image.Point{
+ X: m.Px(w),
+ Y: m.Px(h),
+ }
}
}
@@ -435,9 +709,11 @@ func MaxSize(w, h unit.Value) Option {
if h.V <= 0 {
panic("height must be larger than or equal to 0")
}
- return func(opts *window.Options) {
- opts.MaxWidth = w
- opts.MaxHeight = h
+ return func(m unit.Metric, cnf *Config) {
+ cnf.MaxSize = image.Point{
+ X: m.Px(w),
+ Y: m.Px(h),
+ }
}
}
@@ -449,10 +725,32 @@ func MinSize(w, h unit.Value) Option {
if h.V <= 0 {
panic("height must be larger than or equal to 0")
}
- return func(opts *window.Options) {
- opts.MinWidth = w
- opts.MinHeight = h
+ return func(m unit.Metric, cnf *Config) {
+ cnf.MinSize = image.Point{
+ X: m.Px(w),
+ Y: m.Px(h),
+ }
}
}
-func (driverEvent) ImplementsEvent() {}
+// StatusColor sets the color of the Android status bar.
+func StatusColor(color color.NRGBA) Option {
+ 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(_ unit.Metric, cnf *Config) {
+ cnf.NavigationColor = color
+ }
+}
+
+// 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(_ unit.Metric, cnf *Config) {
+ cnf.CustomRenderer = custom
+ }
+}