// SPDX-License-Identifier: Unlicense OR MIT //go:build darwin && ios // +build darwin,ios package app /* #cgo CFLAGS: -DGLES_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c #include #include #include __attribute__ ((visibility ("hidden"))) int gio_applicationMain(int argc, char *argv[]); __attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle); struct drawParams { CGFloat dpi, sdpi; CGFloat width, height; CGFloat top, right, bottom, left; }; static void writeClipboard(unichar *chars, NSUInteger length) { #if !TARGET_OS_TV @autoreleasepool { NSString *s = [NSString string]; if (length > 0) { s = [NSString stringWithCharacters:chars length:length]; } UIPasteboard *p = UIPasteboard.generalPasteboard; p.string = s; } #endif } static CFTypeRef readClipboard(void) { #if !TARGET_OS_TV @autoreleasepool { UIPasteboard *p = UIPasteboard.generalPasteboard; return (__bridge_retained CFTypeRef)p.string; } #else return nil; #endif } static void showTextInput(CFTypeRef viewRef) { UIView *view = (__bridge UIView *)viewRef; [view becomeFirstResponder]; } static void hideTextInput(CFTypeRef viewRef) { UIView *view = (__bridge UIView *)viewRef; [view resignFirstResponder]; } static struct drawParams viewDrawParams(CFTypeRef viewRef) { UIView *v = (__bridge UIView *)viewRef; struct drawParams params; CGFloat scale = v.layer.contentsScale; // Use 163 as the standard ppi on iOS. params.dpi = 163*scale; params.sdpi = params.dpi; UIEdgeInsets insets = v.layoutMargins; if (@available(iOS 11.0, tvOS 11.0, *)) { UIFontMetrics *metrics = [UIFontMetrics defaultMetrics]; params.sdpi = [metrics scaledValueForValue:params.sdpi]; insets = v.safeAreaInsets; } params.width = v.bounds.size.width*scale; params.height = v.bounds.size.height*scale; params.top = insets.top*scale; params.right = insets.right*scale; params.bottom = insets.bottom*scale; params.left = insets.left*scale; return params; } */ import "C" import ( "image" "io" "os" "runtime" "runtime/cgo" "runtime/debug" "strings" "time" "unicode/utf16" "unsafe" "gioui.org/f32" "gioui.org/io/event" "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/system" "gioui.org/io/transfer" "gioui.org/op" "gioui.org/unit" ) type UIKitViewEvent struct { // ViewController is a CFTypeRef for the UIViewController backing a Window. ViewController uintptr } type window struct { view C.CFTypeRef w *callbacks displayLink *displayLink loop *eventLoop hidden bool cursor pointer.Cursor config Config pointerMap []C.CFTypeRef } var mainWindow = newWindowRendezvous() func init() { // Darwin requires UI operations happen on the main thread only. runtime.LockOSThread() } //export onCreate func onCreate(view, controller C.CFTypeRef) { wopts := <-mainWindow.out w := &window{ view: view, w: wopts.window, } w.loop = newEventLoop(w.w, w.wakeup) w.w.SetDriver(w) mainWindow.windows <- struct{}{} dl, err := newDisplayLink(func() { w.draw(false) }) if err != nil { w.w.ProcessEvent(DestroyEvent{Err: err}) return } w.displayLink = dl C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w))) w.Configure(wopts.options) w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)}) } func viewFor(h C.uintptr_t) *window { return cgo.Handle(h).Value().(*window) } //export gio_onDraw func gio_onDraw(h C.uintptr_t) { w := viewFor(h) w.draw(true) } func (w *window) draw(sync bool) { if w.hidden { return } params := C.viewDrawParams(w.view) if params.width == 0 || params.height == 0 { return } const inchPrDp = 1.0 / 163 m := unit.Metric{ PxPerDp: float32(params.dpi) * inchPrDp, PxPerSp: float32(params.sdpi) * inchPrDp, } dppp := unit.Dp(1. / m.PxPerDp) w.ProcessEvent(frameEvent{ FrameEvent: FrameEvent{ Now: time.Now(), Size: image.Point{ X: int(params.width + .5), Y: int(params.height + .5), }, Insets: Insets{ Top: unit.Dp(params.top) * dppp, Bottom: unit.Dp(params.bottom) * dppp, Left: unit.Dp(params.left) * dppp, Right: unit.Dp(params.right) * dppp, }, Metric: m, }, Sync: sync, }) } //export onStop func onStop(h C.uintptr_t) { w := viewFor(h) w.hidden = true } //export onStart func onStart(h C.uintptr_t) { w := viewFor(h) w.hidden = false w.draw(true) } //export onDestroy func onDestroy(h C.uintptr_t) { w := viewFor(h) w.ProcessEvent(UIKitViewEvent{}) w.ProcessEvent(DestroyEvent{}) w.displayLink.Close() w.displayLink = nil cgo.Handle(h).Delete() w.view = 0 } //export onFocus func onFocus(h C.uintptr_t, focus int) { w := viewFor(h) w.config.Focused = focus != 0 w.ProcessEvent(ConfigEvent{Config: w.config}) } //export onLowMemory func onLowMemory() { runtime.GC() debug.FreeOSMemory() } //export onUpArrow func onUpArrow(h C.uintptr_t) { viewFor(h).onKeyCommand(key.NameUpArrow) } //export onDownArrow func onDownArrow(h C.uintptr_t) { viewFor(h).onKeyCommand(key.NameDownArrow) } //export onLeftArrow func onLeftArrow(h C.uintptr_t) { viewFor(h).onKeyCommand(key.NameLeftArrow) } //export onRightArrow func onRightArrow(h C.uintptr_t) { viewFor(h).onKeyCommand(key.NameRightArrow) } //export onDeleteBackward func onDeleteBackward(h C.uintptr_t) { viewFor(h).onKeyCommand(key.NameDeleteBackward) } //export onText func onText(h C.uintptr_t, str C.CFTypeRef) { w := viewFor(h) w.w.EditorInsert(nsstringToString(str)) } //export onTouch func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) { var kind pointer.Kind switch phase { case C.UITouchPhaseBegan: kind = pointer.Press case C.UITouchPhaseMoved: kind = pointer.Move case C.UITouchPhaseEnded: kind = pointer.Release case C.UITouchPhaseCancelled: kind = pointer.Cancel default: return } w := viewFor(h) t := time.Duration(float64(ti) * float64(time.Second)) p := f32.Point{X: float32(x), Y: float32(y)} w.ProcessEvent(pointer.Event{ Kind: kind, Source: pointer.Touch, PointerID: w.lookupTouch(last != 0, touchRef), Position: p, Time: t, }) } func (w *window) ReadClipboard() { cstr := C.readClipboard() defer C.CFRelease(cstr) content := nsstringToString(cstr) w.ProcessEvent(transfer.DataEvent{ Type: "application/text", Open: func() io.ReadCloser { return io.NopCloser(strings.NewReader(content)) }, }) } func (w *window) WriteClipboard(mime string, s []byte) { u16 := utf16.Encode([]rune(string(s))) var chars *C.unichar if len(u16) > 0 { chars = (*C.unichar)(unsafe.Pointer(&u16[0])) } C.writeClipboard(chars, C.NSUInteger(len(u16))) } func (w *window) Configure([]Option) { // Decorations are never disabled. w.config.Decorated = true w.ProcessEvent(ConfigEvent{Config: w.config}) } func (w *window) EditorStateChanged(old, new editorState) {} func (w *window) Perform(system.Action) {} func (w *window) SetAnimating(anim bool) { if anim { w.displayLink.Start() } else { w.displayLink.Stop() } } func (w *window) SetCursor(cursor pointer.Cursor) { w.cursor = windowSetCursor(w.cursor, cursor) } func (w *window) onKeyCommand(name key.Name) { w.ProcessEvent(key.Event{ Name: name, }) } // lookupTouch maps an UITouch pointer value to an index. If // last is set, the map is cleared. func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID { id := -1 for i, ref := range w.pointerMap { if ref == touch { id = i break } } if id == -1 { id = len(w.pointerMap) w.pointerMap = append(w.pointerMap, touch) } if last { w.pointerMap = w.pointerMap[:0] } return pointer.ID(id) } func (w *window) contextView() C.CFTypeRef { return w.view } func (w *window) ShowTextInput(show bool) { if show { C.showTextInput(w.view) } else { C.hideTextInput(w.view) } } func (w *window) SetInputHint(_ key.InputHint) {} func (w *window) ProcessEvent(e event.Event) { w.w.ProcessEvent(e) w.loop.FlushEvents() } func (w *window) Event() event.Event { return w.loop.Event() } func (w *window) Invalidate() { w.loop.Invalidate() } func (w *window) Run(f func()) { w.loop.Run(f) } func (w *window) Frame(frame *op.Ops) { w.loop.Frame(frame) } func newWindow(win *callbacks, options []Option) { mainWindow.in <- windowAndConfig{win, options} <-mainWindow.windows } var mainMode = mainModeUndefined const ( mainModeUndefined = iota mainModeExe mainModeLibrary ) func osMain() { if !isMainThread() { panic("app.Main must be run on the main goroutine") } switch mainMode { case mainModeUndefined: mainMode = mainModeExe var argv []*C.char for _, arg := range os.Args { a := C.CString(arg) defer C.free(unsafe.Pointer(a)) argv = append(argv, a) } C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv)) case mainModeExe: panic("app.Main may be called only once") case mainModeLibrary: // Do nothing, we're embedded as a library. } } //export gio_runMain func gio_runMain() { if !isMainThread() { panic("app.Main must be run on the main goroutine") } switch mainMode { case mainModeUndefined: mainMode = mainModeLibrary runMain() case mainModeExe: // Do nothing, main has already been called. } } func (UIKitViewEvent) implementsViewEvent() {} func (UIKitViewEvent) ImplementsEvent() {} func (u UIKitViewEvent) Valid() bool { return u != (UIKitViewEvent{}) }