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