github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/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  	"github.com/gop9/olt/gio/app/internal/input"
    12  	"github.com/gop9/olt/gio/app/internal/window"
    13  	"github.com/gop9/olt/gio/io/event"
    14  	"github.com/gop9/olt/gio/io/profile"
    15  	"github.com/gop9/olt/gio/io/system"
    16  	"github.com/gop9/olt/gio/op"
    17  	"github.com/gop9/olt/gio/unit"
    18  
    19  	_ "github.com/gop9/olt/gio/app/internal/log"
    20  )
    21  
    22  // WindowOption configures a Window.
    23  type Option func(opts *window.Options)
    24  
    25  // Window represents an operating system window.
    26  type Window struct {
    27  	driver window.Driver
    28  	loop   *renderLoop
    29  
    30  	// driverFuncs is a channel of functions to run when
    31  	// the Window has a valid driver.
    32  	driverFuncs chan func()
    33  
    34  	out         chan event.Event
    35  	in          chan event.Event
    36  	ack         chan struct{}
    37  	invalidates chan struct{}
    38  	frames      chan *op.Ops
    39  	frameAck    chan struct{}
    40  
    41  	stage        system.Stage
    42  	animating    bool
    43  	hasNextFrame bool
    44  	nextFrame    time.Time
    45  	delayedDraw  *time.Timer
    46  
    47  	queue Queue
    48  
    49  	callbacks callbacks
    50  }
    51  
    52  type callbacks struct {
    53  	w *Window
    54  }
    55  
    56  // Queue is an event.Queue implementation that distributes system events
    57  // to the input handlers declared in the most recent frame.
    58  type Queue struct {
    59  	q input.Router
    60  }
    61  
    62  // driverEvent is sent when a new native driver
    63  // is available for the Window.
    64  type driverEvent struct {
    65  	driver window.Driver
    66  }
    67  
    68  // Pre-allocate the ack event to avoid garbage.
    69  var ackEvent event.Event
    70  
    71  // NewWindow creates a new window for a set of window
    72  // options. The options are hints; the platform is free to
    73  // ignore or adjust them.
    74  //
    75  // If opts are nil, a set of sensible defaults are used.
    76  //
    77  // If the current program is running on iOS and Android,
    78  // NewWindow returns the window previously created by the
    79  // platform.
    80  //
    81  // BUG: Calling NewWindow more than once is not yet supported.
    82  func NewWindow(options ...Option) *Window {
    83  	opts := &window.Options{
    84  		Width:  unit.Dp(800),
    85  		Height: unit.Dp(600),
    86  		Title:  "Gio",
    87  	}
    88  
    89  	for _, o := range options {
    90  		o(opts)
    91  	}
    92  
    93  	w := &Window{
    94  		in:          make(chan event.Event),
    95  		out:         make(chan event.Event),
    96  		ack:         make(chan struct{}),
    97  		invalidates: make(chan struct{}, 1),
    98  		frames:      make(chan *op.Ops),
    99  		frameAck:    make(chan struct{}),
   100  		driverFuncs: make(chan func()),
   101  	}
   102  	w.callbacks.w = w
   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 event.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 frame.
   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 *op.Ops) {
   123  	w.frames <- frame
   124  	<-w.frameAck
   125  }
   126  
   127  func (w *Window) draw(frameStart time.Time, size image.Point, frame *op.Ops) {
   128  	sync := w.loop.Draw(w.queue.q.Profiling(), size, frame)
   129  	w.queue.q.Frame(frame)
   130  	switch w.queue.q.TextInputState() {
   131  	case input.TextInputOpen:
   132  		w.driver.ShowTextInput(true)
   133  	case input.TextInputClose:
   134  		w.driver.ShowTextInput(false)
   135  	}
   136  	if w.queue.q.Profiling() {
   137  		frameDur := time.Since(frameStart)
   138  		frameDur = frameDur.Truncate(100 * time.Microsecond)
   139  		q := 100 * time.Microsecond
   140  		timings := fmt.Sprintf("tot:%7s %s", frameDur.Round(q), w.loop.Summary())
   141  		w.queue.q.AddProfile(profile.Event{Timings: timings})
   142  	}
   143  	if t, ok := w.queue.q.WakeupTime(); ok {
   144  		w.setNextFrame(t)
   145  	}
   146  	w.updateAnimation()
   147  	// Wait for the GPU goroutine to finish processing frame.
   148  	<-sync
   149  }
   150  
   151  // Invalidate the window such that a FrameEvent will be generated
   152  // immediately. If the window is inactive, the event is sent when the
   153  // window becomes active.
   154  // Invalidate is safe for concurrent use.
   155  func (w *Window) Invalidate() {
   156  	select {
   157  	case w.invalidates <- struct{}{}:
   158  	default:
   159  	}
   160  }
   161  
   162  func (w *Window) updateAnimation() {
   163  	animate := false
   164  	if w.delayedDraw != nil {
   165  		w.delayedDraw.Stop()
   166  		w.delayedDraw = nil
   167  	}
   168  	if w.stage >= system.StageRunning && w.hasNextFrame {
   169  		if dt := time.Until(w.nextFrame); dt <= 0 {
   170  			animate = true
   171  		} else {
   172  			w.delayedDraw = time.NewTimer(dt)
   173  		}
   174  	}
   175  	if animate != w.animating {
   176  		w.animating = animate
   177  		w.driver.SetAnimating(animate)
   178  	}
   179  }
   180  
   181  func (w *Window) setNextFrame(at time.Time) {
   182  	if !w.hasNextFrame || at.Before(w.nextFrame) {
   183  		w.hasNextFrame = true
   184  		w.nextFrame = at
   185  	}
   186  }
   187  
   188  func (c *callbacks) SetDriver(d window.Driver) {
   189  	c.Event(driverEvent{d})
   190  }
   191  
   192  func (c *callbacks) Event(e event.Event) {
   193  	c.w.in <- e
   194  	<-c.w.ack
   195  }
   196  
   197  func (w *Window) waitAck() {
   198  	// Send a dummy event; when it gets through we
   199  	// know the application has processed the previous event.
   200  	w.out <- ackEvent
   201  }
   202  
   203  // Prematurely destroy the window and wait for the native window
   204  // destroy event.
   205  func (w *Window) destroy(err error) {
   206  	w.destroyGPU()
   207  	// Ack the current event.
   208  	w.ack <- struct{}{}
   209  	w.out <- system.DestroyEvent{Err: err}
   210  	for e := range w.in {
   211  		w.ack <- struct{}{}
   212  		if _, ok := e.(system.DestroyEvent); ok {
   213  			return
   214  		}
   215  	}
   216  }
   217  
   218  func (w *Window) destroyGPU() {
   219  	if w.loop != nil {
   220  		w.loop.Release()
   221  		w.loop = nil
   222  	}
   223  }
   224  
   225  func (w *Window) run(opts *window.Options) {
   226  	defer close(w.in)
   227  	defer close(w.out)
   228  	if err := window.NewWindow(&w.callbacks, opts); err != nil {
   229  		w.out <- system.DestroyEvent{Err: err}
   230  		return
   231  	}
   232  	for {
   233  		var driverFuncs chan func() = nil
   234  		if w.driver != nil {
   235  			driverFuncs = w.driverFuncs
   236  		}
   237  		var timer <-chan time.Time
   238  		if w.delayedDraw != nil {
   239  			timer = w.delayedDraw.C
   240  		}
   241  		select {
   242  		case <-timer:
   243  			w.setNextFrame(time.Time{})
   244  			w.updateAnimation()
   245  		case <-w.invalidates:
   246  			w.setNextFrame(time.Time{})
   247  			w.updateAnimation()
   248  		case f := <-driverFuncs:
   249  			f()
   250  		case e := <-w.in:
   251  			switch e2 := e.(type) {
   252  			case system.StageEvent:
   253  				if w.loop != nil {
   254  					if e2.Stage < system.StageRunning {
   255  						w.loop.Release()
   256  						w.loop = nil
   257  					} else {
   258  						w.loop.Refresh()
   259  					}
   260  				}
   261  				w.stage = e2.Stage
   262  				w.updateAnimation()
   263  				w.out <- e
   264  				w.waitAck()
   265  			case window.FrameEvent:
   266  				if e2.Size == (image.Point{}) {
   267  					panic(errors.New("internal error: zero-sized Draw"))
   268  				}
   269  				if w.stage < system.StageRunning {
   270  					// No drawing if not visible.
   271  					break
   272  				}
   273  				frameStart := time.Now()
   274  				w.hasNextFrame = false
   275  				e2.Frame = w.update
   276  				w.out <- e2.FrameEvent
   277  				var err error
   278  				if w.loop != nil {
   279  					if e2.Sync {
   280  						w.loop.Refresh()
   281  					}
   282  					if err = w.loop.Flush(); err != nil {
   283  						w.loop.Release()
   284  						w.loop = nil
   285  					}
   286  				} else {
   287  					var ctx window.Context
   288  					ctx, err = w.driver.NewContext()
   289  					if err == nil {
   290  						w.loop, err = newLoop(ctx)
   291  						if err != nil {
   292  							ctx.Release()
   293  						}
   294  					}
   295  				}
   296  				var frame *op.Ops
   297  				// Wait for either a frame or an ack event to go
   298  				// through, which means that the client didn't give us
   299  				// a frame.
   300  				gotFrame := false
   301  				select {
   302  				case frame = <-w.frames:
   303  					gotFrame = true
   304  				case w.out <- ackEvent:
   305  				}
   306  				if err != nil {
   307  					if gotFrame {
   308  						w.frameAck <- struct{}{}
   309  					}
   310  					w.destroy(err)
   311  					return
   312  				}
   313  				w.draw(frameStart, e2.Size, frame)
   314  				if gotFrame {
   315  					// We're done with frame, let the client continue.
   316  					w.frameAck <- struct{}{}
   317  				}
   318  				if e2.Sync {
   319  					if err := w.loop.Flush(); err != nil {
   320  						w.loop.Release()
   321  						w.loop = nil
   322  						w.destroy(err)
   323  						return
   324  					}
   325  				}
   326  			case *system.CommandEvent:
   327  				w.out <- e
   328  				w.waitAck()
   329  			case driverEvent:
   330  				w.driver = e2.driver
   331  			case system.DestroyEvent:
   332  				w.destroyGPU()
   333  				w.out <- e2
   334  				w.ack <- struct{}{}
   335  				return
   336  			case event.Event:
   337  				if w.queue.q.Add(e2) {
   338  					w.setNextFrame(time.Time{})
   339  					w.updateAnimation()
   340  				}
   341  				w.out <- e
   342  			}
   343  			w.ack <- struct{}{}
   344  		}
   345  	}
   346  }
   347  
   348  func (q *Queue) Events(k event.Key) []event.Event {
   349  	return q.q.Events(k)
   350  }
   351  
   352  // Title sets the title of the window.
   353  func Title(t string) Option {
   354  	return func(opts *window.Options) {
   355  		opts.Title = t
   356  	}
   357  }
   358  
   359  // Size sets the size of the window.
   360  func Size(w, h unit.Value) Option {
   361  	if w.V <= 0 {
   362  		panic("width must be larger than or equal to 0")
   363  	}
   364  	if h.V <= 0 {
   365  		panic("height must be larger than or equal to 0")
   366  	}
   367  	return func(opts *window.Options) {
   368  		opts.Width = w
   369  		opts.Height = h
   370  	}
   371  }
   372  
   373  func (driverEvent) ImplementsEvent() {}