aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/app/os_macos.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/app/os_macos.go')
-rw-r--r--vendor/gioui.org/app/os_macos.go663
1 files changed, 663 insertions, 0 deletions
diff --git a/vendor/gioui.org/app/os_macos.go b/vendor/gioui.org/app/os_macos.go
new file mode 100644
index 0000000..270db6a
--- /dev/null
+++ b/vendor/gioui.org/app/os_macos.go
@@ -0,0 +1,663 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+//go:build darwin && !ios
+// +build darwin,!ios
+
+package app
+
+import (
+ "errors"
+ "image"
+ "runtime"
+ "time"
+ "unicode"
+ "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"
+
+ _ "gioui.org/internal/cocoainit"
+)
+
+/*
+#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
+
+#include <AppKit/AppKit.h>
+
+#define MOUSE_MOVE 1
+#define MOUSE_UP 2
+#define MOUSE_DOWN 3
+#define MOUSE_SCROLL 4
+
+__attribute__ ((visibility ("hidden"))) void gio_main(void);
+__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void);
+__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
+
+static void writeClipboard(unichar *chars, NSUInteger length) {
+ @autoreleasepool {
+ NSString *s = [NSString string];
+ if (length > 0) {
+ s = [NSString stringWithCharacters:chars length:length];
+ }
+ NSPasteboard *p = NSPasteboard.generalPasteboard;
+ [p declareTypes:@[NSPasteboardTypeString] owner:nil];
+ [p setString:s forType:NSPasteboardTypeString];
+ }
+}
+
+static CFTypeRef readClipboard(void) {
+ @autoreleasepool {
+ NSPasteboard *p = NSPasteboard.generalPasteboard;
+ NSString *content = [p stringForType:NSPasteboardTypeString];
+ return (__bridge_retained CFTypeRef)content;
+ }
+}
+
+static CGFloat viewHeight(CFTypeRef viewRef) {
+ NSView *view = (__bridge NSView *)viewRef;
+ return [view bounds].size.height;
+}
+
+static CGFloat viewWidth(CFTypeRef viewRef) {
+ NSView *view = (__bridge NSView *)viewRef;
+ return [view bounds].size.width;
+}
+
+static CGFloat getScreenBackingScale(void) {
+ return [NSScreen.mainScreen backingScaleFactor];
+}
+
+static CGFloat getViewBackingScale(CFTypeRef viewRef) {
+ NSView *view = (__bridge NSView *)viewRef;
+ return [view.window backingScaleFactor];
+}
+
+static void setNeedsDisplay(CFTypeRef viewRef) {
+ NSView *view = (__bridge NSView *)viewRef;
+ [view setNeedsDisplay:YES];
+}
+
+static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
+ NSWindow *window = (__bridge NSWindow *)windowRef;
+ return [window cascadeTopLeftFromPoint:topLeft];
+}
+
+static void makeKeyAndOrderFront(CFTypeRef windowRef) {
+ NSWindow *window = (__bridge NSWindow *)windowRef;
+ [window makeKeyAndOrderFront:nil];
+}
+
+static void toggleFullScreen(CFTypeRef windowRef) {
+ NSWindow *window = (__bridge NSWindow *)windowRef;
+ [window toggleFullScreen:nil];
+}
+
+static NSWindowStyleMask getWindowStyleMask(CFTypeRef windowRef) {
+ NSWindow *window = (__bridge NSWindow *)windowRef;
+ return [window styleMask];
+}
+
+static void closeWindow(CFTypeRef windowRef) {
+ NSWindow* window = (__bridge NSWindow *)windowRef;
+ [window performClose:nil];
+}
+
+static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
+ NSWindow* window = (__bridge NSWindow *)windowRef;
+ NSSize size = NSMakeSize(width, height);
+ [window setContentSize:size];
+}
+
+static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
+ NSWindow* window = (__bridge NSWindow *)windowRef;
+ window.contentMinSize = NSMakeSize(width, height);
+}
+
+static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
+ NSWindow* window = (__bridge NSWindow *)windowRef;
+ window.contentMaxSize = NSMakeSize(width, height);
+}
+
+static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
+ NSWindow* window = (__bridge NSWindow *)windowRef;
+ NSRect r = NSMakeRect(x, y, w, h);
+ [window setFrame:r display:YES];
+}
+
+static NSRect getScreenFrame(CFTypeRef windowRef) {
+ NSWindow* window = (__bridge NSWindow *)windowRef;
+ return [[window screen] frame];
+}
+
+static void setTitle(CFTypeRef windowRef, const char *title) {
+ NSWindow* window = (__bridge NSWindow *)windowRef;
+ window.title = [NSString stringWithUTF8String: title];
+}
+
+static CFTypeRef layerForView(CFTypeRef viewRef) {
+ NSView *view = (__bridge NSView *)viewRef;
+ return (__bridge CFTypeRef)view.layer;
+}
+
+static void raiseWindow(CFTypeRef windowRef) {
+ NSWindow* window = (__bridge NSWindow *)windowRef;
+ [window makeKeyAndOrderFront:nil];
+}
+
+*/
+import "C"
+
+func init() {
+ // Darwin requires that UI operations happen on the main thread only.
+ runtime.LockOSThread()
+}
+
+// ViewEvent notified the client of changes to the window AppKit handles.
+// The handles are retained until another ViewEvent is sent.
+type ViewEvent struct {
+ // View is a CFTypeRef for the NSView for the window.
+ View uintptr
+ // Layer is a CFTypeRef of the CALayer of View.
+ Layer uintptr
+}
+
+type window struct {
+ view C.CFTypeRef
+ window C.CFTypeRef
+ w *callbacks
+ stage system.Stage
+ displayLink *displayLink
+ cursor pointer.CursorName
+
+ scale float32
+ config Config
+}
+
+// viewMap is the mapping from Cocoa NSViews to Go windows.
+var viewMap = make(map[C.CFTypeRef]*window)
+
+// launched is closed when applicationDidFinishLaunching is called.
+var launched = make(chan struct{})
+
+// nextTopLeft is the offset to use for the next window's call to
+// cascadeTopLeftFromPoint.
+var nextTopLeft C.NSPoint
+
+// mustView is like lookupView, except that it panics
+// if the view isn't mapped.
+func mustView(view C.CFTypeRef) *window {
+ w, ok := lookupView(view)
+ if !ok {
+ panic("no window for view")
+ }
+ return w
+}
+
+func lookupView(view C.CFTypeRef) (*window, bool) {
+ w, exists := viewMap[view]
+ if !exists {
+ return nil, false
+ }
+ return w, true
+}
+
+func deleteView(view C.CFTypeRef) {
+ delete(viewMap, view)
+}
+
+func insertView(view C.CFTypeRef, w *window) {
+ viewMap[view] = w
+}
+
+func (w *window) contextView() C.CFTypeRef {
+ return w.view
+}
+
+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) updateWindowMode() {
+ style := int(C.getWindowStyleMask(w.window))
+ if style&C.NSWindowStyleMaskFullScreen > 0 {
+ w.config.Mode = Fullscreen
+ } else {
+ w.config.Mode = Windowed
+ }
+}
+
+func (w *window) Configure(options []Option) {
+ screenScale := float32(C.getScreenBackingScale())
+ cfg := configFor(screenScale)
+ prev := w.config
+ w.updateWindowMode()
+ cnf := w.config
+ cnf.apply(cfg, options)
+ cnf.Size = cnf.Size.Div(int(screenScale))
+ cnf.MinSize = cnf.MinSize.Div(int(screenScale))
+ cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
+
+ if cnf.Mode != Fullscreen && prev.Size != cnf.Size {
+ w.config.Size = cnf.Size
+ C.setSize(w.window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
+ }
+ if prev.MinSize != cnf.MinSize {
+ w.config.MinSize = cnf.MinSize
+ C.setMinSize(w.window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
+ }
+ if prev.MaxSize != cnf.MaxSize {
+ w.config.MaxSize = cnf.MaxSize
+ C.setMaxSize(w.window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y))
+ }
+
+ if prev.Title != cnf.Title {
+ w.config.Title = cnf.Title
+ title := C.CString(cnf.Title)
+ defer C.free(unsafe.Pointer(title))
+ C.setTitle(w.window, title)
+ }
+ if prev.Mode != cnf.Mode {
+ switch cnf.Mode {
+ case Windowed, Fullscreen:
+ w.config.Mode = cnf.Mode
+ C.toggleFullScreen(w.window)
+ }
+ }
+ if w.config != prev {
+ w.w.Event(ConfigEvent{Config: w.config})
+ }
+}
+
+func (w *window) SetCursor(name pointer.CursorName) {
+ w.cursor = windowSetCursor(w.cursor, name)
+}
+
+func (w *window) ShowTextInput(show bool) {}
+
+func (w *window) SetInputHint(_ key.InputHint) {}
+
+func (w *window) SetAnimating(anim bool) {
+ if anim {
+ w.displayLink.Start()
+ } else {
+ w.displayLink.Stop()
+ }
+}
+
+func (w *window) Raise() {
+ C.raiseWindow(w.window)
+}
+
+func (w *window) runOnMain(f func()) {
+ runOnMain(func() {
+ // Make sure the view is still valid. The window might've been closed
+ // during the switch to the main thread.
+ if w.view != 0 {
+ f()
+ }
+ })
+}
+
+func (w *window) Close() {
+ C.closeWindow(w.window)
+}
+
+// Maximize the window.
+func (w *window) Maximize() {
+ r := C.getScreenFrame(w.window) // the screen size of the window
+ C.setScreenFrame(w.window, C.CGFloat(0), C.CGFloat(0), r.size.width, r.size.height)
+}
+
+// Center the window.
+func (w *window) Center() {
+ r := C.getScreenFrame(w.window) // the screen size of the window
+
+ screenScale := float32(C.getScreenBackingScale())
+ sz := w.config.Size.Div(int(screenScale))
+ x := (int(r.size.width) - sz.X) / 2
+ y := (int(r.size.height) - sz.Y) / 2
+
+ C.setScreenFrame(w.window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y))
+}
+
+func (w *window) setStage(stage system.Stage) {
+ if stage == w.stage {
+ return
+ }
+ w.stage = stage
+ w.w.Event(system.StageEvent{Stage: stage})
+}
+
+//export gio_onKeys
+func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger, keyDown C.bool) {
+ str := C.GoString(cstr)
+ kmods := convertMods(mods)
+ ks := key.Release
+ if keyDown {
+ ks = key.Press
+ }
+ w := mustView(view)
+ for _, k := range str {
+ if n, ok := convertKey(k); ok {
+ w.w.Event(key.Event{
+ Name: n,
+ Modifiers: kmods,
+ State: ks,
+ })
+ }
+ }
+}
+
+//export gio_onText
+func gio_onText(view C.CFTypeRef, cstr *C.char) {
+ str := C.GoString(cstr)
+ w := mustView(view)
+ w.w.Event(key.EditEvent{Text: str})
+}
+
+//export gio_onMouse
+func gio_onMouse(view C.CFTypeRef, cdir C.int, cbtns C.NSUInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
+ var typ pointer.Type
+ switch cdir {
+ case C.MOUSE_MOVE:
+ typ = pointer.Move
+ case C.MOUSE_UP:
+ typ = pointer.Release
+ case C.MOUSE_DOWN:
+ typ = pointer.Press
+ case C.MOUSE_SCROLL:
+ typ = pointer.Scroll
+ default:
+ panic("invalid direction")
+ }
+ var btns pointer.Buttons
+ if cbtns&(1<<0) != 0 {
+ btns |= pointer.ButtonPrimary
+ }
+ if cbtns&(1<<1) != 0 {
+ btns |= pointer.ButtonSecondary
+ }
+ if cbtns&(1<<2) != 0 {
+ btns |= pointer.ButtonTertiary
+ }
+ t := time.Duration(float64(ti)*float64(time.Second) + .5)
+ w := mustView(view)
+ xf, yf := float32(x)*w.scale, float32(y)*w.scale
+ dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
+ w.w.Event(pointer.Event{
+ Type: typ,
+ Source: pointer.Mouse,
+ Time: t,
+ Buttons: btns,
+ Position: f32.Point{X: xf, Y: yf},
+ Scroll: f32.Point{X: dxf, Y: dyf},
+ Modifiers: convertMods(mods),
+ })
+}
+
+//export gio_onDraw
+func gio_onDraw(view C.CFTypeRef) {
+ w := mustView(view)
+ w.draw()
+}
+
+//export gio_onFocus
+func gio_onFocus(view C.CFTypeRef, focus C.int) {
+ w := mustView(view)
+ w.w.Event(key.FocusEvent{Focus: focus == 1})
+ w.SetCursor(w.cursor)
+}
+
+//export gio_onChangeScreen
+func gio_onChangeScreen(view C.CFTypeRef, did uint64) {
+ w := mustView(view)
+ w.displayLink.SetDisplayID(did)
+}
+
+func (w *window) draw() {
+ w.scale = float32(C.getViewBackingScale(w.view))
+ wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view))
+ sz := image.Point{
+ X: int(wf*w.scale + .5),
+ Y: int(hf*w.scale + .5),
+ }
+ if sz != w.config.Size {
+ w.config.Size = sz
+ w.w.Event(ConfigEvent{Config: w.config})
+ }
+ if sz.X == 0 || sz.Y == 0 {
+ return
+ }
+ cfg := configFor(w.scale)
+ w.setStage(system.StageRunning)
+ w.w.Event(frameEvent{
+ FrameEvent: system.FrameEvent{
+ Now: time.Now(),
+ Size: w.config.Size,
+ Metric: cfg,
+ },
+ Sync: true,
+ })
+}
+
+func configFor(scale float32) unit.Metric {
+ return unit.Metric{
+ PxPerDp: scale,
+ PxPerSp: scale,
+ }
+}
+
+//export gio_onClose
+func gio_onClose(view C.CFTypeRef) {
+ w := mustView(view)
+ w.w.Event(ViewEvent{})
+ deleteView(view)
+ w.w.Event(system.DestroyEvent{})
+ w.displayLink.Close()
+ C.CFRelease(w.view)
+ C.CFRelease(w.window)
+ w.view = 0
+ w.window = 0
+ w.displayLink = nil
+}
+
+//export gio_onHide
+func gio_onHide(view C.CFTypeRef) {
+ w := mustView(view)
+ w.setStage(system.StagePaused)
+}
+
+//export gio_onShow
+func gio_onShow(view C.CFTypeRef) {
+ w := mustView(view)
+ w.setStage(system.StageRunning)
+}
+
+//export gio_onFullscreen
+func gio_onFullscreen(view C.CFTypeRef) {
+ w := mustView(view)
+ w.config.Mode = Fullscreen
+ w.w.Event(ConfigEvent{Config: w.config})
+}
+
+//export gio_onWindowed
+func gio_onWindowed(view C.CFTypeRef) {
+ w := mustView(view)
+ w.config.Mode = Windowed
+ w.w.Event(ConfigEvent{Config: w.config})
+}
+
+//export gio_onAppHide
+func gio_onAppHide() {
+ for _, w := range viewMap {
+ w.setStage(system.StagePaused)
+ }
+}
+
+//export gio_onAppShow
+func gio_onAppShow() {
+ for _, w := range viewMap {
+ w.setStage(system.StageRunning)
+ }
+}
+
+//export gio_onFinishLaunching
+func gio_onFinishLaunching() {
+ close(launched)
+}
+
+func newWindow(win *callbacks, options []Option) error {
+ <-launched
+ errch := make(chan error)
+ runOnMain(func() {
+ w, err := newOSWindow()
+ if err != nil {
+ errch <- err
+ return
+ }
+ errch <- nil
+ w.w = win
+ w.window = C.gio_createWindow(w.view, nil, 0, 0, 0, 0, 0, 0)
+ win.SetDriver(w)
+ w.Configure(options)
+ if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
+ // cascadeTopLeftFromPoint treats (0, 0) as a no-op,
+ // and just returns the offset we need for the first window.
+ nextTopLeft = C.cascadeTopLeftFromPoint(w.window, nextTopLeft)
+ }
+ nextTopLeft = C.cascadeTopLeftFromPoint(w.window, nextTopLeft)
+ C.makeKeyAndOrderFront(w.window)
+ layer := C.layerForView(w.view)
+ w.w.Event(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
+ })
+ return <-errch
+}
+
+func newOSWindow() (*window, error) {
+ view := C.gio_createView()
+ if view == 0 {
+ return nil, errors.New("CreateWindow: failed to create view")
+ }
+ scale := float32(C.getViewBackingScale(view))
+ w := &window{
+ view: view,
+ scale: scale,
+ }
+ dl, err := NewDisplayLink(func() {
+ w.runOnMain(func() {
+ C.setNeedsDisplay(w.view)
+ })
+ })
+ w.displayLink = dl
+ if err != nil {
+ C.CFRelease(view)
+ return nil, err
+ }
+ insertView(view, w)
+ return w, nil
+}
+
+func osMain() {
+ C.gio_main()
+}
+
+func convertKey(k rune) (string, bool) {
+ var n string
+ switch k {
+ case 0x1b:
+ n = key.NameEscape
+ case C.NSLeftArrowFunctionKey:
+ n = key.NameLeftArrow
+ case C.NSRightArrowFunctionKey:
+ n = key.NameRightArrow
+ case C.NSUpArrowFunctionKey:
+ n = key.NameUpArrow
+ case C.NSDownArrowFunctionKey:
+ n = key.NameDownArrow
+ case 0xd:
+ n = key.NameReturn
+ case 0x3:
+ n = key.NameEnter
+ case C.NSHomeFunctionKey:
+ n = key.NameHome
+ case C.NSEndFunctionKey:
+ n = key.NameEnd
+ case 0x7f:
+ n = key.NameDeleteBackward
+ case C.NSDeleteFunctionKey:
+ n = key.NameDeleteForward
+ case C.NSPageUpFunctionKey:
+ n = key.NamePageUp
+ case C.NSPageDownFunctionKey:
+ n = key.NamePageDown
+ case C.NSF1FunctionKey:
+ n = "F1"
+ case C.NSF2FunctionKey:
+ n = "F2"
+ case C.NSF3FunctionKey:
+ n = "F3"
+ case C.NSF4FunctionKey:
+ n = "F4"
+ case C.NSF5FunctionKey:
+ n = "F5"
+ case C.NSF6FunctionKey:
+ n = "F6"
+ case C.NSF7FunctionKey:
+ n = "F7"
+ case C.NSF8FunctionKey:
+ n = "F8"
+ case C.NSF9FunctionKey:
+ n = "F9"
+ case C.NSF10FunctionKey:
+ n = "F10"
+ case C.NSF11FunctionKey:
+ n = "F11"
+ case C.NSF12FunctionKey:
+ n = "F12"
+ case 0x09, 0x19:
+ n = key.NameTab
+ case 0x20:
+ n = key.NameSpace
+ default:
+ k = unicode.ToUpper(k)
+ if !unicode.IsPrint(k) {
+ return "", false
+ }
+ n = string(k)
+ }
+ return n, true
+}
+
+func convertMods(mods C.NSUInteger) key.Modifiers {
+ var kmods key.Modifiers
+ if mods&C.NSAlternateKeyMask != 0 {
+ kmods |= key.ModAlt
+ }
+ if mods&C.NSControlKeyMask != 0 {
+ kmods |= key.ModCtrl
+ }
+ if mods&C.NSCommandKeyMask != 0 {
+ kmods |= key.ModCommand
+ }
+ if mods&C.NSShiftKeyMask != 0 {
+ kmods |= key.ModShift
+ }
+ return kmods
+}
+
+func (_ ViewEvent) ImplementsEvent() {}