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