forked from joejulian/gio
40bc2e1f88
ViewEvent exposes native window handle for platform specific uses. This change implements ViewEvent for iOS. Fixes gio#305 Signed-off-by: Inkeliz <inkeliz@inkeliz.com>
359 lines
7.4 KiB
Go
359 lines
7.4 KiB
Go
// 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 <CoreGraphics/CoreGraphics.h>
|
|
#include <UIKit/UIKit.h>
|
|
#include <stdint.h>
|
|
|
|
struct drawParams {
|
|
CGFloat dpi, sdpi;
|
|
CGFloat width, height;
|
|
CGFloat top, right, bottom, left;
|
|
};
|
|
|
|
static void writeClipboard(unichar *chars, NSUInteger length) {
|
|
@autoreleasepool {
|
|
NSString *s = [NSString string];
|
|
if (length > 0) {
|
|
s = [NSString stringWithCharacters:chars length:length];
|
|
}
|
|
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
|
p.string = s;
|
|
}
|
|
}
|
|
|
|
static CFTypeRef readClipboard(void) {
|
|
@autoreleasepool {
|
|
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
|
return (__bridge_retained CFTypeRef)p.string;
|
|
}
|
|
}
|
|
|
|
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"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"time"
|
|
"unicode/utf16"
|
|
"unsafe"
|
|
|
|
"gioui.org/f32"
|
|
"gioui.org/io/clipboard"
|
|
"gioui.org/io/key"
|
|
"gioui.org/io/pointer"
|
|
"gioui.org/io/system"
|
|
"gioui.org/unit"
|
|
)
|
|
|
|
type ViewEvent struct {
|
|
// View is a CFTypeRef for the UIView backing a Window.
|
|
View uintptr
|
|
}
|
|
|
|
type window struct {
|
|
view C.CFTypeRef
|
|
w *callbacks
|
|
displayLink *displayLink
|
|
|
|
visible bool
|
|
cursor pointer.CursorName
|
|
|
|
pointerMap []C.CFTypeRef
|
|
}
|
|
|
|
var mainWindow = newWindowRendezvous()
|
|
|
|
var views = make(map[C.CFTypeRef]*window)
|
|
|
|
func init() {
|
|
// Darwin requires UI operations happen on the main thread only.
|
|
runtime.LockOSThread()
|
|
}
|
|
|
|
//export onCreate
|
|
func onCreate(view C.CFTypeRef) {
|
|
w := &window{
|
|
view: view,
|
|
}
|
|
dl, err := NewDisplayLink(func() {
|
|
w.draw(false)
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
w.displayLink = dl
|
|
wopts := <-mainWindow.out
|
|
w.w = wopts.window
|
|
w.w.SetDriver(w)
|
|
views[view] = w
|
|
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
|
w.w.Event(ViewEvent{View: uintptr(view)})
|
|
}
|
|
|
|
//export gio_onDraw
|
|
func gio_onDraw(view C.CFTypeRef) {
|
|
w := views[view]
|
|
w.draw(true)
|
|
}
|
|
|
|
func (w *window) draw(sync bool) {
|
|
params := C.viewDrawParams(w.view)
|
|
if params.width == 0 || params.height == 0 {
|
|
return
|
|
}
|
|
wasVisible := w.visible
|
|
w.visible = true
|
|
if !wasVisible {
|
|
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
|
}
|
|
const inchPrDp = 1.0 / 163
|
|
w.w.Event(frameEvent{
|
|
FrameEvent: system.FrameEvent{
|
|
Now: time.Now(),
|
|
Size: image.Point{
|
|
X: int(params.width + .5),
|
|
Y: int(params.height + .5),
|
|
},
|
|
Insets: system.Insets{
|
|
Top: unit.Px(float32(params.top)),
|
|
Right: unit.Px(float32(params.right)),
|
|
Bottom: unit.Px(float32(params.bottom)),
|
|
Left: unit.Px(float32(params.left)),
|
|
},
|
|
Metric: unit.Metric{
|
|
PxPerDp: float32(params.dpi) * inchPrDp,
|
|
PxPerSp: float32(params.sdpi) * inchPrDp,
|
|
},
|
|
},
|
|
Sync: sync,
|
|
})
|
|
}
|
|
|
|
//export onStop
|
|
func onStop(view C.CFTypeRef) {
|
|
w := views[view]
|
|
w.visible = false
|
|
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
|
}
|
|
|
|
//export onDestroy
|
|
func onDestroy(view C.CFTypeRef) {
|
|
w := views[view]
|
|
delete(views, view)
|
|
w.w.Event(ViewEvent{})
|
|
w.w.Event(system.DestroyEvent{})
|
|
w.displayLink.Close()
|
|
w.view = 0
|
|
}
|
|
|
|
//export onFocus
|
|
func onFocus(view C.CFTypeRef, focus int) {
|
|
w := views[view]
|
|
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
|
}
|
|
|
|
//export onLowMemory
|
|
func onLowMemory() {
|
|
runtime.GC()
|
|
debug.FreeOSMemory()
|
|
}
|
|
|
|
//export onUpArrow
|
|
func onUpArrow(view C.CFTypeRef) {
|
|
views[view].onKeyCommand(key.NameUpArrow)
|
|
}
|
|
|
|
//export onDownArrow
|
|
func onDownArrow(view C.CFTypeRef) {
|
|
views[view].onKeyCommand(key.NameDownArrow)
|
|
}
|
|
|
|
//export onLeftArrow
|
|
func onLeftArrow(view C.CFTypeRef) {
|
|
views[view].onKeyCommand(key.NameLeftArrow)
|
|
}
|
|
|
|
//export onRightArrow
|
|
func onRightArrow(view C.CFTypeRef) {
|
|
views[view].onKeyCommand(key.NameRightArrow)
|
|
}
|
|
|
|
//export onDeleteBackward
|
|
func onDeleteBackward(view C.CFTypeRef) {
|
|
views[view].onKeyCommand(key.NameDeleteBackward)
|
|
}
|
|
|
|
//export onText
|
|
func onText(view C.CFTypeRef, str *C.char) {
|
|
w := views[view]
|
|
w.w.Event(key.EditEvent{
|
|
Text: C.GoString(str),
|
|
})
|
|
}
|
|
|
|
//export onTouch
|
|
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
|
var typ pointer.Type
|
|
switch phase {
|
|
case C.UITouchPhaseBegan:
|
|
typ = pointer.Press
|
|
case C.UITouchPhaseMoved:
|
|
typ = pointer.Move
|
|
case C.UITouchPhaseEnded:
|
|
typ = pointer.Release
|
|
case C.UITouchPhaseCancelled:
|
|
typ = pointer.Cancel
|
|
default:
|
|
return
|
|
}
|
|
w := views[view]
|
|
t := time.Duration(float64(ti) * float64(time.Second))
|
|
p := f32.Point{X: float32(x), Y: float32(y)}
|
|
w.w.Event(pointer.Event{
|
|
Type: typ,
|
|
Source: pointer.Touch,
|
|
PointerID: w.lookupTouch(last != 0, touchRef),
|
|
Position: p,
|
|
Time: t,
|
|
})
|
|
}
|
|
|
|
func (w *window) ReadClipboard() {
|
|
content := nsstringToString(C.readClipboard())
|
|
w.w.Event(clipboard.Event{Text: content})
|
|
}
|
|
|
|
func (w *window) WriteClipboard(s string) {
|
|
u16 := utf16.Encode([]rune(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) {}
|
|
|
|
func (w *window) Raise() {}
|
|
|
|
func (w *window) SetAnimating(anim bool) {
|
|
v := w.view
|
|
if v == 0 {
|
|
return
|
|
}
|
|
if anim {
|
|
w.displayLink.Start()
|
|
} else {
|
|
w.displayLink.Stop()
|
|
}
|
|
}
|
|
|
|
func (w *window) SetCursor(name pointer.CursorName) {
|
|
w.cursor = windowSetCursor(w.cursor, name)
|
|
}
|
|
|
|
func (w *window) onKeyCommand(name string) {
|
|
w.w.Event(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) {}
|
|
|
|
// Close the window. Not implemented for iOS.
|
|
func (w *window) Close() {}
|
|
|
|
// Maximize the window. Not implemented for iOS.
|
|
func (w *window) Maximize() {}
|
|
|
|
// Center the window. Not implemented for iOS.
|
|
func (w *window) Center() {}
|
|
|
|
func newWindow(win *callbacks, options []Option) error {
|
|
mainWindow.in <- windowAndConfig{win, options}
|
|
return <-mainWindow.errs
|
|
}
|
|
|
|
func osMain() {
|
|
}
|
|
|
|
//export gio_runMain
|
|
func gio_runMain() {
|
|
runMain()
|
|
}
|
|
|
|
func (_ ViewEvent) ImplementsEvent() {}
|