Blender V4.3
GHOST_WindowCocoa.mm
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
7#include "GHOST_Debug.hh"
9
10/* Don't generate OpenGL deprecation warning. This is a known thing, and is not something easily
11 * solvable in a short term. */
12#ifdef __clang__
13# pragma clang diagnostic ignored "-Wdeprecated-declarations"
14#endif
15
16#ifdef WITH_METAL_BACKEND
17# include "GHOST_ContextCGL.hh"
18#endif
19
20#ifdef WITH_VULKAN_BACKEND
21# include "GHOST_ContextVK.hh"
22#endif
23
24#import <Cocoa/Cocoa.h>
25#import <Metal/Metal.h>
26#import <QuartzCore/QuartzCore.h>
27
28#include <sys/sysctl.h>
29
30/* --------------------------------------------------------------------
31 * Blender window delegate object.
32 */
33
34@interface BlenderWindowDelegate : NSObject <NSWindowDelegate>
35
36@property(nonatomic, readonly, assign) GHOST_SystemCocoa *systemCocoa;
37@property(nonatomic, readonly, assign) GHOST_WindowCocoa *windowCocoa;
38
39- (instancetype)initWithSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
40 windowCocoa:(GHOST_WindowCocoa *)winCocoa;
41- (void)windowDidBecomeKey:(NSNotification *)notification;
42- (void)windowDidResignKey:(NSNotification *)notification;
43- (void)windowDidExpose:(NSNotification *)notification;
44- (void)windowDidResize:(NSNotification *)notification;
45- (void)windowDidMove:(NSNotification *)notification;
46- (void)windowWillMove:(NSNotification *)notification;
47- (BOOL)windowShouldClose:(id)sender;
48- (void)windowDidChangeBackingProperties:(NSNotification *)notification;
49
50@end
51
52@implementation BlenderWindowDelegate : NSObject
53
54@synthesize systemCocoa = m_systemCocoa;
55@synthesize windowCocoa = m_windowCocoa;
56
57- (instancetype)initWithSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
59{
60 self = [super init];
61
62 if (self) {
63 m_systemCocoa = sysCocoa;
64 m_windowCocoa = winCocoa;
65 }
66
67 return self;
68}
69
70- (void)windowDidBecomeKey:(NSNotification *)notification
71{
72 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowActivate, m_windowCocoa);
73 /* Workaround for broken app-switching when combining Command-Tab and mission-control. */
74 [(NSWindow *)m_windowCocoa->getOSWindow() orderFrontRegardless];
75}
76
77- (void)windowDidResignKey:(NSNotification *)notification
78{
79 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowDeactivate, m_windowCocoa);
80}
81
82- (void)windowDidExpose:(NSNotification *)notification
83{
84 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowUpdate, m_windowCocoa);
85}
86
87- (void)windowDidMove:(NSNotification *)notification
88{
89 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowMove, m_windowCocoa);
90}
91
92- (void)windowWillMove:(NSNotification *)notification
93{
94 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowMove, m_windowCocoa);
95}
96
97- (void)windowWillEnterFullScreen:(NSNotification *)notification
98{
99 m_windowCocoa->setImmediateDraw(true);
100}
101
102- (void)windowDidEnterFullScreen:(NSNotification *)notification
103{
104 /* macOS does not send a window resize event when switching between zoomed
105 * and full-screen, when automatic show/hide of dock and menu bar are enabled.
106 * Send our own to prevent artifacts. */
107 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowSize, m_windowCocoa);
108
109 m_windowCocoa->setImmediateDraw(false);
110}
111
112- (void)windowWillExitFullScreen:(NSNotification *)notification
113{
114 m_windowCocoa->setImmediateDraw(true);
115}
116
117- (void)windowDidExitFullScreen:(NSNotification *)notification
118{
119 /* See comment for windowWillEnterFullScreen. */
120 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowSize, m_windowCocoa);
121 m_windowCocoa->setImmediateDraw(false);
122}
123
124- (void)windowDidResize:(NSNotification *)notification
125{
126#if 0
127 if (![[notification object] inLiveResize])
128#endif
129 {
130 /* Send event only once, at end of resize operation (when user has released mouse button). */
131 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowSize, m_windowCocoa);
132 }
133 /* Live resize, send event, gets handled in wm_window.c.
134 * Needed because live resize runs in a modal loop, not letting main loop run */
135 if ([[notification object] inLiveResize]) {
136 m_systemCocoa->dispatchEvents();
137 }
138}
139
140- (void)windowDidChangeBackingProperties:(NSNotification *)notification
141{
142 m_systemCocoa->handleWindowEvent(GHOST_kEventNativeResolutionChange, m_windowCocoa);
143 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowSize, m_windowCocoa);
144}
145
146- (BOOL)windowShouldClose:(id)sender;
147{
148 /* Let Blender close the window rather than closing immediately. */
149 m_systemCocoa->handleWindowEvent(GHOST_kEventWindowClose, m_windowCocoa);
150 return false;
151}
152
153@end
154
155@interface BlenderWindow : NSWindow
156
157@property(nonatomic, readonly, assign) GHOST_SystemCocoa *systemCocoa;
158@property(nonatomic, readonly, assign) GHOST_WindowCocoa *windowCocoa;
159@property(nonatomic, readonly, assign) GHOST_TDragnDropTypes draggedObjectType;
160
161- (instancetype)initWithSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
162 windowCocoa:(GHOST_WindowCocoa *)winCocoa
163 contentRect:(NSRect)contentRect
164 styleMask:(NSWindowStyleMask)style
165 backing:(NSBackingStoreType)backingStoreType
166 defer:(BOOL)flag;
167
168@end
169
170@implementation BlenderWindow
171
172@synthesize systemCocoa = m_systemCocoa;
173@synthesize windowCocoa = m_windowCocoa;
174@synthesize draggedObjectType = m_draggedObjectType;
175
176- (instancetype)initWithSystemCocoa:(GHOST_SystemCocoa *)sysCocoa
177 windowCocoa:(GHOST_WindowCocoa *)winCocoa
178 contentRect:(NSRect)contentRect
179 styleMask:(NSWindowStyleMask)style
180 backing:(NSBackingStoreType)backingStoreType
181 defer:(BOOL)flag
182{
183 self = [super initWithContentRect:contentRect
184 styleMask:style
185 backing:backingStoreType
186 defer:flag];
187
188 if (self) {
189 m_systemCocoa = sysCocoa;
190 m_windowCocoa = winCocoa;
191 }
192
193 return self;
194}
195
196- (BOOL)canBecomeKeyWindow
197{
198 /* Don't make other windows active when a dialog window is open. */
199 return (m_windowCocoa->isDialog() || !m_systemCocoa->hasDialogWindow());
200}
201
202/* The drag & drop dragging destination methods. */
203- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
204{
205 @autoreleasepool {
206 NSPasteboard *draggingPBoard = sender.draggingPasteboard;
207 if ([[draggingPBoard types] containsObject:NSPasteboardTypeTIFF]) {
208 m_draggedObjectType = GHOST_kDragnDropTypeBitmap;
209 }
210 else if ([[draggingPBoard types] containsObject:NSFilenamesPboardType]) {
211 m_draggedObjectType = GHOST_kDragnDropTypeFilenames;
212 }
213 else if ([[draggingPBoard types] containsObject:NSPasteboardTypeString]) {
214 m_draggedObjectType = GHOST_kDragnDropTypeString;
215 }
216 else {
217 return NSDragOperationNone;
218 }
219
220 const NSPoint mouseLocation = sender.draggingLocation;
221 m_windowCocoa->setAcceptDragOperation(TRUE); /* Drag operation is accepted by default. */
222 m_systemCocoa->handleDraggingEvent(GHOST_kEventDraggingEntered,
223 m_draggedObjectType,
224 m_windowCocoa,
225 mouseLocation.x,
226 mouseLocation.y,
227 nil);
228 }
229 return NSDragOperationCopy;
230}
231
232- (BOOL)wantsPeriodicDraggingUpdates
233{
234 return NO; /* No need to overflow blender event queue. Events shall be sent only on changes. */
235}
236
237- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
238{
239 const NSPoint mouseLocation = [sender draggingLocation];
240
241 m_systemCocoa->handleDraggingEvent(GHOST_kEventDraggingUpdated,
242 m_draggedObjectType,
243 m_windowCocoa,
244 mouseLocation.x,
245 mouseLocation.y,
246 nil);
247 return m_windowCocoa->canAcceptDragOperation() ? NSDragOperationCopy : NSDragOperationNone;
248}
249
250- (void)draggingExited:(id<NSDraggingInfo>)sender
251{
252 m_systemCocoa->handleDraggingEvent(
253 GHOST_kEventDraggingExited, m_draggedObjectType, m_windowCocoa, 0, 0, nil);
254 m_draggedObjectType = GHOST_kDragnDropTypeUnknown;
255}
256
257- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)sender
258{
259 if (m_windowCocoa->canAcceptDragOperation()) {
260 return YES;
261 }
262 return NO;
263}
264
265- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
266{
267 @autoreleasepool {
268 NSPasteboard *draggingPBoard = sender.draggingPasteboard;
269 id data;
270
271 switch (m_draggedObjectType) {
273 if ([NSImage canInitWithPasteboard:draggingPBoard]) {
274 NSImage *droppedImg = [[[NSImage alloc] initWithPasteboard:draggingPBoard] autorelease];
275 data = droppedImg; // [draggingPBoard dataForType:NSPasteboardTypeTIFF];
276 }
277 else {
278 return NO;
279 }
280
281 break;
282 }
284 data = [draggingPBoard propertyListForType:NSFilenamesPboardType];
285 break;
287 data = [draggingPBoard stringForType:NSPasteboardTypeString];
288 break;
289 default:
290 return NO;
291 break;
292 }
293
294 const NSPoint mouseLocation = sender.draggingLocation;
295 m_systemCocoa->handleDraggingEvent(GHOST_kEventDraggingDropDone,
296 m_draggedObjectType,
297 m_windowCocoa,
298 mouseLocation.x,
299 mouseLocation.y,
300 (void *)data);
301 }
302 return YES;
303}
304
305@end
306
307/* NSView for handling input and drawing. */
308#define COCOA_VIEW_CLASS CocoaOpenGLView
309#define COCOA_VIEW_BASE_CLASS NSOpenGLView
311#undef COCOA_VIEW_CLASS
312#undef COCOA_VIEW_BASE_CLASS
313
314#define COCOA_VIEW_CLASS CocoaMetalView
315#define COCOA_VIEW_BASE_CLASS NSView
317#undef COCOA_VIEW_CLASS
318#undef COCOA_VIEW_BASE_CLASS
319
320/* --------------------------------------------------------------------
321 * Initialization / Finalization.
322 */
323
325 const char *title,
326 int32_t left,
327 int32_t bottom,
328 uint32_t width,
329 uint32_t height,
332 const bool stereoVisual,
333 bool is_debug,
334 bool is_dialog,
335 GHOST_WindowCocoa *parentWindow,
336 const GHOST_GPUDevice &preferred_device)
337 : GHOST_Window(width, height, state, stereoVisual, false),
338 m_openGLView(nil),
339 m_metalView(nil),
340 m_metalLayer(nil),
341 m_systemCocoa(systemCocoa),
342 m_customCursor(nullptr),
343 m_immediateDraw(false),
344 m_debug_context(is_debug),
345 m_is_dialog(is_dialog),
346 m_preferred_device(preferred_device)
347{
348 m_fullScreen = false;
349
350 @autoreleasepool {
351 /* Create the window. */
352 NSRect rect;
353 rect.origin.x = left;
354 rect.origin.y = bottom;
355 rect.size.width = width;
356 rect.size.height = height;
357
358 NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
359 NSWindowStyleMaskResizable;
360 if (!is_dialog) {
361 styleMask |= NSWindowStyleMaskMiniaturizable;
362 }
363
364 m_window = [[BlenderWindow alloc] initWithSystemCocoa:systemCocoa
365 windowCocoa:this
366 contentRect:rect
367 styleMask:styleMask
368 backing:NSBackingStoreBuffered
369 defer:NO];
370
371 /* Forbid to resize the window below the blender defined minimum one. */
372 const NSSize minSize = {320, 240};
373 m_window.contentMinSize = minSize;
374
375 /* Create NSView inside the window. */
376 id<MTLDevice> metalDevice = MTLCreateSystemDefaultDevice();
377 NSView *view;
378
379 if (metalDevice) {
380 /* Create metal layer and view if supported. */
381 m_metalLayer = [[CAMetalLayer alloc] init];
382 m_metalLayer.edgeAntialiasingMask = 0;
383 m_metalLayer.masksToBounds = NO;
384 m_metalLayer.opaque = YES;
385 m_metalLayer.framebufferOnly = YES;
386 m_metalLayer.presentsWithTransaction = NO;
387 [m_metalLayer removeAllAnimations];
388 m_metalLayer.device = metalDevice;
389
390 if (type == GHOST_kDrawingContextTypeMetal) {
391 /* Enable EDR support. This is done by:
392 * 1. Using a floating point render target, so that values outside 0..1 can be used
393 * 2. Informing the OS that we are EDR aware, and intend to use values outside 0..1
394 * 3. Setting the extended sRGB color space so that the OS knows how to interpret the
395 * values.
396 */
397 m_metalLayer.wantsExtendedDynamicRangeContent = YES;
398 m_metalLayer.pixelFormat = MTLPixelFormatRGBA16Float;
399 const CFStringRef name = kCGColorSpaceExtendedSRGB;
400 CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(name);
401 m_metalLayer.colorspace = colorspace;
402 CGColorSpaceRelease(colorspace);
403 }
404
405 m_metalView = [[CocoaMetalView alloc] initWithSystemCocoa:systemCocoa
406 windowCocoa:this
407 frame:rect];
408 m_metalView.wantsLayer = YES;
410 view = m_metalView;
411 }
412 else {
413 /* Fallback to OpenGL view if there is no Metal support. */
414 m_openGLView = [[CocoaOpenGLView alloc] initWithSystemCocoa:systemCocoa
415 windowCocoa:this
416 frame:rect];
417 view = m_openGLView;
418 }
419
421 /* Needs to happen early when building with the 10.14 SDK, otherwise
422 * has no effect until resizing the window. */
423 if ([view respondsToSelector:@selector(setWantsBestResolutionOpenGLSurface:)]) {
424 view.wantsBestResolutionOpenGLSurface = YES;
425 }
426 }
427
428 m_window.contentView = view;
429 m_window.initialFirstResponder = view;
430
431 [m_window makeKeyAndOrderFront:nil];
432
436
437 setTitle(title);
438
440
441 BlenderWindowDelegate *windowDelegate = [[BlenderWindowDelegate alloc]
442 initWithSystemCocoa:systemCocoa
443 windowCocoa:this];
444 m_window.delegate = windowDelegate;
445
446 m_window.acceptsMouseMovedEvents = YES;
447
448 NSView *contentview = m_window.contentView;
449 contentview.allowedTouchTypes = (NSTouchTypeMaskDirect | NSTouchTypeMaskIndirect);
450
451 [m_window registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,
452 NSPasteboardTypeString,
453 NSPasteboardTypeTIFF,
454 nil]];
455
456 if (is_dialog && parentWindow) {
457 [parentWindow->getViewWindow() addChildWindow:m_window ordered:NSWindowAbove];
458 m_window.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary;
459 }
460 else {
461 m_window.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary;
462 }
463
466 }
467
469 }
470}
471
473{
474 @autoreleasepool {
475 if (m_customCursor) {
476 [m_customCursor release];
477 m_customCursor = nil;
478 }
479
481
482 if (m_openGLView) {
483 [m_openGLView release];
484 m_openGLView = nil;
485 }
486 if (m_metalView) {
487 [m_metalView release];
488 m_metalView = nil;
489 }
490 if (m_metalLayer) {
491 [m_metalLayer release];
492 m_metalLayer = nil;
493 }
494
495 if (m_window) {
496 [m_window close];
497 }
498
499 /* Check for other blender opened windows and make the front-most key
500 * NOTE: for some reason the closed window is still in the list. */
501 NSArray *windowsList = [NSApp orderedWindows];
502 for (int a = 0; a < [windowsList count]; a++) {
503 if (m_window != (BlenderWindow *)[windowsList objectAtIndex:a]) {
504 [[windowsList objectAtIndex:a] makeKeyWindow];
505 break;
506 }
507 }
508 m_window = nil;
509 }
510}
511
512/* --------------------------------------------------------------------
513 * Accessors.
514 */
515
517{
518 NSView *view = (m_openGLView) ? m_openGLView : m_metalView;
519 return GHOST_Window::getValid() && m_window != nullptr && view != nullptr;
520}
521
523{
524 return (void *)m_window;
525}
526
527void GHOST_WindowCocoa::setTitle(const char *title)
528{
529 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setTitle(): window invalid");
530
531 @autoreleasepool {
532 NSString *windowTitle = [[NSString alloc] initWithCString:title encoding:NSUTF8StringEncoding];
533 m_window.title = windowTitle;
534
535 [windowTitle release];
536 }
537}
538
540{
541 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::getTitle(): window invalid");
542
543 std::string title;
544 @autoreleasepool {
545 NSString *windowTitle = m_window.title;
546 if (windowTitle != nil) {
547 title = windowTitle.UTF8String;
548 }
549 }
550 return title;
551}
552
554{
555 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setAssociatedFile(): window invalid");
557
558 @autoreleasepool {
559 NSString *associatedFileName = [[NSString alloc] initWithCString:filepath
560 encoding:NSUTF8StringEncoding];
561 @try
562 {
563 m_window.representedFilename = associatedFileName;
564 }
565 @catch (NSException *e)
566 {
567 printf("\nInvalid file path given for window");
568 success = GHOST_kFailure;
569 }
570
571 [associatedFileName release];
572 }
573 return success;
574}
575
577{
578 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::getWindowBounds(): window invalid");
579
580 @autoreleasepool {
581 const NSRect screenSize = m_window.screen.visibleFrame;
582 const NSRect rect = m_window.frame;
583
584 bounds.m_b = screenSize.size.height - (rect.origin.y - screenSize.origin.y);
585 bounds.m_l = rect.origin.x - screenSize.origin.x;
586 bounds.m_r = rect.origin.x - screenSize.origin.x + rect.size.width;
587 bounds.m_t = screenSize.size.height - (rect.origin.y + rect.size.height - screenSize.origin.y);
588 }
589}
590
592{
593 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::getClientBounds(): window invalid");
594
595 @autoreleasepool {
596 const NSRect screenSize = m_window.screen.visibleFrame;
597
598 /* Max window contents as screen size (excluding title bar...). */
599 const NSRect contentRect = [BlenderWindow contentRectForFrameRect:screenSize
600 styleMask:[m_window styleMask]];
601
602 const NSRect rect = [m_window contentRectForFrameRect:[m_window frame]];
603
604 bounds.m_b = contentRect.size.height - (rect.origin.y - contentRect.origin.y);
605 bounds.m_l = rect.origin.x - contentRect.origin.x;
606 bounds.m_r = rect.origin.x - contentRect.origin.x + rect.size.width;
607 bounds.m_t = contentRect.size.height -
608 (rect.origin.y + rect.size.height - contentRect.origin.y);
609 }
610}
611
613{
614 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setClientWidth(): window invalid");
615
616 @autoreleasepool {
617 GHOST_Rect cBnds;
618 getClientBounds(cBnds);
619
620 if ((uint32_t(cBnds.getWidth())) != width) {
621 const NSSize size = {(CGFloat)width, (CGFloat)cBnds.getHeight()};
622 [m_window setContentSize:size];
623 }
624 }
625 return GHOST_kSuccess;
626}
627
629{
630 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setClientHeight(): window invalid");
631
632 @autoreleasepool {
633 GHOST_Rect cBnds;
634 getClientBounds(cBnds);
635
636 if ((uint32_t(cBnds.getHeight())) != height) {
637 const NSSize size = {(CGFloat)cBnds.getWidth(), (CGFloat)height};
638 [m_window setContentSize:size];
639 }
640 }
641 return GHOST_kSuccess;
642}
643
645{
646 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setClientSize(): window invalid");
647
648 @autoreleasepool {
649 GHOST_Rect cBnds;
650 getClientBounds(cBnds);
651 if (((uint32_t(cBnds.getWidth())) != width) || ((uint32_t(cBnds.getHeight())) != height)) {
652 const NSSize size = {(CGFloat)width, (CGFloat)height};
653 [m_window setContentSize:size];
654 }
655 }
656 return GHOST_kSuccess;
657}
658
660{
661 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::getState(): window invalid");
662
663 @autoreleasepool {
664 NSUInteger masks = m_window.styleMask;
665
666 if (masks & NSWindowStyleMaskFullScreen) {
667 /* Lion style full-screen. */
668 if (!m_immediateDraw) {
670 }
672 }
673 if (m_window.isMiniaturized) {
675 }
676 if (m_window.isZoomed) {
678 }
679 if (m_immediateDraw) {
681 }
683 }
684}
685
687 int32_t inY,
688 int32_t &outX,
689 int32_t &outY) const
690{
691 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::screenToClient(): window invalid");
692
693 screenToClientIntern(inX, inY, outX, outY);
694
695 /* switch y to match ghost convention */
696 GHOST_Rect cBnds;
697 getClientBounds(cBnds);
698 outY = (cBnds.getHeight() - 1) - outY;
699}
700
702 int32_t inY,
703 int32_t &outX,
704 int32_t &outY) const
705{
706 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::clientToScreen(): window invalid");
707
708 /* switch y to match ghost convention */
709 GHOST_Rect cBnds;
710 getClientBounds(cBnds);
711 inY = (cBnds.getHeight() - 1) - inY;
712
713 clientToScreenIntern(inX, inY, outX, outY);
714}
715
717 int32_t inY,
718 int32_t &outX,
719 int32_t &outY) const
720{
721 NSRect screenCoord;
722 screenCoord.origin = {(CGFloat)inX, (CGFloat)inY};
723
724 const NSRect baseCoord = [m_window convertRectFromScreen:screenCoord];
725
726 outX = baseCoord.origin.x;
727 outY = baseCoord.origin.y;
728}
729
731 int32_t inY,
732 int32_t &outX,
733 int32_t &outY) const
734{
735 NSRect baseCoord;
736 baseCoord.origin = {(CGFloat)inX, (CGFloat)inY};
737
738 const NSRect screenCoord = [m_window convertRectToScreen:baseCoord];
739
740 outX = screenCoord.origin.x;
741 outY = screenCoord.origin.y;
742}
743
745{
746 return m_window.screen;
747}
748
749/* called for event, when window leaves monitor to another */
751{
752 NSView *view = (m_openGLView) ? m_openGLView : m_metalView;
753 const NSRect backingBounds = [view convertRectToBacking:[view bounds]];
754
755 GHOST_Rect rect;
756 getClientBounds(rect);
757
758 m_nativePixelSize = float(backingBounds.size.width) / float(rect.getWidth());
759}
760
769{
770 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setState(): window invalid");
771
772 @autoreleasepool {
773 switch (state) {
775 [m_window miniaturize:nil];
776 break;
778 [m_window zoom:nil];
779 break;
780
782 const NSUInteger masks = m_window.styleMask;
783
784 if (!(masks & NSWindowStyleMaskFullScreen)) {
785 [m_window toggleFullScreen:nil];
786 }
787 break;
788 }
790 default:
791 @autoreleasepool {
792 const NSUInteger masks = m_window.styleMask;
793
794 if (masks & NSWindowStyleMaskFullScreen) {
795 /* Lion style full-screen. */
796 [m_window toggleFullScreen:nil];
797 }
798 else if (m_window.isMiniaturized) {
799 [m_window deminiaturize:nil];
800 }
801 else if (m_window.isZoomed) {
802 [m_window zoom:nil];
803 }
804 }
805 break;
806 }
807 }
808 return GHOST_kSuccess;
809}
810
812{
813 @autoreleasepool {
814 m_window.documentEdited = isUnsavedChanges;
815 }
816 return GHOST_Window::setModifiedState(isUnsavedChanges);
817}
818
820{
821 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::setOrder(): window invalid");
822
823 @autoreleasepool {
824 if (order == GHOST_kWindowOrderTop) {
825 [NSApp activateIgnoringOtherApps:YES];
826 [m_window makeKeyAndOrderFront:nil];
827 }
828 else {
829 NSArray *windowsList;
830
831 [m_window orderBack:nil];
832
833 /* Check for other blender opened windows and make the front-most key. */
834 windowsList = [NSApp orderedWindows];
835 if (windowsList.count) {
836 [[windowsList objectAtIndex:0] makeKeyAndOrderFront:nil];
837 }
838 }
839 }
840 return GHOST_kSuccess;
841}
842
843/* --------------------------------------------------------------------
844 * Drawing context.
845 */
846
848{
849 switch (type) {
850#ifdef WITH_VULKAN_BACKEND
851 case GHOST_kDrawingContextTypeVulkan: {
852 GHOST_Context *context = new GHOST_ContextVK(
854 if (context->initializeDrawingContext()) {
855 return context;
856 }
857 delete context;
858 return nullptr;
859 }
860#endif
861
862#ifdef WITH_METAL_BACKEND
863 case GHOST_kDrawingContextTypeMetal: {
864 GHOST_Context *context = new GHOST_ContextCGL(
866 if (context->initializeDrawingContext()) {
867 return context;
868 }
869 delete context;
870 return nullptr;
871 }
872#endif
873
874 default:
875 /* Unsupported backend. */
876 return nullptr;
877 }
878}
879
880/* --------------------------------------------------------------------
881 * Invalidate.
882 */
883
885{
886 GHOST_ASSERT(getValid(), "GHOST_WindowCocoa::invalidate(): window invalid");
887
888 @autoreleasepool {
889 NSView *view = (m_openGLView) ? m_openGLView : m_metalView;
890 view.needsDisplay = YES;
891 }
892 return GHOST_kSuccess;
893}
894
895/* --------------------------------------------------------------------
896 * Progress bar.
897 */
898
900{
901 @autoreleasepool {
902 if ((progress >= 0.0) && (progress <= 1.0)) {
903 NSImage *dockIcon = [[NSImage alloc] initWithSize:NSMakeSize(128, 128)];
904
905 [dockIcon lockFocus];
906
907 [[NSImage imageNamed:@"NSApplicationIcon"] drawAtPoint:NSZeroPoint
908 fromRect:NSZeroRect
909 operation:NSCompositingOperationSourceOver
910 fraction:1.0];
911
912 NSRect progressRect = {{8, 8}, {112, 14}};
913 NSBezierPath *progressPath;
914
915 /* Draw white track. */
916 [[[NSColor whiteColor] colorWithAlphaComponent:0.6] setFill];
917 progressPath = [NSBezierPath bezierPathWithRoundedRect:progressRect xRadius:7 yRadius:7];
918 [progressPath fill];
919
920 /* Black progress fill. */
921 [[[NSColor blackColor] colorWithAlphaComponent:0.7] setFill];
922 progressRect = NSInsetRect(progressRect, 2, 2);
923 progressRect.size.width *= progress;
924 progressPath = [NSBezierPath bezierPathWithRoundedRect:progressRect xRadius:5 yRadius:5];
925 [progressPath fill];
926
927 [dockIcon unlockFocus];
928 [NSApp setApplicationIconImage:dockIcon];
929 [dockIcon release];
930
932 }
933 }
934 return GHOST_kSuccess;
935}
936
938{
940 return GHOST_kFailure;
941 }
942 m_progressBarVisible = false;
943
944 /* Reset application icon to remove the progress bar. */
945 @autoreleasepool {
946 NSImage *dockIcon = [[NSImage alloc] initWithSize:NSMakeSize(128, 128)];
947 [dockIcon lockFocus];
948 [[NSImage imageNamed:@"NSApplicationIcon"] drawAtPoint:NSZeroPoint
949 fromRect:NSZeroRect
950 operation:NSCompositingOperationSourceOver
951 fraction:1.0];
952 [dockIcon unlockFocus];
953 [NSApp setApplicationIconImage:dockIcon];
954 [dockIcon release];
955 }
956 return GHOST_kSuccess;
957}
958
959/* --------------------------------------------------------------------
960 * Cursor handling.
961 */
962
963static NSCursor *getImageCursor(GHOST_TStandardCursor shape, NSString *name, NSPoint hotspot)
964{
965 static NSCursor *cursors[GHOST_kStandardCursorNumCursors] = {nullptr};
966 static bool loaded[GHOST_kStandardCursorNumCursors] = {false};
967
968 const int index = int(shape);
969 if (!loaded[index]) {
970 /* Load image from file in application Resources folder. */
971 @autoreleasepool {
972 NSImage *image = [NSImage imageNamed:name];
973 if (image != nullptr) {
974 cursors[index] = [[NSCursor alloc] initWithImage:image hotSpot:hotspot];
975 }
976 }
977
978 loaded[index] = true;
979 }
980
981 return cursors[index];
982}
983
985{
986 @autoreleasepool {
987 switch (shape) {
989 if (m_customCursor) {
990 return m_customCursor;
991 }
992 else {
993 return nullptr;
994 }
996 return [NSCursor disappearingItemCursor];
998 return [NSCursor IBeamCursor];
1000 return [NSCursor crosshairCursor];
1002 return [NSCursor resizeUpDownCursor];
1004 return [NSCursor resizeLeftRightCursor];
1006 return [NSCursor resizeUpCursor];
1008 return [NSCursor resizeDownCursor];
1010 return [NSCursor resizeLeftCursor];
1012 return [NSCursor resizeRightCursor];
1014 return [NSCursor dragCopyCursor];
1016 return [NSCursor operationNotAllowedCursor];
1018 return [NSCursor openHandCursor];
1020 return [NSCursor openHandCursor];
1022 return [NSCursor closedHandCursor];
1024 return [NSCursor pointingHandCursor];
1026 return [NSCursor arrowCursor];
1028 return getImageCursor(shape, @"knife.pdf", NSMakePoint(6, 24));
1030 return getImageCursor(shape, @"eraser.pdf", NSMakePoint(6, 24));
1032 return getImageCursor(shape, @"pen.pdf", NSMakePoint(6, 24));
1034 return getImageCursor(shape, @"eyedropper.pdf", NSMakePoint(6, 24));
1036 return getImageCursor(shape, @"zoomin.pdf", NSMakePoint(8, 7));
1038 return getImageCursor(shape, @"zoomout.pdf", NSMakePoint(8, 7));
1040 return getImageCursor(shape, @"scrollnsew.pdf", NSMakePoint(16, 16));
1042 return getImageCursor(shape, @"scrollns.pdf", NSMakePoint(16, 16));
1044 return getImageCursor(shape, @"scrollew.pdf", NSMakePoint(16, 16));
1046 return getImageCursor(shape, @"arrowup.pdf", NSMakePoint(16, 16));
1048 return getImageCursor(shape, @"arrowdown.pdf", NSMakePoint(16, 16));
1050 return getImageCursor(shape, @"arrowleft.pdf", NSMakePoint(16, 16));
1052 return getImageCursor(shape, @"arrowright.pdf", NSMakePoint(16, 16));
1054 return getImageCursor(shape, @"splitv.pdf", NSMakePoint(16, 16));
1056 return getImageCursor(shape, @"splith.pdf", NSMakePoint(16, 16));
1058 return getImageCursor(shape, @"paint_cursor_cross.pdf", NSMakePoint(16, 15));
1060 return getImageCursor(shape, @"paint_cursor_dot.pdf", NSMakePoint(16, 15));
1062 return getImageCursor(shape, @"crossc.pdf", NSMakePoint(16, 16));
1064 return getImageCursor(shape, @"handle_left.pdf", NSMakePoint(12, 14));
1066 return getImageCursor(shape, @"handle_right.pdf", NSMakePoint(10, 14));
1068 return getImageCursor(shape, @"handle_both.pdf", NSMakePoint(11, 14));
1069 default:
1070 return nullptr;
1071 }
1072 }
1073}
1074
1076{
1077 static bool systemCursorVisible = true;
1078
1079 @autoreleasepool {
1080 if (visible != systemCursorVisible) {
1081 if (visible) {
1082 [NSCursor unhide];
1083 systemCursorVisible = true;
1084 }
1085 else {
1086 [NSCursor hide];
1087 systemCursorVisible = false;
1088 }
1089 }
1090
1091 NSCursor *cursor = getStandardCursor(shape);
1092 if (cursor == nullptr) {
1094 }
1095
1096 [cursor set];
1097 }
1098}
1099
1101{
1102 return m_is_dialog;
1103}
1104
1106{
1107 @autoreleasepool {
1108 if (m_window.isVisible) {
1109 loadCursor(visible, getCursorShape());
1110 }
1111 }
1112 return GHOST_kSuccess;
1113}
1114
1116{
1117 @autoreleasepool {
1118 if (mode != GHOST_kGrabDisable) {
1119 /* No need to perform grab without warp as it is always on in OS X. */
1120 if (mode != GHOST_kGrabNormal) {
1121 @autoreleasepool {
1123 setCursorGrabAccum(0, 0);
1124
1125 if (mode == GHOST_kGrabHide) {
1127 }
1128
1129 /* Make window key if it wasn't to get the mouse move events. */
1130 [m_window makeKeyWindow];
1131 }
1132 }
1133 }
1134 else {
1138 }
1139
1140 /* Almost works without but important otherwise the mouse GHOST location
1141 * can be incorrect on exit. */
1142 setCursorGrabAccum(0, 0);
1143 m_cursorGrabBounds.m_l = m_cursorGrabBounds.m_r = -1; /* disable */
1144 }
1145 }
1146 return GHOST_kSuccess;
1147}
1148
1150{
1151 @autoreleasepool {
1152 if (m_window.isVisible) {
1154 }
1155 }
1156 return GHOST_kSuccess;
1157}
1158
1160{
1161 @autoreleasepool {
1163 return success;
1164 }
1165}
1166
1167/* Reverse the bits in a uint8_t */
1168#if 0
1170{
1171 ch= ((ch >> 1) & 0x55) | ((ch << 1) & 0xAA);
1172 ch= ((ch >> 2) & 0x33) | ((ch << 2) & 0xCC);
1173 ch= ((ch >> 4) & 0x0F) | ((ch << 4) & 0xF0);
1174 return ch;
1175}
1176#endif
1177
1180{
1181 shrt = ((shrt >> 1) & 0x5555) | ((shrt << 1) & 0xAAAA);
1182 shrt = ((shrt >> 2) & 0x3333) | ((shrt << 2) & 0xCCCC);
1183 shrt = ((shrt >> 4) & 0x0F0F) | ((shrt << 4) & 0xF0F0);
1184 shrt = ((shrt >> 8) & 0x00FF) | ((shrt << 8) & 0xFF00);
1185 return shrt;
1186}
1187
1189 uint8_t *bitmap, uint8_t *mask, int sizex, int sizey, int hotX, int hotY, bool canInvertColor)
1190{
1191 @autoreleasepool {
1192 if (m_customCursor) {
1193 [m_customCursor release];
1194 m_customCursor = nil;
1195 }
1196
1197 NSBitmapImageRep *cursorImageRep = [[NSBitmapImageRep alloc]
1198 initWithBitmapDataPlanes:nil
1199 pixelsWide:sizex
1200 pixelsHigh:sizey
1201 bitsPerSample:1
1202 samplesPerPixel:2
1203 hasAlpha:YES
1204 isPlanar:YES
1205 colorSpaceName:NSDeviceWhiteColorSpace
1206 bytesPerRow:(sizex / 8 + (sizex % 8 > 0 ? 1 : 0))
1207 bitsPerPixel:1];
1208
1209 uint16_t *cursorBitmap = (uint16_t *)cursorImageRep.bitmapData;
1210 int nbUns16 = cursorImageRep.bytesPerPlane / 2;
1211
1212 for (int y = 0; y < nbUns16; y++) {
1213#if !defined(__LITTLE_ENDIAN__)
1214 cursorBitmap[y] = uns16ReverseBits((bitmap[2 * y] << 0) | (bitmap[2 * y + 1] << 8));
1215 cursorBitmap[nbUns16 + y] = uns16ReverseBits((mask[2 * y] << 0) | (mask[2 * y + 1] << 8));
1216#else
1217 cursorBitmap[y] = uns16ReverseBits((bitmap[2 * y + 1] << 0) | (bitmap[2 * y] << 8));
1218 cursorBitmap[nbUns16 + y] = uns16ReverseBits((mask[2 * y + 1] << 0) | (mask[2 * y] << 8));
1219#endif
1220
1221 /* Flip white cursor with black outline to black cursor with white outline
1222 * to match macOS platform conventions. */
1223 if (canInvertColor) {
1224 cursorBitmap[y] = ~cursorBitmap[y];
1225 }
1226 }
1227
1228 const NSSize imSize = {(CGFloat)sizex, (CGFloat)sizey};
1229 NSImage *cursorImage = [[NSImage alloc] initWithSize:imSize];
1230 [cursorImage addRepresentation:cursorImageRep];
1231
1232 const NSPoint hotSpotPoint = {(CGFloat)(hotX), (CGFloat)(hotY)};
1233
1234 /* Foreground and background color parameter is not handled for now (10.6). */
1235 m_customCursor = [[NSCursor alloc] initWithImage:cursorImage hotSpot:hotSpotPoint];
1236
1237 [cursorImageRep release];
1238 [cursorImage release];
1239
1240 if (m_window.isVisible) {
1242 }
1243 }
1244 return GHOST_kSuccess;
1245}
1246
1247#ifdef WITH_INPUT_IME
1248void GHOST_WindowCocoa::beginIME(int32_t x, int32_t y, int32_t w, int32_t h, bool completed)
1249{
1250 if (m_openGLView) {
1251 [m_openGLView beginIME:x y:y w:w h:h completed:completed];
1252 }
1253 else {
1254 [m_metalView beginIME:x y:y w:w h:h completed:completed];
1255 }
1256}
1257
1258void GHOST_WindowCocoa::endIME()
1259{
1260 if (m_openGLView) {
1261 [m_openGLView endIME];
1262 }
1263 else {
1264 [m_metalView endIME];
1265 }
1266}
1267#endif /* WITH_INPUT_IME */
static AppView * view
#define GHOST_ASSERT(x, info)
GHOST_TWindowState
@ GHOST_kWindowStateMinimized
@ GHOST_kWindowStateMaximized
@ GHOST_kWindowStateNormal
@ GHOST_kWindowStateFullScreen
GHOST_TStandardCursor
@ GHOST_kStandardCursorLeftHandle
@ GHOST_kStandardCursorHandClosed
@ GHOST_kStandardCursorHandOpen
@ GHOST_kStandardCursorZoomIn
@ GHOST_kStandardCursorVerticalSplit
@ GHOST_kStandardCursorCopy
@ GHOST_kStandardCursorRightHandle
@ GHOST_kStandardCursorHorizontalSplit
@ GHOST_kStandardCursorTopSide
@ GHOST_kStandardCursorStop
@ GHOST_kStandardCursorCrosshair
@ GHOST_kStandardCursorCustom
@ GHOST_kStandardCursorNSEWScroll
@ GHOST_kStandardCursorLeftRight
@ GHOST_kStandardCursorPencil
@ GHOST_kStandardCursorNSScroll
@ GHOST_kStandardCursorCrosshairA
@ GHOST_kStandardCursorUpDown
@ GHOST_kStandardCursorUpArrow
@ GHOST_kStandardCursorHandPoint
@ GHOST_kStandardCursorBottomSide
@ GHOST_kStandardCursorBothHandles
@ GHOST_kStandardCursorEyedropper
@ GHOST_kStandardCursorKnife
@ GHOST_kStandardCursorMove
@ GHOST_kStandardCursorCrosshairB
@ GHOST_kStandardCursorDownArrow
@ GHOST_kStandardCursorEraser
@ GHOST_kStandardCursorDefault
@ GHOST_kStandardCursorEWScroll
@ GHOST_kStandardCursorRightSide
@ GHOST_kStandardCursorRightArrow
@ GHOST_kStandardCursorDestroy
@ GHOST_kStandardCursorCrosshairC
@ GHOST_kStandardCursorZoomOut
@ GHOST_kStandardCursorLeftSide
@ GHOST_kStandardCursorText
@ GHOST_kStandardCursorLeftArrow
@ GHOST_kEventWindowClose
@ GHOST_kEventWindowMove
@ GHOST_kEventWindowSize
@ GHOST_kEventDraggingDropDone
@ GHOST_kEventDraggingExited
@ GHOST_kEventNativeResolutionChange
@ GHOST_kEventDraggingUpdated
@ GHOST_kEventDraggingEntered
@ GHOST_kEventWindowActivate
@ GHOST_kEventWindowUpdate
@ GHOST_kEventWindowDeactivate
static const GHOST_TabletData GHOST_TABLET_DATA_NONE
GHOST_TDrawingContextType
GHOST_TWindowOrder
@ GHOST_kWindowOrderTop
GHOST_TSuccess
Definition GHOST_Types.h:87
@ GHOST_kFailure
Definition GHOST_Types.h:87
@ GHOST_kSuccess
Definition GHOST_Types.h:87
GHOST_TGrabCursorMode
@ GHOST_kGrabDisable
@ GHOST_kGrabHide
@ GHOST_kGrabNormal
GHOST_TDragnDropTypes
@ GHOST_kDragnDropTypeUnknown
@ GHOST_kDragnDropTypeFilenames
@ GHOST_kDragnDropTypeBitmap
@ GHOST_kDragnDropTypeString
static NSCursor * getImageCursor(GHOST_TStandardCursor shape, NSString *name, NSPoint hotspot)
static uint16_t uns16ReverseBits(uint16_t shrt)
static uint8_t uns8ReverseBits(uint8_t ch)
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
PyObject * self
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
int32_t m_l
int32_t m_r
virtual int32_t getHeight() const
virtual int32_t getWidth() const
GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const override
GHOST_TSuccess setCursorPosition(int32_t x, int32_t y) override
bool isDialog() const override
GHOST_TSuccess setProgressBar(float progress) override
GHOST_TSuccess setWindowCursorShape(GHOST_TStandardCursor shape) override
GHOST_SystemCocoa * m_systemCocoa
GHOST_TSuccess setState(GHOST_TWindowState state) override
std::string getTitle() const override
BlenderWindow * m_window
GHOST_TSuccess setClientHeight(uint32_t height) override
void clientToScreen(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const override
void setTitle(const char *title) override
GHOST_TSuccess setModifiedState(bool isUnsavedChanges) override
GHOST_TSuccess invalidate() override
CocoaOpenGLView * m_openGLView
CAMetalLayer * m_metalLayer
GHOST_TSuccess endProgressBar() override
NSCursor * getStandardCursor(GHOST_TStandardCursor cursor) const
GHOST_TWindowState getState() const override
GHOST_TSuccess setClientWidth(uint32_t width) override
GHOST_GPUDevice m_preferred_device
GHOST_TabletData m_tablet
GHOST_WindowCocoa(GHOST_SystemCocoa *systemCocoa, const char *title, int32_t left, int32_t bottom, uint32_t width, uint32_t height, GHOST_TWindowState state, GHOST_TDrawingContextType type, const bool stereoVisual, bool is_debug, bool dialog, GHOST_WindowCocoa *parentWindow, const GHOST_GPUDevice &preferred_device)
GHOST_TSuccess hasCursorShape(GHOST_TStandardCursor shape) override
void clientToScreenIntern(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const
void * getOSWindow() const override
void getClientBounds(GHOST_Rect &bounds) const override
void screenToClient(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const override
void getWindowBounds(GHOST_Rect &bounds) const override
GHOST_Context * newDrawingContext(GHOST_TDrawingContextType type) override
GHOST_TSuccess setWindowCursorGrab(GHOST_TGrabCursorMode mode) override
GHOST_TSuccess setWindowCustomCursorShape(uint8_t *bitmap, uint8_t *mask, int sizex, int sizey, int hotX, int hotY, bool canInvertColor) override
GHOST_TSuccess setOrder(GHOST_TWindowOrder order) override
void screenToClientIntern(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const
CocoaMetalView * m_metalView
GHOST_TSuccess setPath(const char *filepath) override
bool getValid() const override
GHOST_TSuccess setWindowCursorVisibility(bool visible) override
void loadCursor(bool visible, GHOST_TStandardCursor cursor) const
GHOST_TSuccess setClientSize(uint32_t width, uint32_t height) override
GHOST_Rect m_cursorGrabBounds
void setCursorGrabAccum(int32_t x, int32_t y)
GHOST_TGrabCursorMode m_cursorGrab
bool m_wantStereoVisual
int32_t m_cursorGrabInitPos[2]
GHOST_TStandardCursor getCursorShape() const override
GHOST_TSuccess setDrawingContextType(GHOST_TDrawingContextType type) override
GHOST_TSuccess releaseNativeHandles()
bool getCursorVisibility() const override
virtual GHOST_TSuccess activateDrawingContext() override
float m_nativePixelSize
GHOST_TSuccess updateDrawingContext()
virtual GHOST_TSuccess setModifiedState(bool isUnsavedChanges) override
bool m_progressBarVisible
virtual bool getValid() const override
#define printf
draw_view in_light_buf[] float
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
GHOST_SystemCocoa * systemCocoa
GHOST_WindowCocoa * windowCocoa
static ulong state[N]
static int left
unsigned short uint16_t
Definition stdint.h:79
unsigned int uint32_t
Definition stdint.h:80
signed int int32_t
Definition stdint.h:77
unsigned char uint8_t
Definition stdint.h:78