os_ios.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 // +build darwin,ios
4
5 package wm
6
7 /*
8 #cgo CFLAGS: -DGLES_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
9
10 #include <CoreGraphics/CoreGraphics.h>
11 #include <UIKit/UIKit.h>
12 #include <stdint.h>
13
14 struct drawParams {
15 CGFloat dpi, sdpi;
16 CGFloat width, height;
17 CGFloat top, right, bottom, left;
18 };
19
20 __attribute__ ((visibility ("hidden"))) void gio_showTextInput(CFTypeRef viewRef);
21 __attribute__ ((visibility ("hidden"))) void gio_hideTextInput(CFTypeRef viewRef);
22 __attribute__ ((visibility ("hidden"))) void gio_addLayerToView(CFTypeRef viewRef, CFTypeRef layerRef);
23 __attribute__ ((visibility ("hidden"))) void gio_updateView(CFTypeRef viewRef, CFTypeRef layerRef);
24 __attribute__ ((visibility ("hidden"))) void gio_removeLayer(CFTypeRef layerRef);
25 __attribute__ ((visibility ("hidden"))) struct drawParams gio_viewDrawParams(CFTypeRef viewRef);
26 __attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void);
27 __attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length);
28 */
29 import "C"
30
31 import (
32 "image"
33 "runtime"
34 "runtime/debug"
35 "sync/atomic"
36 "time"
37 "unicode/utf16"
38 "unsafe"
39
40 "github.com/p9c/p9/pkg/gel/gio/f32"
41 "github.com/p9c/p9/pkg/gel/gio/io/clipboard"
42 "github.com/p9c/p9/pkg/gel/gio/io/key"
43 "github.com/p9c/p9/pkg/gel/gio/io/pointer"
44 "github.com/p9c/p9/pkg/gel/gio/io/system"
45 "github.com/p9c/p9/pkg/gel/gio/unit"
46 )
47
48 type window struct {
49 view C.CFTypeRef
50 w Callbacks
51 displayLink *displayLink
52
53 layer C.CFTypeRef
54 visible atomic.Value
55 cursor pointer.CursorName
56
57 pointerMap []C.CFTypeRef
58 }
59
60 var mainWindow = newWindowRendezvous()
61
62 var layerFactory func() uintptr
63
64 var views = make(map[C.CFTypeRef]*window)
65
66 func init() {
67 // Darwin requires UI operations happen on the main thread only.
68 runtime.LockOSThread()
69 }
70
71 //export onCreate
72 func onCreate(view C.CFTypeRef) {
73 w := &window{
74 view: view,
75 }
76 dl, err := NewDisplayLink(func() {
77 w.draw(false)
78 })
79 if err != nil {
80 panic(err)
81 }
82 w.displayLink = dl
83 wopts := <-mainWindow.out
84 w.w = wopts.window
85 w.w.SetDriver(w)
86 w.visible.Store(false)
87 w.layer = C.CFTypeRef(layerFactory())
88 C.gio_addLayerToView(view, w.layer)
89 views[view] = w
90 w.w.Event(system.StageEvent{Stage: system.StagePaused})
91 }
92
93 //export gio_onDraw
94 func gio_onDraw(view C.CFTypeRef) {
95 w := views[view]
96 w.draw(true)
97 }
98
99 func (w *window) draw(sync bool) {
100 params := C.gio_viewDrawParams(w.view)
101 if params.width == 0 || params.height == 0 {
102 return
103 }
104 wasVisible := w.isVisible()
105 w.visible.Store(true)
106 C.gio_updateView(w.view, w.layer)
107 if !wasVisible {
108 w.w.Event(system.StageEvent{Stage: system.StageRunning})
109 }
110 const inchPrDp = 1.0 / 163
111 w.w.Event(FrameEvent{
112 FrameEvent: system.FrameEvent{
113 Now: time.Now(),
114 Size: image.Point{
115 X: int(params.width + .5),
116 Y: int(params.height + .5),
117 },
118 Insets: system.Insets{
119 Top: unit.Px(float32(params.top)),
120 Right: unit.Px(float32(params.right)),
121 Bottom: unit.Px(float32(params.bottom)),
122 Left: unit.Px(float32(params.left)),
123 },
124 Metric: unit.Metric{
125 PxPerDp: float32(params.dpi) * inchPrDp,
126 PxPerSp: float32(params.sdpi) * inchPrDp,
127 },
128 },
129 Sync: sync,
130 })
131 }
132
133 //export onStop
134 func onStop(view C.CFTypeRef) {
135 w := views[view]
136 w.visible.Store(false)
137 w.w.Event(system.StageEvent{Stage: system.StagePaused})
138 }
139
140 //export onDestroy
141 func onDestroy(view C.CFTypeRef) {
142 w := views[view]
143 delete(views, view)
144 w.w.Event(system.DestroyEvent{})
145 w.displayLink.Close()
146 C.gio_removeLayer(w.layer)
147 C.CFRelease(w.layer)
148 w.layer = 0
149 w.view = 0
150 }
151
152 //export onFocus
153 func onFocus(view C.CFTypeRef, focus int) {
154 w := views[view]
155 w.w.Event(key.FocusEvent{Focus: focus != 0})
156 }
157
158 //export onLowMemory
159 func onLowMemory() {
160 runtime.GC()
161 debug.FreeOSMemory()
162 }
163
164 //export onUpArrow
165 func onUpArrow(view C.CFTypeRef) {
166 views[view].onKeyCommand(key.NameUpArrow)
167 }
168
169 //export onDownArrow
170 func onDownArrow(view C.CFTypeRef) {
171 views[view].onKeyCommand(key.NameDownArrow)
172 }
173
174 //export onLeftArrow
175 func onLeftArrow(view C.CFTypeRef) {
176 views[view].onKeyCommand(key.NameLeftArrow)
177 }
178
179 //export onRightArrow
180 func onRightArrow(view C.CFTypeRef) {
181 views[view].onKeyCommand(key.NameRightArrow)
182 }
183
184 //export onDeleteBackward
185 func onDeleteBackward(view C.CFTypeRef) {
186 views[view].onKeyCommand(key.NameDeleteBackward)
187 }
188
189 //export onText
190 func onText(view C.CFTypeRef, str *C.char) {
191 w := views[view]
192 w.w.Event(key.EditEvent{
193 Text: C.GoString(str),
194 })
195 }
196
197 //export onTouch
198 func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
199 var typ pointer.Type
200 switch phase {
201 case C.UITouchPhaseBegan:
202 typ = pointer.Press
203 case C.UITouchPhaseMoved:
204 typ = pointer.Move
205 case C.UITouchPhaseEnded:
206 typ = pointer.Release
207 case C.UITouchPhaseCancelled:
208 typ = pointer.Cancel
209 default:
210 return
211 }
212 w := views[view]
213 t := time.Duration(float64(ti) * float64(time.Second))
214 p := f32.Point{X: float32(x), Y: float32(y)}
215 w.w.Event(pointer.Event{
216 Type: typ,
217 Source: pointer.Touch,
218 PointerID: w.lookupTouch(last != 0, touchRef),
219 Position: p,
220 Time: t,
221 })
222 }
223
224 func (w *window) ReadClipboard() {
225 runOnMain(func() {
226 content := nsstringToString(C.gio_readClipboard())
227 w.w.Event(clipboard.Event{Text: content})
228 })
229 }
230
231 func (w *window) WriteClipboard(s string) {
232 u16 := utf16.Encode([]rune(s))
233 runOnMain(func() {
234 var chars *C.unichar
235 if len(u16) > 0 {
236 chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
237 }
238 C.gio_writeClipboard(chars, C.NSUInteger(len(u16)))
239 })
240 }
241
242 func (w *window) Option(opts *Options) {}
243
244 func (w *window) SetAnimating(anim bool) {
245 v := w.view
246 if v == 0 {
247 return
248 }
249 if anim {
250 w.displayLink.Start()
251 } else {
252 w.displayLink.Stop()
253 }
254 }
255
256 func (w *window) SetCursor(name pointer.CursorName) {
257 w.cursor = windowSetCursor(w.cursor, name)
258 }
259
260 func (w *window) onKeyCommand(name string) {
261 w.w.Event(key.Event{
262 Name: name,
263 })
264 }
265
266 // lookupTouch maps an UITouch pointer value to an index. If
267 // last is set, the map is cleared.
268 func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID {
269 id := -1
270 for i, ref := range w.pointerMap {
271 if ref == touch {
272 id = i
273 break
274 }
275 }
276 if id == -1 {
277 id = len(w.pointerMap)
278 w.pointerMap = append(w.pointerMap, touch)
279 }
280 if last {
281 w.pointerMap = w.pointerMap[:0]
282 }
283 return pointer.ID(id)
284 }
285
286 func (w *window) contextLayer() uintptr {
287 return uintptr(w.layer)
288 }
289
290 func (w *window) isVisible() bool {
291 return w.visible.Load().(bool)
292 }
293
294 func (w *window) ShowTextInput(show bool) {
295 v := w.view
296 if v == 0 {
297 return
298 }
299 C.CFRetain(v)
300 runOnMain(func() {
301 defer C.CFRelease(v)
302 if show {
303 C.gio_showTextInput(w.view)
304 } else {
305 C.gio_hideTextInput(w.view)
306 }
307 })
308 }
309
310 // Close the window. Not implemented for iOS.
311 func (w *window) Close() {}
312
313 func NewWindow(win Callbacks, opts *Options) error {
314 mainWindow.in <- windowAndOptions{win, opts}
315 return <-mainWindow.errs
316 }
317
318 func Main() {
319 }
320
321 //export gio_runMain
322 func gio_runMain() {
323 runMain()
324 }
325