github.com/Seikaijyu/gio@v0.0.1/app/os_macos.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 import ( 9 "errors" 10 "image" 11 "runtime" 12 "time" 13 "unicode" 14 "unicode/utf8" 15 16 "github.com/Seikaijyu/gio/internal/f32" 17 "github.com/Seikaijyu/gio/io/clipboard" 18 "github.com/Seikaijyu/gio/io/key" 19 "github.com/Seikaijyu/gio/io/pointer" 20 "github.com/Seikaijyu/gio/io/system" 21 "github.com/Seikaijyu/gio/unit" 22 23 _ "github.com/Seikaijyu/gio/internal/cocoainit" 24 ) 25 26 /* 27 #cgo CFLAGS: -Werror -Wno-deprecated-declarations -fobjc-arc -x objective-c 28 #cgo LDFLAGS: -framework AppKit -framework QuartzCore 29 30 #include <AppKit/AppKit.h> 31 32 #define MOUSE_MOVE 1 33 #define MOUSE_UP 2 34 #define MOUSE_DOWN 3 35 #define MOUSE_SCROLL 4 36 37 __attribute__ ((visibility ("hidden"))) void gio_main(void); 38 __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createView(void); 39 __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight); 40 41 static void writeClipboard(CFTypeRef str) { 42 @autoreleasepool { 43 NSString *s = (__bridge NSString *)str; 44 NSPasteboard *p = NSPasteboard.generalPasteboard; 45 [p declareTypes:@[NSPasteboardTypeString] owner:nil]; 46 [p setString:s forType:NSPasteboardTypeString]; 47 } 48 } 49 50 static CFTypeRef readClipboard(void) { 51 @autoreleasepool { 52 NSPasteboard *p = NSPasteboard.generalPasteboard; 53 NSString *content = [p stringForType:NSPasteboardTypeString]; 54 return (__bridge_retained CFTypeRef)content; 55 } 56 } 57 58 static CGFloat viewHeight(CFTypeRef viewRef) { 59 NSView *view = (__bridge NSView *)viewRef; 60 return [view bounds].size.height; 61 } 62 63 static CGFloat viewWidth(CFTypeRef viewRef) { 64 NSView *view = (__bridge NSView *)viewRef; 65 return [view bounds].size.width; 66 } 67 68 static CGFloat getScreenBackingScale(void) { 69 return [NSScreen.mainScreen backingScaleFactor]; 70 } 71 72 static CGFloat getViewBackingScale(CFTypeRef viewRef) { 73 NSView *view = (__bridge NSView *)viewRef; 74 return [view.window backingScaleFactor]; 75 } 76 77 static void setNeedsDisplay(CFTypeRef viewRef) { 78 NSView *view = (__bridge NSView *)viewRef; 79 [view setNeedsDisplay:YES]; 80 } 81 82 static NSPoint cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) { 83 NSWindow *window = (__bridge NSWindow *)windowRef; 84 return [window cascadeTopLeftFromPoint:topLeft]; 85 } 86 87 static void makeKeyAndOrderFront(CFTypeRef windowRef) { 88 NSWindow *window = (__bridge NSWindow *)windowRef; 89 [window makeKeyAndOrderFront:nil]; 90 } 91 92 static void toggleFullScreen(CFTypeRef windowRef) { 93 NSWindow *window = (__bridge NSWindow *)windowRef; 94 [window toggleFullScreen:nil]; 95 } 96 97 static NSWindowStyleMask getWindowStyleMask(CFTypeRef windowRef) { 98 NSWindow *window = (__bridge NSWindow *)windowRef; 99 return [window styleMask]; 100 } 101 102 static void setWindowStyleMask(CFTypeRef windowRef, NSWindowStyleMask mask) { 103 NSWindow *window = (__bridge NSWindow *)windowRef; 104 window.styleMask = mask; 105 } 106 107 static void setWindowTitleVisibility(CFTypeRef windowRef, NSWindowTitleVisibility state) { 108 NSWindow *window = (__bridge NSWindow *)windowRef; 109 window.titleVisibility = state; 110 } 111 112 static void setWindowTitlebarAppearsTransparent(CFTypeRef windowRef, int transparent) { 113 NSWindow *window = (__bridge NSWindow *)windowRef; 114 window.titlebarAppearsTransparent = (BOOL)transparent; 115 } 116 117 static void setWindowStandardButtonHidden(CFTypeRef windowRef, NSWindowButton btn, int hide) { 118 NSWindow *window = (__bridge NSWindow *)windowRef; 119 [window standardWindowButton:btn].hidden = (BOOL)hide; 120 } 121 122 static void performWindowDragWithEvent(CFTypeRef windowRef, CFTypeRef evt) { 123 NSWindow *window = (__bridge NSWindow *)windowRef; 124 [window performWindowDragWithEvent:(__bridge NSEvent*)evt]; 125 } 126 127 static void closeWindow(CFTypeRef windowRef) { 128 NSWindow* window = (__bridge NSWindow *)windowRef; 129 [window performClose:nil]; 130 } 131 132 static void setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) { 133 NSWindow* window = (__bridge NSWindow *)windowRef; 134 NSSize size = NSMakeSize(width, height); 135 [window setContentSize:size]; 136 } 137 138 static void setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) { 139 NSWindow* window = (__bridge NSWindow *)windowRef; 140 window.contentMinSize = NSMakeSize(width, height); 141 } 142 143 static void setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) { 144 NSWindow* window = (__bridge NSWindow *)windowRef; 145 window.contentMaxSize = NSMakeSize(width, height); 146 } 147 148 static void setScreenFrame(CFTypeRef windowRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h) { 149 NSWindow* window = (__bridge NSWindow *)windowRef; 150 NSRect r = NSMakeRect(x, y, w, h); 151 [window setFrame:r display:YES]; 152 } 153 154 static void hideWindow(CFTypeRef windowRef) { 155 NSWindow* window = (__bridge NSWindow *)windowRef; 156 [window miniaturize:window]; 157 } 158 159 static void unhideWindow(CFTypeRef windowRef) { 160 NSWindow* window = (__bridge NSWindow *)windowRef; 161 [window deminiaturize:window]; 162 } 163 164 static NSRect getScreenFrame(CFTypeRef windowRef) { 165 NSWindow* window = (__bridge NSWindow *)windowRef; 166 return [[window screen] frame]; 167 } 168 169 static void setTitle(CFTypeRef windowRef, CFTypeRef titleRef) { 170 NSWindow *window = (__bridge NSWindow *)windowRef; 171 window.title = (__bridge NSString *)titleRef; 172 } 173 174 static int isWindowZoomed(CFTypeRef windowRef) { 175 NSWindow *window = (__bridge NSWindow *)windowRef; 176 return window.zoomed ? 1 : 0; 177 } 178 179 static void zoomWindow(CFTypeRef windowRef) { 180 NSWindow *window = (__bridge NSWindow *)windowRef; 181 [window zoom:nil]; 182 } 183 184 static CFTypeRef layerForView(CFTypeRef viewRef) { 185 NSView *view = (__bridge NSView *)viewRef; 186 return (__bridge CFTypeRef)view.layer; 187 } 188 189 static CFTypeRef windowForView(CFTypeRef viewRef) { 190 NSView *view = (__bridge NSView *)viewRef; 191 return (__bridge CFTypeRef)view.window; 192 } 193 194 static void raiseWindow(CFTypeRef windowRef) { 195 NSRunningApplication *currentApp = [NSRunningApplication currentApplication]; 196 if (![currentApp isActive]) { 197 [currentApp activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; 198 } 199 NSWindow* window = (__bridge NSWindow *)windowRef; 200 [window makeKeyAndOrderFront:nil]; 201 } 202 203 static CFTypeRef createInputContext(CFTypeRef clientRef) { 204 @autoreleasepool { 205 id<NSTextInputClient> client = (__bridge id<NSTextInputClient>)clientRef; 206 NSTextInputContext *ctx = [[NSTextInputContext alloc] initWithClient:client]; 207 return CFBridgingRetain(ctx); 208 } 209 } 210 211 static void discardMarkedText(CFTypeRef viewRef) { 212 @autoreleasepool { 213 id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef; 214 NSTextInputContext *ctx = [NSTextInputContext currentInputContext]; 215 if (view == [ctx client]) { 216 [ctx discardMarkedText]; 217 } 218 } 219 } 220 221 static void invalidateCharacterCoordinates(CFTypeRef viewRef) { 222 @autoreleasepool { 223 id<NSTextInputClient> view = (__bridge id<NSTextInputClient>)viewRef; 224 NSTextInputContext *ctx = [NSTextInputContext currentInputContext]; 225 if (view == [ctx client]) { 226 [ctx invalidateCharacterCoordinates]; 227 } 228 } 229 } 230 */ 231 import "C" 232 233 func init() { 234 // Darwin requires that UI operations happen on the main thread only. 235 runtime.LockOSThread() 236 } 237 238 // ViewEvent notified the client of changes to the window AppKit handles. 239 // The handles are retained until another ViewEvent is sent. 240 type ViewEvent struct { 241 // View is a CFTypeRef for the NSView for the window. 242 View uintptr 243 // Layer is a CFTypeRef of the CALayer of View. 244 Layer uintptr 245 } 246 247 type window struct { 248 view C.CFTypeRef 249 w *callbacks 250 stage system.Stage 251 displayLink *displayLink 252 // redraw is a single entry channel for making sure only one 253 // display link redraw request is in flight. 254 redraw chan struct{} 255 cursor pointer.Cursor 256 pointerBtns pointer.Buttons 257 258 scale float32 259 config Config 260 } 261 262 // viewMap is the mapping from Cocoa NSViews to Go windows. 263 var viewMap = make(map[C.CFTypeRef]*window) 264 265 // launched is closed when applicationDidFinishLaunching is called. 266 var launched = make(chan struct{}) 267 268 // nextTopLeft is the offset to use for the next window's call to 269 // cascadeTopLeftFromPoint. 270 var nextTopLeft C.NSPoint 271 272 // mustView is like lookupView, except that it panics 273 // if the view isn't mapped. 274 func mustView(view C.CFTypeRef) *window { 275 w, ok := lookupView(view) 276 if !ok { 277 panic("no window for view") 278 } 279 return w 280 } 281 282 func lookupView(view C.CFTypeRef) (*window, bool) { 283 w, exists := viewMap[view] 284 if !exists { 285 return nil, false 286 } 287 return w, true 288 } 289 290 func deleteView(view C.CFTypeRef) { 291 delete(viewMap, view) 292 } 293 294 func insertView(view C.CFTypeRef, w *window) { 295 viewMap[view] = w 296 } 297 298 func (w *window) contextView() C.CFTypeRef { 299 return w.view 300 } 301 302 func (w *window) ReadClipboard() { 303 cstr := C.readClipboard() 304 if cstr != 0 { 305 defer C.CFRelease(cstr) 306 } 307 content := nsstringToString(cstr) 308 w.w.Event(clipboard.Event{Text: content}) 309 } 310 311 func (w *window) WriteClipboard(s string) { 312 cstr := stringToNSString(s) 313 defer C.CFRelease(cstr) 314 C.writeClipboard(cstr) 315 } 316 317 func (w *window) updateWindowMode() { 318 style := int(C.getWindowStyleMask(C.windowForView(w.view))) 319 if style&C.NSWindowStyleMaskFullScreen != 0 { 320 w.config.Mode = Fullscreen 321 } else { 322 w.config.Mode = Windowed 323 } 324 w.config.Decorated = style&C.NSWindowStyleMaskFullSizeContentView == 0 325 } 326 327 func (w *window) Configure(options []Option) { 328 screenScale := float32(C.getScreenBackingScale()) 329 cfg := configFor(screenScale) 330 prev := w.config 331 w.updateWindowMode() 332 cnf := w.config 333 cnf.apply(cfg, options) 334 window := C.windowForView(w.view) 335 336 switch cnf.Mode { 337 case Fullscreen: 338 switch prev.Mode { 339 case Fullscreen: 340 case Minimized: 341 C.unhideWindow(window) 342 fallthrough 343 default: 344 w.config.Mode = Fullscreen 345 C.toggleFullScreen(window) 346 } 347 case Minimized: 348 switch prev.Mode { 349 case Minimized, Fullscreen: 350 default: 351 w.config.Mode = Minimized 352 C.hideWindow(window) 353 } 354 case Maximized: 355 switch prev.Mode { 356 case Fullscreen: 357 case Minimized: 358 C.unhideWindow(window) 359 fallthrough 360 default: 361 w.config.Mode = Maximized 362 w.setTitle(prev, cnf) 363 if C.isWindowZoomed(window) == 0 { 364 C.zoomWindow(window) 365 } 366 } 367 case Windowed: 368 switch prev.Mode { 369 case Fullscreen: 370 C.toggleFullScreen(window) 371 case Minimized: 372 C.unhideWindow(window) 373 case Maximized: 374 if C.isWindowZoomed(window) != 0 { 375 C.zoomWindow(window) 376 } 377 } 378 w.config.Mode = Windowed 379 w.setTitle(prev, cnf) 380 if prev.Size != cnf.Size { 381 w.config.Size = cnf.Size 382 cnf.Size = cnf.Size.Div(int(screenScale)) 383 C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y)) 384 } 385 if prev.MinSize != cnf.MinSize { 386 w.config.MinSize = cnf.MinSize 387 cnf.MinSize = cnf.MinSize.Div(int(screenScale)) 388 C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y)) 389 } 390 if prev.MaxSize != cnf.MaxSize { 391 w.config.MaxSize = cnf.MaxSize 392 cnf.MaxSize = cnf.MaxSize.Div(int(screenScale)) 393 C.setMaxSize(window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y)) 394 } 395 } 396 if cnf.Decorated != prev.Decorated { 397 w.config.Decorated = cnf.Decorated 398 mask := C.getWindowStyleMask(window) 399 style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable) 400 style = C.NSWindowStyleMaskFullSizeContentView 401 mask &^= style 402 barTrans := C.int(C.NO) 403 titleVis := C.NSWindowTitleVisibility(C.NSWindowTitleVisible) 404 if !cnf.Decorated { 405 mask |= style 406 barTrans = C.YES 407 titleVis = C.NSWindowTitleHidden 408 } 409 C.setWindowTitlebarAppearsTransparent(window, barTrans) 410 C.setWindowTitleVisibility(window, titleVis) 411 C.setWindowStyleMask(window, mask) 412 C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans) 413 C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans) 414 C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans) 415 } 416 w.w.Event(ConfigEvent{Config: w.config}) 417 } 418 419 func (w *window) setTitle(prev, cnf Config) { 420 if prev.Title != cnf.Title { 421 w.config.Title = cnf.Title 422 title := stringToNSString(cnf.Title) 423 defer C.CFRelease(title) 424 C.setTitle(C.windowForView(w.view), title) 425 } 426 } 427 428 func (w *window) Perform(acts system.Action) { 429 window := C.windowForView(w.view) 430 walkActions(acts, func(a system.Action) { 431 switch a { 432 case system.ActionCenter: 433 r := C.getScreenFrame(window) // the screen size of the window 434 screenScale := float32(C.getScreenBackingScale()) 435 sz := w.config.Size.Div(int(screenScale)) 436 x := (int(r.size.width) - sz.X) / 2 437 y := (int(r.size.height) - sz.Y) / 2 438 C.setScreenFrame(window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y)) 439 case system.ActionRaise: 440 C.raiseWindow(window) 441 } 442 }) 443 if acts&system.ActionClose != 0 { 444 C.closeWindow(window) 445 } 446 } 447 448 func (w *window) SetCursor(cursor pointer.Cursor) { 449 w.cursor = windowSetCursor(w.cursor, cursor) 450 } 451 452 func (w *window) EditorStateChanged(old, new editorState) { 453 if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet { 454 C.discardMarkedText(w.view) 455 w.w.SetComposingRegion(key.Range{Start: -1, End: -1}) 456 } 457 if old.Selection.Caret != new.Selection.Caret || old.Selection.Transform != new.Selection.Transform { 458 C.invalidateCharacterCoordinates(w.view) 459 } 460 } 461 462 func (w *window) ShowTextInput(show bool) {} 463 464 func (w *window) SetInputHint(_ key.InputHint) {} 465 466 func (w *window) SetAnimating(anim bool) { 467 if anim { 468 w.displayLink.Start() 469 } else { 470 w.displayLink.Stop() 471 } 472 } 473 474 func (w *window) runOnMain(f func()) { 475 runOnMain(func() { 476 // Make sure the view is still valid. The window might've been closed 477 // during the switch to the main thread. 478 if w.view != 0 { 479 f() 480 } 481 }) 482 } 483 484 func (w *window) setStage(stage system.Stage) { 485 if stage == w.stage { 486 return 487 } 488 w.stage = stage 489 w.w.Event(system.StageEvent{Stage: stage}) 490 } 491 492 //export gio_onKeys 493 func gio_onKeys(view, cstr C.CFTypeRef, ti C.double, mods C.NSUInteger, keyDown C.bool) { 494 str := nsstringToString(cstr) 495 kmods := convertMods(mods) 496 ks := key.Release 497 if keyDown { 498 ks = key.Press 499 } 500 w := mustView(view) 501 for _, k := range str { 502 if n, ok := convertKey(k); ok { 503 w.w.Event(key.Event{ 504 Name: n, 505 Modifiers: kmods, 506 State: ks, 507 }) 508 } 509 } 510 } 511 512 //export gio_onText 513 func gio_onText(view, cstr C.CFTypeRef) { 514 str := nsstringToString(cstr) 515 w := mustView(view) 516 w.w.EditorInsert(str) 517 } 518 519 //export gio_onMouse 520 func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) { 521 w := mustView(view) 522 t := time.Duration(float64(ti)*float64(time.Second) + .5) 523 xf, yf := float32(x)*w.scale, float32(y)*w.scale 524 dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale 525 pos := f32.Point{X: xf, Y: yf} 526 var btn pointer.Buttons 527 switch cbtn { 528 case 0: 529 btn = pointer.ButtonPrimary 530 case 1: 531 btn = pointer.ButtonSecondary 532 case 2: 533 btn = pointer.ButtonTertiary 534 } 535 var typ pointer.Kind 536 switch cdir { 537 case C.MOUSE_MOVE: 538 typ = pointer.Move 539 case C.MOUSE_UP: 540 typ = pointer.Release 541 w.pointerBtns &^= btn 542 case C.MOUSE_DOWN: 543 typ = pointer.Press 544 w.pointerBtns |= btn 545 act, ok := w.w.ActionAt(pos) 546 if ok && w.config.Mode != Fullscreen { 547 switch act { 548 case system.ActionMove: 549 C.performWindowDragWithEvent(C.windowForView(w.view), evt) 550 return 551 } 552 } 553 case C.MOUSE_SCROLL: 554 typ = pointer.Scroll 555 default: 556 panic("invalid direction") 557 } 558 w.w.Event(pointer.Event{ 559 Kind: typ, 560 Source: pointer.Mouse, 561 Time: t, 562 Buttons: w.pointerBtns, 563 Position: pos, 564 Scroll: f32.Point{X: dxf, Y: dyf}, 565 Modifiers: convertMods(mods), 566 }) 567 } 568 569 //export gio_onDraw 570 func gio_onDraw(view C.CFTypeRef) { 571 w := mustView(view) 572 w.draw() 573 } 574 575 //export gio_onFocus 576 func gio_onFocus(view C.CFTypeRef, focus C.int) { 577 w := mustView(view) 578 w.w.Event(key.FocusEvent{Focus: focus == 1}) 579 if w.stage >= system.StageInactive { 580 if focus == 0 { 581 w.setStage(system.StageInactive) 582 } else { 583 w.setStage(system.StageRunning) 584 } 585 } 586 w.SetCursor(w.cursor) 587 } 588 589 //export gio_onChangeScreen 590 func gio_onChangeScreen(view C.CFTypeRef, did uint64) { 591 w := mustView(view) 592 w.displayLink.SetDisplayID(did) 593 C.setNeedsDisplay(w.view) 594 } 595 596 //export gio_hasMarkedText 597 func gio_hasMarkedText(view C.CFTypeRef) C.int { 598 w := mustView(view) 599 state := w.w.EditorState() 600 if state.compose.Start != -1 { 601 return 1 602 } 603 return 0 604 } 605 606 //export gio_markedRange 607 func gio_markedRange(view C.CFTypeRef) C.NSRange { 608 w := mustView(view) 609 state := w.w.EditorState() 610 rng := state.compose 611 start, end := rng.Start, rng.End 612 if start == -1 { 613 return C.NSMakeRange(C.NSNotFound, 0) 614 } 615 u16start := state.UTF16Index(start) 616 return C.NSMakeRange( 617 C.NSUInteger(u16start), 618 C.NSUInteger(state.UTF16Index(end)-u16start), 619 ) 620 } 621 622 //export gio_selectedRange 623 func gio_selectedRange(view C.CFTypeRef) C.NSRange { 624 w := mustView(view) 625 state := w.w.EditorState() 626 rng := state.Selection 627 start, end := rng.Start, rng.End 628 if start > end { 629 start, end = end, start 630 } 631 u16start := state.UTF16Index(start) 632 return C.NSMakeRange( 633 C.NSUInteger(u16start), 634 C.NSUInteger(state.UTF16Index(end)-u16start), 635 ) 636 } 637 638 //export gio_unmarkText 639 func gio_unmarkText(view C.CFTypeRef) { 640 w := mustView(view) 641 w.w.SetComposingRegion(key.Range{Start: -1, End: -1}) 642 } 643 644 //export gio_setMarkedText 645 func gio_setMarkedText(view, cstr C.CFTypeRef, selRange C.NSRange, replaceRange C.NSRange) { 646 w := mustView(view) 647 str := nsstringToString(cstr) 648 state := w.w.EditorState() 649 rng := state.compose 650 if rng.Start == -1 { 651 rng = state.Selection.Range 652 } 653 if replaceRange.location != C.NSNotFound { 654 // replaceRange is relative to marked (or selected) text. 655 offset := state.UTF16Index(rng.Start) 656 start := state.RunesIndex(int(replaceRange.location) + offset) 657 end := state.RunesIndex(int(replaceRange.location+replaceRange.length) + offset) 658 rng = key.Range{ 659 Start: start, 660 End: end, 661 } 662 } 663 w.w.EditorReplace(rng, str) 664 comp := key.Range{ 665 Start: rng.Start, 666 End: rng.Start + utf8.RuneCountInString(str), 667 } 668 w.w.SetComposingRegion(comp) 669 670 sel := key.Range{Start: comp.End, End: comp.End} 671 if selRange.location != C.NSNotFound { 672 // selRange is relative to inserted text. 673 offset := state.UTF16Index(rng.Start) 674 start := state.RunesIndex(int(selRange.location) + offset) 675 end := state.RunesIndex(int(selRange.location+selRange.length) + offset) 676 sel = key.Range{ 677 Start: start, 678 End: end, 679 } 680 } 681 w.w.SetEditorSelection(sel) 682 } 683 684 //export gio_substringForProposedRange 685 func gio_substringForProposedRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.CFTypeRef { 686 w := mustView(view) 687 state := w.w.EditorState() 688 start, end := state.Snippet.Start, state.Snippet.End 689 if start > end { 690 start, end = end, start 691 } 692 rng := key.Range{ 693 Start: state.RunesIndex(int(crng.location)), 694 End: state.RunesIndex(int(crng.location + crng.length)), 695 } 696 if rng.Start < start || end < rng.End { 697 w.w.SetEditorSnippet(rng) 698 } 699 u16start := state.UTF16Index(start) 700 actual.location = C.NSUInteger(u16start) 701 actual.length = C.NSUInteger(state.UTF16Index(end) - u16start) 702 return stringToNSString(state.Snippet.Text) 703 } 704 705 //export gio_insertText 706 func gio_insertText(view, cstr C.CFTypeRef, crng C.NSRange) { 707 w := mustView(view) 708 state := w.w.EditorState() 709 rng := state.compose 710 if rng.Start == -1 { 711 rng = state.Selection.Range 712 } 713 if crng.location != C.NSNotFound { 714 rng = key.Range{ 715 Start: state.RunesIndex(int(crng.location)), 716 End: state.RunesIndex(int(crng.location + crng.length)), 717 } 718 } 719 str := nsstringToString(cstr) 720 w.w.EditorReplace(rng, str) 721 w.w.SetComposingRegion(key.Range{Start: -1, End: -1}) 722 start := rng.Start 723 if rng.End < start { 724 start = rng.End 725 } 726 pos := start + utf8.RuneCountInString(str) 727 w.w.SetEditorSelection(key.Range{Start: pos, End: pos}) 728 } 729 730 //export gio_characterIndexForPoint 731 func gio_characterIndexForPoint(view C.CFTypeRef, p C.NSPoint) C.NSUInteger { 732 return C.NSNotFound 733 } 734 735 //export gio_firstRectForCharacterRange 736 func gio_firstRectForCharacterRange(view C.CFTypeRef, crng C.NSRange, actual C.NSRangePointer) C.NSRect { 737 w := mustView(view) 738 state := w.w.EditorState() 739 sel := state.Selection 740 u16start := state.UTF16Index(sel.Start) 741 actual.location = C.NSUInteger(u16start) 742 actual.length = 0 743 // Transform to NSView local coordinates (lower left origin, undo backing scale). 744 scale := 1. / float32(C.getViewBackingScale(w.view)) 745 height := float32(C.viewHeight(w.view)) 746 local := f32.Affine2D{}.Scale(f32.Pt(0, 0), f32.Pt(scale, -scale)).Offset(f32.Pt(0, height)) 747 t := local.Mul(sel.Transform) 748 bounds := f32.Rectangle{ 749 Min: t.Transform(sel.Pos.Sub(f32.Pt(0, sel.Ascent))), 750 Max: t.Transform(sel.Pos.Add(f32.Pt(0, sel.Descent))), 751 }.Canon() 752 sz := bounds.Size() 753 return C.NSMakeRect( 754 C.CGFloat(bounds.Min.X), C.CGFloat(bounds.Min.Y), 755 C.CGFloat(sz.X), C.CGFloat(sz.Y), 756 ) 757 } 758 759 func (w *window) draw() { 760 select { 761 case <-w.redraw: 762 default: 763 } 764 w.scale = float32(C.getViewBackingScale(w.view)) 765 wf, hf := float32(C.viewWidth(w.view)), float32(C.viewHeight(w.view)) 766 sz := image.Point{ 767 X: int(wf*w.scale + .5), 768 Y: int(hf*w.scale + .5), 769 } 770 if sz != w.config.Size { 771 w.config.Size = sz 772 w.w.Event(ConfigEvent{Config: w.config}) 773 } 774 if sz.X == 0 || sz.Y == 0 { 775 return 776 } 777 cfg := configFor(w.scale) 778 w.setStage(system.StageRunning) 779 w.w.Event(frameEvent{ 780 FrameEvent: system.FrameEvent{ 781 Now: time.Now(), 782 Size: w.config.Size, 783 Metric: cfg, 784 }, 785 Sync: true, 786 }) 787 } 788 789 func configFor(scale float32) unit.Metric { 790 return unit.Metric{ 791 PxPerDp: scale, 792 PxPerSp: scale, 793 } 794 } 795 796 //export gio_onClose 797 func gio_onClose(view C.CFTypeRef) { 798 w := mustView(view) 799 w.w.Event(ViewEvent{}) 800 w.w.Event(system.DestroyEvent{}) 801 w.displayLink.Close() 802 w.displayLink = nil 803 deleteView(view) 804 C.CFRelease(w.view) 805 w.view = 0 806 } 807 808 //export gio_onHide 809 func gio_onHide(view C.CFTypeRef) { 810 w := mustView(view) 811 w.setStage(system.StagePaused) 812 } 813 814 //export gio_onShow 815 func gio_onShow(view C.CFTypeRef) { 816 w := mustView(view) 817 w.setStage(system.StageRunning) 818 } 819 820 //export gio_onFullscreen 821 func gio_onFullscreen(view C.CFTypeRef) { 822 w := mustView(view) 823 w.config.Mode = Fullscreen 824 w.w.Event(ConfigEvent{Config: w.config}) 825 } 826 827 //export gio_onWindowed 828 func gio_onWindowed(view C.CFTypeRef) { 829 w := mustView(view) 830 w.config.Mode = Windowed 831 w.w.Event(ConfigEvent{Config: w.config}) 832 } 833 834 //export gio_onAppHide 835 func gio_onAppHide() { 836 for _, w := range viewMap { 837 w.setStage(system.StagePaused) 838 } 839 } 840 841 //export gio_onAppShow 842 func gio_onAppShow() { 843 for _, w := range viewMap { 844 w.setStage(system.StageRunning) 845 } 846 } 847 848 //export gio_onFinishLaunching 849 func gio_onFinishLaunching() { 850 close(launched) 851 } 852 853 func newWindow(win *callbacks, options []Option) error { 854 <-launched 855 errch := make(chan error) 856 runOnMain(func() { 857 w, err := newOSWindow() 858 if err != nil { 859 errch <- err 860 return 861 } 862 errch <- nil 863 w.w = win 864 window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0) 865 w.updateWindowMode() 866 win.SetDriver(w) 867 w.Configure(options) 868 if nextTopLeft.x == 0 && nextTopLeft.y == 0 { 869 // cascadeTopLeftFromPoint treats (0, 0) as a no-op, 870 // and just returns the offset we need for the first window. 871 nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft) 872 } 873 nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft) 874 // makeKeyAndOrderFront assumes ownership of our window reference. 875 C.makeKeyAndOrderFront(window) 876 layer := C.layerForView(w.view) 877 w.w.Event(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)}) 878 }) 879 return <-errch 880 } 881 882 func newOSWindow() (*window, error) { 883 view := C.gio_createView() 884 if view == 0 { 885 return nil, errors.New("newOSWindows: failed to create view") 886 } 887 scale := float32(C.getViewBackingScale(view)) 888 w := &window{ 889 view: view, 890 scale: scale, 891 redraw: make(chan struct{}, 1), 892 } 893 dl, err := newDisplayLink(func() { 894 select { 895 case w.redraw <- struct{}{}: 896 default: 897 return 898 } 899 w.runOnMain(func() { 900 C.setNeedsDisplay(w.view) 901 }) 902 }) 903 w.displayLink = dl 904 if err != nil { 905 C.CFRelease(view) 906 return nil, err 907 } 908 insertView(view, w) 909 return w, nil 910 } 911 912 func osMain() { 913 C.gio_main() 914 } 915 916 func convertKey(k rune) (string, bool) { 917 var n string 918 switch k { 919 case 0x1b: 920 n = key.NameEscape 921 case C.NSLeftArrowFunctionKey: 922 n = key.NameLeftArrow 923 case C.NSRightArrowFunctionKey: 924 n = key.NameRightArrow 925 case C.NSUpArrowFunctionKey: 926 n = key.NameUpArrow 927 case C.NSDownArrowFunctionKey: 928 n = key.NameDownArrow 929 case 0xd: 930 n = key.NameReturn 931 case 0x3: 932 n = key.NameEnter 933 case C.NSHomeFunctionKey: 934 n = key.NameHome 935 case C.NSEndFunctionKey: 936 n = key.NameEnd 937 case 0x7f: 938 n = key.NameDeleteBackward 939 case C.NSDeleteFunctionKey: 940 n = key.NameDeleteForward 941 case C.NSPageUpFunctionKey: 942 n = key.NamePageUp 943 case C.NSPageDownFunctionKey: 944 n = key.NamePageDown 945 case C.NSF1FunctionKey: 946 n = key.NameF1 947 case C.NSF2FunctionKey: 948 n = key.NameF2 949 case C.NSF3FunctionKey: 950 n = key.NameF3 951 case C.NSF4FunctionKey: 952 n = key.NameF4 953 case C.NSF5FunctionKey: 954 n = key.NameF5 955 case C.NSF6FunctionKey: 956 n = key.NameF6 957 case C.NSF7FunctionKey: 958 n = key.NameF7 959 case C.NSF8FunctionKey: 960 n = key.NameF8 961 case C.NSF9FunctionKey: 962 n = key.NameF9 963 case C.NSF10FunctionKey: 964 n = key.NameF10 965 case C.NSF11FunctionKey: 966 n = key.NameF11 967 case C.NSF12FunctionKey: 968 n = key.NameF12 969 case 0x09, 0x19: 970 n = key.NameTab 971 case 0x20: 972 n = key.NameSpace 973 default: 974 k = unicode.ToUpper(k) 975 if !unicode.IsPrint(k) { 976 return "", false 977 } 978 n = string(k) 979 } 980 return n, true 981 } 982 983 func convertMods(mods C.NSUInteger) key.Modifiers { 984 var kmods key.Modifiers 985 if mods&C.NSAlternateKeyMask != 0 { 986 kmods |= key.ModAlt 987 } 988 if mods&C.NSControlKeyMask != 0 { 989 kmods |= key.ModCtrl 990 } 991 if mods&C.NSCommandKeyMask != 0 { 992 kmods |= key.ModCommand 993 } 994 if mods&C.NSShiftKeyMask != 0 { 995 kmods |= key.ModShift 996 } 997 return kmods 998 } 999 1000 func (_ ViewEvent) ImplementsEvent() {}