window.go raw

   1  package gel
   2  
   3  import (
   4  	"math"
   5  	"os/exec"
   6  	"runtime"
   7  	"strconv"
   8  	"strings"
   9  	"time"
  10  
  11  	"github.com/p9c/opts/binary"
  12  	"github.com/p9c/opts/meta"
  13  
  14  	clipboard2 "github.com/p9c/gio/io/clipboard"
  15  
  16  	"github.com/p9c/gel/clipboard"
  17  	"github.com/p9c/gel/fonts/p9fonts"
  18  
  19  	"github.com/p9c/gio/io/event"
  20  
  21  	"github.com/p9c/qu"
  22  
  23  	uberatomic "go.uber.org/atomic"
  24  
  25  	"github.com/p9c/gio/app"
  26  	"github.com/p9c/gio/io/system"
  27  	l "github.com/p9c/gio/layout"
  28  	"github.com/p9c/gio/op"
  29  	"github.com/p9c/gio/unit"
  30  )
  31  
  32  type CallbackQueue chan func() error
  33  
  34  func NewCallbackQueue(bufSize int) CallbackQueue {
  35  	return make(CallbackQueue, bufSize)
  36  }
  37  
  38  type scaledConfig struct {
  39  	Scale float32
  40  }
  41  
  42  func (s *scaledConfig) Now() time.Time {
  43  	return time.Now()
  44  }
  45  
  46  func (s *scaledConfig) Px(v unit.Value) int {
  47  	scale := s.Scale
  48  	if v.U == unit.UnitPx {
  49  		scale = 1
  50  	}
  51  	return int(math.Round(float64(scale * v.V)))
  52  }
  53  
  54  type Window struct {
  55  	*Theme
  56  	*app.Window
  57  	opts                  []app.Option
  58  	scale                 *scaledConfig
  59  	Width                 *uberatomic.Int32 // stores the width at the beginning of render
  60  	Height                *uberatomic.Int32
  61  	ops                   op.Ops
  62  	evQ                   system.FrameEvent
  63  	Runner                CallbackQueue
  64  	overlay               []*func(gtx l.Context)
  65  	ClipboardWriteReqs    chan string
  66  	ClipboardReadReqs     chan func(string)
  67  	ClipboardContent      string
  68  	clipboardReadReady    qu.C
  69  	clipboardReadResponse chan string
  70  }
  71  
  72  func (w *Window) PushOverlay(overlay *func(gtx l.Context)) {
  73  	w.overlay = append(w.overlay, overlay)
  74  }
  75  
  76  func (w *Window) PopOverlay(overlay *func(gtx l.Context)) {
  77  	if len(w.overlay) == 0 {
  78  		return
  79  	}
  80  	index := -1
  81  	for i := range w.overlay {
  82  		if overlay == w.overlay[i] {
  83  			index = i
  84  			break
  85  		}
  86  	}
  87  	if index != -1 {
  88  		if index == len(w.overlay)-1 {
  89  			w.overlay = w.overlay[:index]
  90  		} else if index == 0 {
  91  			w.overlay = w.overlay[1:]
  92  		} else {
  93  			w.overlay = append(w.overlay[:index], w.overlay[index+1:]...)
  94  		}
  95  	}
  96  }
  97  
  98  func (w *Window) Overlay(gtx l.Context) {
  99  	for _, overlay := range w.overlay {
 100  		(*overlay)(gtx)
 101  	}
 102  }
 103  
 104  // NewWindowP9 creates a new window
 105  func NewWindowP9(quit chan struct{}) (out *Window) {
 106  	out = &Window{
 107  		scale:              &scaledConfig{1},
 108  		Runner:             NewCallbackQueue(32),
 109  		Width:              uberatomic.NewInt32(0),
 110  		Height:             uberatomic.NewInt32(0),
 111  		ClipboardWriteReqs: make(chan string, 1),
 112  		ClipboardReadReqs:  make(chan func(string), 32),
 113  		clipboardReadReady: qu.Ts(1),
 114  		clipboardReadResponse: make(chan string,1),
 115  	}
 116  	out.Theme = NewTheme(
 117  		binary.New(meta.Data{}, false, nil),
 118  		p9fonts.Collection(), quit,
 119  	)
 120  	out.Theme.WidgetPool = out.NewPool()
 121  	clipboard.Start()
 122  	return
 123  }
 124  
 125  // NewWindow creates a new window
 126  func NewWindow(th *Theme) (out *Window) {
 127  	out = &Window{
 128  		Theme: th,
 129  		scale: &scaledConfig{1},
 130  	}
 131  	return
 132  }
 133  
 134  // Title sets the title of the window
 135  func (w *Window) Title(title string) (out *Window) {
 136  	w.opts = append(w.opts, app.Title(title))
 137  	return w
 138  }
 139  
 140  // Size sets the dimensions of the window
 141  func (w *Window) Size(width, height float32) (out *Window) {
 142  	w.opts = append(
 143  		w.opts,
 144  		app.Size(w.TextSize.Scale(width), w.TextSize.Scale(height)),
 145  	)
 146  	return w
 147  }
 148  
 149  // Scale sets the scale factor for rendering
 150  func (w *Window) Scale(s float32) *Window {
 151  	w.scale = &scaledConfig{s}
 152  	return w
 153  }
 154  
 155  // Open sets the window options and initialise the node.window
 156  func (w *Window) Open() (out *Window) {
 157  	if w.scale == nil {
 158  		w.Scale(1)
 159  	}
 160  	if w.opts != nil {
 161  		w.Window = app.NewWindow(w.opts...)
 162  		w.opts = nil
 163  	}
 164  
 165  	return w
 166  }
 167  
 168  func (w *Window) Run(frame func(ctx l.Context) l.Dimensions, destroy func(), quit qu.C,) (e error) {
 169  	runner := func() {
 170  		ticker := time.NewTicker(time.Second)
 171  		for {
 172  			select {
 173  			case content := <-w.ClipboardWriteReqs:
 174  				w.WriteClipboard(content)
 175  			case fn := <-w.ClipboardReadReqs:
 176  				go func() {
 177  					w.ReadClipboard()
 178  					fn(<-w.clipboardReadResponse)
 179  				}()
 180  			case <-ticker.C:
 181  				if runtime.GOOS == "linux" {
 182  					var e error
 183  					var b []byte
 184  					textSize := unit.Sp(16)
 185  					runner := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor")
 186  					if b, e = runner.CombinedOutput(); D.Chk(e) {
 187  					}
 188  					var factor float64
 189  					numberString := strings.TrimSpace(string(b))
 190  					if factor, e = strconv.ParseFloat(numberString, 10); D.Chk(e) {
 191  					}
 192  					w.TextSize = textSize.Scale(float32(factor))
 193  					// I.Ln(w.TextSize)
 194  				}
 195  				w.Invalidate()
 196  			case fn := <-w.Runner:
 197  				if e = fn(); E.Chk(e) {
 198  					return
 199  				}
 200  			case <-quit.Wait():
 201  				return
 202  			// by repeating selectors we decrease the chance of a runner delaying
 203  			// a frame event hitting the physical frame deadline
 204  			case ev := <-w.Window.Events():
 205  				if e = w.processEvents(ev, frame, destroy); E.Chk(e) {
 206  					return
 207  				}
 208  			case ev := <-w.Window.Events():
 209  				if e = w.processEvents(ev, frame, destroy); E.Chk(e) {
 210  					return
 211  				}
 212  			case ev := <-w.Window.Events():
 213  				if e = w.processEvents(ev, frame, destroy); E.Chk(e) {
 214  					return
 215  				}
 216  			case ev := <-w.Window.Events():
 217  				if e = w.processEvents(ev, frame, destroy); E.Chk(e) {
 218  					return
 219  				}
 220  			case ev := <-w.Window.Events():
 221  				if e = w.processEvents(ev, frame, destroy); E.Chk(e) {
 222  					return
 223  				}
 224  			case ev := <-w.Window.Events():
 225  				if e = w.processEvents(ev, frame, destroy); E.Chk(e) {
 226  					return
 227  				}
 228  			case ev := <-w.Window.Events():
 229  				if e = w.processEvents(ev, frame, destroy); E.Chk(e) {
 230  					return
 231  				}
 232  			case ev := <-w.Window.Events():
 233  				if e = w.processEvents(ev, frame, destroy); E.Chk(e) {
 234  					return
 235  				}
 236  			case ev := <-w.Window.Events():
 237  				if e = w.processEvents(ev, frame, destroy); E.Chk(e) {
 238  					return
 239  				}
 240  			}
 241  		}
 242  	}
 243  	switch runtime.GOOS {
 244  	case "ios", "android":
 245  		go runner()
 246  	default:
 247  		runner()
 248  	}
 249  	return
 250  }
 251  
 252  func (w *Window) processEvents(e event.Event, frame func(ctx l.Context) l.Dimensions, destroy func()) error {
 253  	switch ev := e.(type) {
 254  	case system.DestroyEvent:
 255  		D.Ln("received destroy event", ev.Err)
 256  		destroy()
 257  		return ev.Err
 258  	case system.FrameEvent:
 259  		ops := op.Ops{}
 260  		c := l.NewContext(&ops, ev)
 261  		// update dimensions for responsive sizing widgets
 262  		w.Width.Store(int32(c.Constraints.Max.X))
 263  		w.Height.Store(int32(c.Constraints.Max.Y))
 264  		frame(c)
 265  		w.Overlay(c)
 266  		ev.Frame(c.Ops)
 267  	case clipboard2.Event:
 268  		// w.ClipboardContent = ev.Text
 269  		// w.clipboardReadReady.Signal()
 270  		w.clipboardReadResponse <- ev.Text
 271  	}
 272  	return nil
 273  }
 274