mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
58cdb3e1da
Fixes: https://todo.sr.ht/~eliasnaur/gio/116 References: https://todo.sr.ht/~eliasnaur/gio/246 Signed-off-by: Elias Naur <mail@eliasnaur.com>
715 lines
16 KiB
Go
715 lines
16 KiB
Go
// 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 void hideWindow(CFTypeRef windowRef) {
|
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
|
[window miniaturize:window];
|
|
}
|
|
|
|
static void unhideWindow(CFTypeRef windowRef) {
|
|
NSWindow* window = (__bridge NSWindow *)windowRef;
|
|
[window deminiaturize:window];
|
|
}
|
|
|
|
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)
|
|
// Decorations are never disabled.
|
|
cnf.Decorated = true
|
|
|
|
switch cnf.Mode {
|
|
case Fullscreen:
|
|
switch prev.Mode {
|
|
case Fullscreen:
|
|
case Minimized:
|
|
C.unhideWindow(w.window)
|
|
fallthrough
|
|
default:
|
|
w.config.Mode = Fullscreen
|
|
C.toggleFullScreen(w.window)
|
|
}
|
|
case Minimized:
|
|
switch prev.Mode {
|
|
case Minimized, Fullscreen:
|
|
default:
|
|
w.config.Mode = Minimized
|
|
C.hideWindow(w.window)
|
|
}
|
|
case Maximized:
|
|
switch prev.Mode {
|
|
case Fullscreen:
|
|
case Minimized:
|
|
C.unhideWindow(w.window)
|
|
fallthrough
|
|
default:
|
|
w.config.Mode = Maximized
|
|
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)
|
|
w.config.Size = image.Pt(int(r.size.width), int(r.size.height))
|
|
w.setTitle(prev, cnf)
|
|
}
|
|
case Windowed:
|
|
switch prev.Mode {
|
|
case Fullscreen:
|
|
w.config.Mode = Windowed
|
|
C.toggleFullScreen(w.window)
|
|
case Minimized:
|
|
w.config.Mode = Windowed
|
|
C.unhideWindow(w.window)
|
|
case Maximized:
|
|
w.config.Mode = Windowed
|
|
}
|
|
w.setTitle(prev, cnf)
|
|
if prev.Size != cnf.Size {
|
|
w.config.Size = cnf.Size
|
|
cnf.Size = cnf.Size.Div(int(screenScale))
|
|
C.setSize(w.window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
|
|
}
|
|
if prev.MinSize != cnf.MinSize {
|
|
w.config.MinSize = cnf.MinSize
|
|
cnf.MinSize = cnf.MinSize.Div(int(screenScale))
|
|
C.setMinSize(w.window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
|
|
}
|
|
if prev.MaxSize != cnf.MaxSize {
|
|
w.config.MaxSize = cnf.MaxSize
|
|
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
|
|
C.setMaxSize(w.window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y))
|
|
}
|
|
if cnf.center {
|
|
r := C.getScreenFrame(w.window) // the screen size of the window
|
|
sz := w.config.Size
|
|
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))
|
|
}
|
|
}
|
|
if cnf.Decorated != prev.Decorated {
|
|
w.config.Decorated = cnf.Decorated
|
|
}
|
|
if w.config != prev {
|
|
w.w.Event(ConfigEvent{Config: w.config})
|
|
}
|
|
}
|
|
|
|
func (w *window) setTitle(prev, cnf Config) {
|
|
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)
|
|
}
|
|
}
|
|
|
|
func (w *window) Perform(system.Action) {}
|
|
|
|
func (w *window) SetCursor(name pointer.CursorName) {
|
|
w.cursor = windowSetCursor(w.cursor, name)
|
|
}
|
|
|
|
func (w *window) EditorStateChanged(old, new editorState) {}
|
|
|
|
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)
|
|
}
|
|
|
|
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.EditorInsert(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,
|
|
}
|
|
done := make(chan struct{})
|
|
setNeedsDisplay := func() {
|
|
C.setNeedsDisplay(w.view)
|
|
done <- struct{}{}
|
|
}
|
|
dl, err := NewDisplayLink(func() {
|
|
w.runOnMain(setNeedsDisplay)
|
|
<-done
|
|
})
|
|
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 = key.NameF1
|
|
case C.NSF2FunctionKey:
|
|
n = key.NameF2
|
|
case C.NSF3FunctionKey:
|
|
n = key.NameF3
|
|
case C.NSF4FunctionKey:
|
|
n = key.NameF4
|
|
case C.NSF5FunctionKey:
|
|
n = key.NameF5
|
|
case C.NSF6FunctionKey:
|
|
n = key.NameF6
|
|
case C.NSF7FunctionKey:
|
|
n = key.NameF7
|
|
case C.NSF8FunctionKey:
|
|
n = key.NameF8
|
|
case C.NSF9FunctionKey:
|
|
n = key.NameF9
|
|
case C.NSF10FunctionKey:
|
|
n = key.NameF10
|
|
case C.NSF11FunctionKey:
|
|
n = key.NameF11
|
|
case C.NSF12FunctionKey:
|
|
n = key.NameF12
|
|
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() {}
|