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