gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/window.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package app
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"image"
     9  	"time"
    10  
    11  	"gioui.org/ui"
    12  	"gioui.org/ui/app/internal/gpu"
    13  	"gioui.org/ui/app/internal/input"
    14  	"gioui.org/ui/system"
    15  )
    16  
    17  // WindowOption configures a Window.
    18  type WindowOption struct {
    19  	apply func(opts *windowOptions)
    20  }
    21  
    22  type windowOptions struct {
    23  	Width, Height ui.Value
    24  	Title         string
    25  }
    26  
    27  // Window represents an operating system window.
    28  type Window struct {
    29  	driver    *window
    30  	lastFrame time.Time
    31  	drawStart time.Time
    32  	gpu       *gpu.GPU
    33  
    34  	out         chan ui.Event
    35  	in          chan ui.Event
    36  	ack         chan struct{}
    37  	invalidates chan struct{}
    38  	frames      chan *ui.Ops
    39  
    40  	stage        Stage
    41  	animating    bool
    42  	hasNextFrame bool
    43  	nextFrame    time.Time
    44  	delayedDraw  *time.Timer
    45  
    46  	queue Queue
    47  }
    48  
    49  // Queue is an ui.Queue implementation that distributes system events
    50  // to the input handlers declared in the most recent call to Update.
    51  type Queue struct {
    52  	q input.Router
    53  }
    54  
    55  // driverEvent is sent when a new native driver
    56  // is available for the Window.
    57  type driverEvent struct {
    58  	driver *window
    59  }
    60  
    61  // driver is the interface for the platform implementation
    62  // of a Window.
    63  var _ interface {
    64  	// setAnimating sets the animation flag. When the window is animating,
    65  	// UpdateEvents are delivered as fast as the display can handle them.
    66  	setAnimating(anim bool)
    67  	// showTextInput updates the virtual keyboard state.
    68  	showTextInput(show bool)
    69  } = (*window)(nil)
    70  
    71  // Pre-allocate the ack event to avoid garbage.
    72  var ackEvent ui.Event
    73  
    74  // NewWindow creates a new window for a set of window
    75  // options. The options are hints; the platform is free to
    76  // ignore or adjust them.
    77  //
    78  // If opts are nil, a set of sensible defaults are used.
    79  //
    80  // If the current program is running on iOS and Android,
    81  // NewWindow returns the window previously created by the
    82  // platform.
    83  //
    84  // BUG: Calling NewWindow more than once is not yet supported.
    85  func NewWindow(options ...WindowOption) *Window {
    86  	opts := &windowOptions{
    87  		Width:  ui.Dp(800),
    88  		Height: ui.Dp(600),
    89  		Title:  "Gio",
    90  	}
    91  
    92  	for _, o := range options {
    93  		o.apply(opts)
    94  	}
    95  
    96  	w := &Window{
    97  		in:          make(chan ui.Event),
    98  		out:         make(chan ui.Event),
    99  		ack:         make(chan struct{}),
   100  		invalidates: make(chan struct{}, 1),
   101  		frames:      make(chan *ui.Ops),
   102  	}
   103  	go w.run(opts)
   104  	return w
   105  }
   106  
   107  // Events returns the channel where events are delivered.
   108  func (w *Window) Events() <-chan ui.Event {
   109  	return w.out
   110  }
   111  
   112  // Queue returns the Window's event queue. The queue contains
   113  // the events received since the last UpdateEvent.
   114  func (w *Window) Queue() *Queue {
   115  	return &w.queue
   116  }
   117  
   118  // Update updates the Window. Paint operations updates the
   119  // window contents, input operations declare input handlers,
   120  // and so on. The supplied operations list completely replaces
   121  // the window state from previous calls.
   122  func (w *Window) Update(frame *ui.Ops) {
   123  	w.frames <- frame
   124  }
   125  
   126  func (w *Window) draw(size image.Point, frame *ui.Ops) {
   127  	var drawDur time.Duration
   128  	if !w.drawStart.IsZero() {
   129  		drawDur = time.Since(w.drawStart)
   130  		w.drawStart = time.Time{}
   131  	}
   132  	w.gpu.Draw(w.queue.q.Profiling(), size, frame)
   133  	w.queue.q.Frame(frame)
   134  	now := time.Now()
   135  	switch w.queue.q.TextInputState() {
   136  	case input.TextInputOpen:
   137  		w.driver.showTextInput(true)
   138  	case input.TextInputClose:
   139  		w.driver.showTextInput(false)
   140  	}
   141  	frameDur := now.Sub(w.lastFrame)
   142  	frameDur = frameDur.Truncate(100 * time.Microsecond)
   143  	w.lastFrame = now
   144  	if w.queue.q.Profiling() {
   145  		q := 100 * time.Microsecond
   146  		timings := fmt.Sprintf("tot:%7s cpu:%7s %s", frameDur.Round(q), drawDur.Round(q), w.gpu.Timings())
   147  		w.queue.q.AddProfile(system.ProfileEvent{Timings: timings})
   148  		w.setNextFrame(time.Time{})
   149  	}
   150  	if t, ok := w.queue.q.WakeupTime(); ok {
   151  		w.setNextFrame(t)
   152  	}
   153  	w.updateAnimation()
   154  }
   155  
   156  // Invalidate the window such that a UpdateEvent will be generated
   157  // immediately. If the window is inactive, the event is sent when the
   158  // window becomes active.
   159  // Invalidate is safe for concurrent use.
   160  func (w *Window) Invalidate() {
   161  	select {
   162  	case w.invalidates <- struct{}{}:
   163  	default:
   164  	}
   165  }
   166  
   167  func (w *Window) updateAnimation() {
   168  	animate := false
   169  	if w.delayedDraw != nil {
   170  		w.delayedDraw.Stop()
   171  		w.delayedDraw = nil
   172  	}
   173  	if w.stage >= StageRunning && w.hasNextFrame {
   174  		if dt := time.Until(w.nextFrame); dt <= 0 {
   175  			animate = true
   176  		} else {
   177  			w.delayedDraw = time.NewTimer(dt)
   178  		}
   179  	}
   180  	if animate != w.animating {
   181  		w.animating = animate
   182  		w.driver.setAnimating(animate)
   183  	}
   184  }
   185  
   186  func (w *Window) setNextFrame(at time.Time) {
   187  	if !w.hasNextFrame || at.Before(w.nextFrame) {
   188  		w.hasNextFrame = true
   189  		w.nextFrame = at
   190  	}
   191  }
   192  
   193  func (w *Window) setDriver(d *window) {
   194  	w.event(driverEvent{d})
   195  }
   196  
   197  func (w *Window) event(e ui.Event) {
   198  	w.in <- e
   199  	<-w.ack
   200  }
   201  
   202  func (w *Window) waitAck() {
   203  	// Send a dummy event; when it gets through we
   204  	// know the application has processed the previous event.
   205  	w.out <- ackEvent
   206  }
   207  
   208  // Prematurely destroy the window and wait for the native window
   209  // destroy event.
   210  func (w *Window) destroy(err error) {
   211  	// Ack the current event.
   212  	w.ack <- struct{}{}
   213  	w.out <- DestroyEvent{err}
   214  	for e := range w.in {
   215  		w.ack <- struct{}{}
   216  		if _, ok := e.(DestroyEvent); ok {
   217  			return
   218  		}
   219  	}
   220  }
   221  
   222  func (w *Window) run(opts *windowOptions) {
   223  	defer close(w.in)
   224  	defer close(w.out)
   225  	if err := createWindow(w, opts); err != nil {
   226  		w.out <- DestroyEvent{err}
   227  		return
   228  	}
   229  	for {
   230  		var timer <-chan time.Time
   231  		if w.delayedDraw != nil {
   232  			timer = w.delayedDraw.C
   233  		}
   234  		select {
   235  		case <-timer:
   236  			w.setNextFrame(time.Time{})
   237  			w.updateAnimation()
   238  		case <-w.invalidates:
   239  			w.setNextFrame(time.Time{})
   240  			w.updateAnimation()
   241  		case e := <-w.in:
   242  			switch e2 := e.(type) {
   243  			case StageEvent:
   244  				if w.gpu != nil {
   245  					if e2.Stage < StageRunning {
   246  						w.gpu.Release()
   247  						w.gpu = nil
   248  					} else {
   249  						w.gpu.Refresh()
   250  					}
   251  				}
   252  				w.stage = e2.Stage
   253  				w.updateAnimation()
   254  				w.out <- e
   255  				w.waitAck()
   256  			case UpdateEvent:
   257  				if e2.Size == (image.Point{}) {
   258  					panic(errors.New("internal error: zero-sized Draw"))
   259  				}
   260  				if w.stage < StageRunning {
   261  					// No drawing if not visible.
   262  					break
   263  				}
   264  				w.drawStart = time.Now()
   265  				w.hasNextFrame = false
   266  				w.out <- e
   267  				var frame *ui.Ops
   268  				// Wait for either a frame or the ack event,
   269  				// which meant that the client didn't draw.
   270  				select {
   271  				case frame = <-w.frames:
   272  				case w.out <- ackEvent:
   273  				}
   274  				if w.gpu != nil {
   275  					if e2.sync {
   276  						w.gpu.Refresh()
   277  					}
   278  					if err := w.gpu.Flush(); err != nil {
   279  						w.gpu.Release()
   280  						w.gpu = nil
   281  						w.destroy(err)
   282  						return
   283  					}
   284  				} else {
   285  					ctx, err := newContext(w.driver)
   286  					if err != nil {
   287  						w.destroy(err)
   288  						return
   289  					}
   290  					w.gpu, err = gpu.NewGPU(ctx)
   291  					if err != nil {
   292  						w.destroy(err)
   293  						return
   294  					}
   295  				}
   296  				w.draw(e2.Size, frame)
   297  				if e2.sync {
   298  					if err := w.gpu.Flush(); err != nil {
   299  						w.gpu.Release()
   300  						w.gpu = nil
   301  						w.destroy(err)
   302  						return
   303  					}
   304  				}
   305  			case *CommandEvent:
   306  				w.out <- e
   307  				w.waitAck()
   308  			case driverEvent:
   309  				w.driver = e2.driver
   310  			case DestroyEvent:
   311  				w.out <- e2
   312  				w.ack <- struct{}{}
   313  				return
   314  			case ui.Event:
   315  				if w.queue.q.Add(e2) {
   316  					w.setNextFrame(time.Time{})
   317  					w.updateAnimation()
   318  				}
   319  				w.out <- e
   320  			}
   321  			w.ack <- struct{}{}
   322  		}
   323  	}
   324  }
   325  
   326  func (q *Queue) Events(k ui.Key) []ui.Event {
   327  	return q.q.Events(k)
   328  }
   329  
   330  // WithTitle returns an option that sets the window title.
   331  func WithTitle(t string) WindowOption {
   332  	return WindowOption{
   333  		apply: func(opts *windowOptions) {
   334  			opts.Title = t
   335  		},
   336  	}
   337  }
   338  
   339  // WithWidth returns an option that sets the window width.
   340  func WithWidth(w ui.Value) WindowOption {
   341  	if w.V <= 0 {
   342  		panic("width must be larger than or equal to 0")
   343  	}
   344  	return WindowOption{
   345  		apply: func(opts *windowOptions) {
   346  			opts.Width = w
   347  		},
   348  	}
   349  }
   350  
   351  // WithHeight returns an option that sets the window height.
   352  func WithHeight(h ui.Value) WindowOption {
   353  	if h.V <= 0 {
   354  		panic("height must be larger than or equal to 0")
   355  	}
   356  	return WindowOption{
   357  		apply: func(opts *windowOptions) {
   358  			opts.Height = h
   359  		},
   360  	}
   361  }
   362  
   363  func (driverEvent) ImplementsEvent() {}