github.com/utopiagio/gio@v0.0.8/app/os_ios.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  //go:build darwin && ios
     4  // +build darwin,ios
     5  
     6  package app
     7  
     8  /*
     9  #cgo CFLAGS: -DGLES_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
    10  
    11  #include <CoreGraphics/CoreGraphics.h>
    12  #include <UIKit/UIKit.h>
    13  #include <stdint.h>
    14  
    15  __attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
    16  
    17  struct drawParams {
    18  	CGFloat dpi, sdpi;
    19  	CGFloat width, height;
    20  	CGFloat top, right, bottom, left;
    21  };
    22  
    23  static void writeClipboard(unichar *chars, NSUInteger length) {
    24  #if !TARGET_OS_TV
    25  	@autoreleasepool {
    26  		NSString *s = [NSString string];
    27  		if (length > 0) {
    28  			s = [NSString stringWithCharacters:chars length:length];
    29  		}
    30  		UIPasteboard *p = UIPasteboard.generalPasteboard;
    31  		p.string = s;
    32  	}
    33  #endif
    34  }
    35  
    36  static CFTypeRef readClipboard(void) {
    37  #if !TARGET_OS_TV
    38  	@autoreleasepool {
    39  		UIPasteboard *p = UIPasteboard.generalPasteboard;
    40  		return (__bridge_retained CFTypeRef)p.string;
    41  	}
    42  #else
    43  	return nil;
    44  #endif
    45  }
    46  
    47  static void showTextInput(CFTypeRef viewRef) {
    48  	UIView *view = (__bridge UIView *)viewRef;
    49  	[view becomeFirstResponder];
    50  }
    51  
    52  static void hideTextInput(CFTypeRef viewRef) {
    53  	UIView *view = (__bridge UIView *)viewRef;
    54  	[view resignFirstResponder];
    55  }
    56  
    57  static struct drawParams viewDrawParams(CFTypeRef viewRef) {
    58  	UIView *v = (__bridge UIView *)viewRef;
    59  	struct drawParams params;
    60  	CGFloat scale = v.layer.contentsScale;
    61  	// Use 163 as the standard ppi on iOS.
    62  	params.dpi = 163*scale;
    63  	params.sdpi = params.dpi;
    64  	UIEdgeInsets insets = v.layoutMargins;
    65  	if (@available(iOS 11.0, tvOS 11.0, *)) {
    66  		UIFontMetrics *metrics = [UIFontMetrics defaultMetrics];
    67  		params.sdpi = [metrics scaledValueForValue:params.sdpi];
    68  		insets = v.safeAreaInsets;
    69  	}
    70  	params.width = v.bounds.size.width*scale;
    71  	params.height = v.bounds.size.height*scale;
    72  	params.top = insets.top*scale;
    73  	params.right = insets.right*scale;
    74  	params.bottom = insets.bottom*scale;
    75  	params.left = insets.left*scale;
    76  	return params;
    77  }
    78  */
    79  import "C"
    80  
    81  import (
    82  	"image"
    83  	"io"
    84  	"runtime"
    85  	"runtime/cgo"
    86  	"runtime/debug"
    87  	"strings"
    88  	"time"
    89  	"unicode/utf16"
    90  	"unsafe"
    91  
    92  	"github.com/utopiagio/gio/f32"
    93  	"github.com/utopiagio/gio/io/event"
    94  	"github.com/utopiagio/gio/io/key"
    95  	"github.com/utopiagio/gio/io/pointer"
    96  	"github.com/utopiagio/gio/io/system"
    97  	"github.com/utopiagio/gio/io/transfer"
    98  	"github.com/utopiagio/gio/op"
    99  	"github.com/utopiagio/gio/unit"
   100  )
   101  
   102  type UIKitViewEvent struct {
   103  	// ViewController is a CFTypeRef for the UIViewController backing a Window.
   104  	ViewController uintptr
   105  }
   106  
   107  type window struct {
   108  	view        C.CFTypeRef
   109  	w           *callbacks
   110  	displayLink *displayLink
   111  	loop        *eventLoop
   112  
   113  	hidden bool
   114  	cursor pointer.Cursor
   115  	config Config
   116  
   117  	pointerMap []C.CFTypeRef
   118  }
   119  
   120  var mainWindow = newWindowRendezvous()
   121  
   122  func init() {
   123  	// Darwin requires UI operations happen on the main thread only.
   124  	runtime.LockOSThread()
   125  }
   126  
   127  //export onCreate
   128  func onCreate(view, controller C.CFTypeRef) {
   129  	wopts := <-mainWindow.out
   130  	w := &window{
   131  		view: view,
   132  		w:    wopts.window,
   133  	}
   134  	w.loop = newEventLoop(w.w, w.wakeup)
   135  	w.w.SetDriver(w)
   136  	mainWindow.windows <- struct{}{}
   137  	dl, err := newDisplayLink(func() {
   138  		w.draw(false)
   139  	})
   140  	if err != nil {
   141  		w.w.ProcessEvent(DestroyEvent{Err: err})
   142  		return
   143  	}
   144  	w.displayLink = dl
   145  	C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
   146  	w.Configure(wopts.options)
   147  	w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
   148  }
   149  
   150  func viewFor(h C.uintptr_t) *window {
   151  	return cgo.Handle(h).Value().(*window)
   152  }
   153  
   154  //export gio_onDraw
   155  func gio_onDraw(h C.uintptr_t) {
   156  	w := viewFor(h)
   157  	w.draw(true)
   158  }
   159  
   160  func (w *window) draw(sync bool) {
   161  	if w.hidden {
   162  		return
   163  	}
   164  	params := C.viewDrawParams(w.view)
   165  	if params.width == 0 || params.height == 0 {
   166  		return
   167  	}
   168  	const inchPrDp = 1.0 / 163
   169  	m := unit.Metric{
   170  		PxPerDp: float32(params.dpi) * inchPrDp,
   171  		PxPerSp: float32(params.sdpi) * inchPrDp,
   172  	}
   173  	dppp := unit.Dp(1. / m.PxPerDp)
   174  	w.ProcessEvent(frameEvent{
   175  		FrameEvent: FrameEvent{
   176  			Now: time.Now(),
   177  			Size: image.Point{
   178  				X: int(params.width + .5),
   179  				Y: int(params.height + .5),
   180  			},
   181  			Insets: Insets{
   182  				Top:    unit.Dp(params.top) * dppp,
   183  				Bottom: unit.Dp(params.bottom) * dppp,
   184  				Left:   unit.Dp(params.left) * dppp,
   185  				Right:  unit.Dp(params.right) * dppp,
   186  			},
   187  			Metric: m,
   188  		},
   189  		Sync: sync,
   190  	})
   191  }
   192  
   193  //export onStop
   194  func onStop(h C.uintptr_t) {
   195  	w := viewFor(h)
   196  	w.hidden = true
   197  }
   198  
   199  //export onStart
   200  func onStart(h C.uintptr_t) {
   201  	w := viewFor(h)
   202  	w.hidden = false
   203  	w.draw(true)
   204  }
   205  
   206  //export onDestroy
   207  func onDestroy(h C.uintptr_t) {
   208  	w := viewFor(h)
   209  	w.ProcessEvent(UIKitViewEvent{})
   210  	w.ProcessEvent(DestroyEvent{})
   211  	w.displayLink.Close()
   212  	w.displayLink = nil
   213  	cgo.Handle(h).Delete()
   214  	w.view = 0
   215  }
   216  
   217  //export onFocus
   218  func onFocus(h C.uintptr_t, focus int) {
   219  	w := viewFor(h)
   220  	w.config.Focused = focus != 0
   221  	w.ProcessEvent(ConfigEvent{Config: w.config})
   222  }
   223  
   224  //export onLowMemory
   225  func onLowMemory() {
   226  	runtime.GC()
   227  	debug.FreeOSMemory()
   228  }
   229  
   230  //export onUpArrow
   231  func onUpArrow(h C.uintptr_t) {
   232  	viewFor(h).onKeyCommand(key.NameUpArrow)
   233  }
   234  
   235  //export onDownArrow
   236  func onDownArrow(h C.uintptr_t) {
   237  	viewFor(h).onKeyCommand(key.NameDownArrow)
   238  }
   239  
   240  //export onLeftArrow
   241  func onLeftArrow(h C.uintptr_t) {
   242  	viewFor(h).onKeyCommand(key.NameLeftArrow)
   243  }
   244  
   245  //export onRightArrow
   246  func onRightArrow(h C.uintptr_t) {
   247  	viewFor(h).onKeyCommand(key.NameRightArrow)
   248  }
   249  
   250  //export onDeleteBackward
   251  func onDeleteBackward(h C.uintptr_t) {
   252  	viewFor(h).onKeyCommand(key.NameDeleteBackward)
   253  }
   254  
   255  //export onText
   256  func onText(h C.uintptr_t, str C.CFTypeRef) {
   257  	w := viewFor(h)
   258  	w.w.EditorInsert(nsstringToString(str))
   259  }
   260  
   261  //export onTouch
   262  func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
   263  	var kind pointer.Kind
   264  	switch phase {
   265  	case C.UITouchPhaseBegan:
   266  		kind = pointer.Press
   267  	case C.UITouchPhaseMoved:
   268  		kind = pointer.Move
   269  	case C.UITouchPhaseEnded:
   270  		kind = pointer.Release
   271  	case C.UITouchPhaseCancelled:
   272  		kind = pointer.Cancel
   273  	default:
   274  		return
   275  	}
   276  	w := viewFor(h)
   277  	t := time.Duration(float64(ti) * float64(time.Second))
   278  	p := f32.Point{X: float32(x), Y: float32(y)}
   279  	w.ProcessEvent(pointer.Event{
   280  		Kind:      kind,
   281  		Source:    pointer.Touch,
   282  		PointerID: w.lookupTouch(last != 0, touchRef),
   283  		Position:  p,
   284  		Time:      t,
   285  	})
   286  }
   287  
   288  func (w *window) ReadClipboard() {
   289  	cstr := C.readClipboard()
   290  	defer C.CFRelease(cstr)
   291  	content := nsstringToString(cstr)
   292  	w.ProcessEvent(transfer.DataEvent{
   293  		Type: "application/text",
   294  		Open: func() io.ReadCloser {
   295  			return io.NopCloser(strings.NewReader(content))
   296  		},
   297  	})
   298  }
   299  
   300  func (w *window) WriteClipboard(mime string, s []byte) {
   301  	u16 := utf16.Encode([]rune(string(s)))
   302  	var chars *C.unichar
   303  	if len(u16) > 0 {
   304  		chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
   305  	}
   306  	C.writeClipboard(chars, C.NSUInteger(len(u16)))
   307  }
   308  
   309  func (w *window) Configure([]Option) {
   310  	// Decorations are never disabled.
   311  	w.config.Decorated = true
   312  	w.ProcessEvent(ConfigEvent{Config: w.config})
   313  }
   314  
   315  func (w *window) EditorStateChanged(old, new editorState) {}
   316  
   317  func (w *window) Perform(system.Action) {}
   318  
   319  func (w *window) SetAnimating(anim bool) {
   320  	if anim {
   321  		w.displayLink.Start()
   322  	} else {
   323  		w.displayLink.Stop()
   324  	}
   325  }
   326  
   327  func (w *window) SetCursor(cursor pointer.Cursor) {
   328  	w.cursor = windowSetCursor(w.cursor, cursor)
   329  }
   330  
   331  func (w *window) onKeyCommand(name key.Name) {
   332  	w.ProcessEvent(key.Event{
   333  		Name: name,
   334  	})
   335  }
   336  
   337  // lookupTouch maps an UITouch pointer value to an index. If
   338  // last is set, the map is cleared.
   339  func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID {
   340  	id := -1
   341  	for i, ref := range w.pointerMap {
   342  		if ref == touch {
   343  			id = i
   344  			break
   345  		}
   346  	}
   347  	if id == -1 {
   348  		id = len(w.pointerMap)
   349  		w.pointerMap = append(w.pointerMap, touch)
   350  	}
   351  	if last {
   352  		w.pointerMap = w.pointerMap[:0]
   353  	}
   354  	return pointer.ID(id)
   355  }
   356  
   357  func (w *window) contextView() C.CFTypeRef {
   358  	return w.view
   359  }
   360  
   361  func (w *window) ShowTextInput(show bool) {
   362  	if show {
   363  		C.showTextInput(w.view)
   364  	} else {
   365  		C.hideTextInput(w.view)
   366  	}
   367  }
   368  
   369  func (w *window) SetInputHint(_ key.InputHint) {}
   370  
   371  func (w *window) ProcessEvent(e event.Event) {
   372  	w.w.ProcessEvent(e)
   373  	w.loop.FlushEvents()
   374  }
   375  
   376  func (w *window) Event() event.Event {
   377  	return w.loop.Event()
   378  }
   379  
   380  func (w *window) Invalidate() {
   381  	w.loop.Invalidate()
   382  }
   383  
   384  func (w *window) Run(f func()) {
   385  	w.loop.Run(f)
   386  }
   387  
   388  func (w *window) Frame(frame *op.Ops) {
   389  	w.loop.Frame(frame)
   390  }
   391  
   392  func newWindow(win *callbacks, options []Option) {
   393  	mainWindow.in <- windowAndConfig{win, options}
   394  	<-mainWindow.windows
   395  }
   396  
   397  func osMain() {
   398  }
   399  
   400  //export gio_runMain
   401  func gio_runMain() {
   402  	runMain()
   403  }
   404  
   405  func (UIKitViewEvent) implementsViewEvent() {}
   406  func (UIKitViewEvent) ImplementsEvent()     {}