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