2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include "dali/public-api/adaptor-framework/window.h"
19 #include "dali/public-api/events/wheel-event.h"
20 #include <Carbon/Carbon.h>
21 #import <Cocoa/Cocoa.h>
24 #include <dali/internal/window-system/macos/window-base-mac.h>
27 #include <dali/public-api/object/any.h>
28 #include <dali/integration-api/debug.h>
31 #include <dali/internal/window-system/common/window-impl.h>
32 #include <dali/internal/window-system/common/window-render-surface.h>
33 #include <dali/internal/window-system/common/window-system.h>
37 using Dali::Internal::Adaptor::WindowBaseCocoa;
39 // Angle is default selecting CGL as its backend and because
40 // of that we are using NSOpenGLView. Ideally we should use
41 // Metal as the backend. When this happends, we must change
42 // the parent class to MTKView.
43 @interface CocoaView : NSOpenGLView
44 - (CocoaView *) initWithFrame:(NSRect) rect withImpl:(WindowBaseCocoa::Impl *) impl;
46 - (BOOL) wantsUpdateLayer;
47 - (BOOL) acceptsFirstResponder;
48 - (void) mouseDown:(NSEvent *) event;
49 - (void) mouseUp:(NSEvent *) event;
50 - (void) mouseDragged:(NSEvent *) event;
51 - (void) keyDown:(NSEvent *) event;
52 - (void) keyUp:(NSEvent *) event;
53 - (void) drawRect:(NSRect) dirtyRect;
54 - (void) prepareOpenGL;
57 @interface WindowDelegate : NSObject <NSWindowDelegate>
58 - (WindowDelegate *) init:(WindowBaseCocoa::Impl *) impl;
59 - (void) windowDidBecomeKey:(NSNotification *) notification;
60 - (void) windowDidResignKey:(NSNotification *) notification;
61 - (void) windowWillClose:(NSNotification *) notification;
64 namespace Dali::Internal::Adaptor
70 #if defined(DEBUG_ENABLED)
71 Debug::Filter* gWindowBaseLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_WINDOW_BASE" );
74 // Converts a y coordinate from top to bottom coordinate
75 CGFloat BottomYCoordinate(CGFloat topYCoordinate, CGFloat windowHeight) noexcept
77 const auto screen = [NSScreen.mainScreen frame];
78 return screen.size.height - windowHeight - topYCoordinate;
81 NSRect PositionSizeToRect(const PositionSize &positionSize, bool flipped = false) noexcept
83 // positionSize assumes top-left coordinate system
84 // Cocoa assumes bottom-left coordinate system
85 // If NSView isFlipped method returns YES, then it uses top-left coordinate system
86 const auto windowHeight = static_cast<CGFloat>(positionSize.height);
87 const auto yGiven = static_cast<CGFloat>(positionSize.y);
96 yWindow = BottomYCoordinate(yGiven, windowHeight);
103 .x = static_cast<CGFloat>(positionSize.x),
108 .width = static_cast<CGFloat>(positionSize.width),
109 .height = windowHeight,
114 } // unnamed namespace
116 struct WindowBaseCocoa::Impl final
119 NSWindowController *mWinController;
120 WindowBaseCocoa *mThis;
122 Impl(const Impl &rhs) = delete;
123 Impl &operator<(const Impl &rhs) = delete;
124 Impl(const Impl &&rhs) = delete;
125 Impl &operator<(const Impl &&rhs) = delete;
128 WindowBaseCocoa *pThis,
129 PositionSize positionSize,
136 void OnFocus(bool focus)
138 mThis->mFocusChangedSignal.Emit(focus);
141 // Handle mouse events
142 void OnMouse(NSEvent *event, PointState::Type state);
143 void OnMouseWheel(NSEvent *event);
144 void OnKey(NSEvent *event, Integration::KeyEvent::State keyState);
145 void OnWindowDamaged(const NSRect &rect);
149 mThis->mWindowRedrawRequestSignal.Emit();
153 uint32_t GetKeyModifiers(NSEvent *event) const noexcept;
154 std::string GetKeyName(NSEvent *event) const;
157 WindowBaseCocoa::Impl::Impl(
158 WindowBaseCocoa *pThis,
159 PositionSize positionSize,
164 constexpr NSUInteger style =
165 NSWindowStyleMaskTitled
166 | NSWindowStyleMaskClosable
167 | NSWindowStyleMaskMiniaturizable
168 | NSWindowStyleMaskResizable;
170 mWindow = [[NSWindow alloc] initWithContentRect:PositionSizeToRect(positionSize)
172 backing:NSBackingStoreBuffered
175 mWindow.alphaValue = static_cast<CGFloat>(!isTransparent);
176 mWinController = [[NSWindowController alloc] initWithWindow:mWindow];
178 mWindow.delegate = [[WindowDelegate alloc] init:this];
180 NSView *view = [[CocoaView alloc] initWithFrame:PositionSizeToRect(positionSize, true)
182 NSPoint origin{0, 0};
183 [view setFrameOrigin:origin];
185 mWindow.contentView = view;
187 [mWindow makeKeyAndOrderFront:nil];
190 WindowBaseCocoa::Impl::~Impl()
192 [mWinController close];
196 void WindowBaseCocoa::Impl::OnMouse(NSEvent *event, PointState::Type state)
198 Integration::Point point;
199 point.SetDeviceId(event.deviceID);
200 point.SetState(state);
201 auto p = [event locationInWindow];
202 auto [x, y] = [mWindow.contentView convertPoint:p fromView:nil];
203 point.SetScreenPosition(Vector2(x, y));
204 point.SetRadius(std::sqrt(x*x + y*y));
205 point.SetPressure(event.pressure);
209 point.SetAngle(Degree(0.0));
213 point.SetAngle(Radian(std::atan(y/x)));
217 gWindowBaseLogFilter,
219 "WindowBaseCocoa::Impl::OnMouse(%.1f, %.1f)\n",
224 // timestamp is given in seconds, the signal expects it in milliseconds
225 mThis->mTouchEventSignal.Emit(point, event.timestamp * 1000);
228 void WindowBaseCocoa::Impl::OnMouseWheel(NSEvent *event)
230 auto p = [event locationInWindow];
231 auto [x, y] = [mWindow.contentView convertPoint:p fromView:nil];
233 const auto modifiers = GetKeyModifiers(event);
234 const Vector2 vec(x, y);
235 const auto timestamp = event.timestamp * 1000;
237 if (event.scrollingDeltaY)
239 Integration::WheelEvent wheelEvent(
240 Integration::WheelEvent::MOUSE_WHEEL,
244 event.scrollingDeltaY < 0 ? -1 : 1,
248 mThis->mWheelEventSignal.Emit(wheelEvent);
251 if (event.scrollingDeltaX)
253 Integration::WheelEvent wheelEvent(
254 Integration::WheelEvent::MOUSE_WHEEL,
258 event.scrollingDeltaX < 0 ? -1 : 1,
262 mThis->mWheelEventSignal.Emit(wheelEvent);
266 void WindowBaseCocoa::Impl::OnKey(NSEvent *event, Integration::KeyEvent::State keyState)
268 const std::string empty;
270 Integration::KeyEvent keyEvent(
273 [event.characters UTF8String],
275 GetKeyModifiers(event),
276 event.timestamp * 1000,
281 Device::Subclass::NONE
285 gWindowBaseLogFilter,
287 "WindowBaseCocoa::Impl::OnKey(%s)\n",
288 [event.characters UTF8String]
291 mThis->mKeyEventSignal.Emit(keyEvent);
294 void WindowBaseCocoa::Impl::OnWindowDamaged(const NSRect &rect)
296 const DamageArea area(
303 mThis->mWindowDamagedSignal.Emit(area);
306 uint32_t WindowBaseCocoa::Impl::GetKeyModifiers(NSEvent *event) const noexcept
308 uint32_t modifiers = 0;
310 if (event.modifierFlags & NSEventModifierFlagShift)
315 if (event.modifierFlags & NSEventModifierFlagControl)
320 if (event.modifierFlags & NSEventModifierFlagCommand)
328 std::string WindowBaseCocoa::Impl::GetKeyName(NSEvent *event) const
330 switch (event.keyCode)
332 case kVK_Control: return "Control";
333 case kVK_Shift: return "Shift";
334 case kVK_Delete: return "Backspace";
335 case kVK_Command: return "Command";
336 case kVK_Tab: return "Tab";
337 case kVK_Return: return "Return";
338 case kVK_Escape: return "Escape";
339 case kVK_Space: return "Space";
340 case kVK_LeftArrow: return "Left";
341 case kVK_UpArrow: return "Up";
342 case kVK_RightArrow: return "Right";
343 case kVK_DownArrow: return "Down";
344 case kVK_ANSI_0: return "0";
345 case kVK_ANSI_1: return "1";
346 case kVK_ANSI_2: return "2";
347 case kVK_ANSI_3: return "3";
348 case kVK_ANSI_4: return "4";
349 case kVK_ANSI_5: return "5";
350 case kVK_ANSI_6: return "6";
351 case kVK_ANSI_7: return "7";
352 case kVK_ANSI_8: return "8";
353 case kVK_ANSI_9: return "9";
354 default: return [event.characters UTF8String];
360 WindowBaseCocoa::WindowBaseCocoa(PositionSize positionSize, Any surface, bool isTransparent)
361 : mImpl(std::make_unique<Impl>(this, positionSize, surface, isTransparent))
365 WindowBaseCocoa::~WindowBaseCocoa()
369 Any WindowBaseCocoa::GetNativeWindow()
371 return mImpl->mWindow;
374 int WindowBaseCocoa::GetNativeWindowId()
376 return mImpl->mWindow.windowNumber;
379 EGLNativeWindowType WindowBaseCocoa::CreateEglWindow(int width, int height)
381 // XXX: this method is called from a secondary thread, but
382 // we can only resize the window from the main thread
383 //PositionSize size(0, 0, width, height);
385 return mImpl->mWindow.contentView.layer;
388 void WindowBaseCocoa::DestroyEglWindow()
392 void WindowBaseCocoa::SetEglWindowRotation( int angle )
396 void WindowBaseCocoa::SetEglWindowBufferTransform( int angle )
400 void WindowBaseCocoa::SetEglWindowTransform( int angle )
404 void WindowBaseCocoa::ResizeEglWindow( PositionSize positionSize )
406 Resize(positionSize);
409 bool WindowBaseCocoa::IsEglWindowRotationSupported()
414 void WindowBaseCocoa::Move( PositionSize positionSize )
417 .x = static_cast<CGFloat>(positionSize.x),
418 .y = static_cast<CGFloat>(positionSize.y),
421 [mImpl->mWindow setFrameTopLeftPoint:p];
424 void WindowBaseCocoa::Resize( PositionSize positionSize )
426 auto r = mImpl->mWindow.frame;
427 r.size.width = static_cast<CGFloat>(positionSize.width);
428 r.size.height = static_cast<CGFloat>(positionSize.height);
429 [mImpl->mWindow setFrame:r display:YES];
433 .width = r.size.width,
434 .height = r.size.height,
437 [mImpl->mWindow.contentView setFrameSize:size];
441 void WindowBaseCocoa::MoveResize( PositionSize positionSize )
443 [mImpl->mWindow setFrame: PositionSizeToRect(positionSize) display:YES];
447 .width = static_cast<CGFloat>(positionSize.width),
448 .height = static_cast<CGFloat>(positionSize.height),
451 [mImpl->mWindow.contentView setFrameSize:size];
454 void WindowBaseCocoa::SetClass( const std::string& name, const std::string& className )
458 void WindowBaseCocoa::Raise()
460 [mImpl->mWindow orderFront:nil];
463 void WindowBaseCocoa::Lower()
465 [mImpl->mWindow orderBack:nil];
468 void WindowBaseCocoa::Activate()
470 [mImpl->mWinController showWindow:nil];
473 void WindowBaseCocoa::SetAvailableAnlges( const std::vector< int >& angles )
477 void WindowBaseCocoa::SetPreferredAngle( int angle )
481 void WindowBaseCocoa::SetAcceptFocus( bool accept )
485 void WindowBaseCocoa::Show()
487 [mImpl->mWinController showWindow:nil];
490 void WindowBaseCocoa::Hide()
492 [mImpl->mWindow orderOut:nil];
495 unsigned int WindowBaseCocoa::GetSupportedAuxiliaryHintCount() const
500 std::string WindowBaseCocoa::GetSupportedAuxiliaryHint( unsigned int index ) const
502 return std::string();
505 unsigned int WindowBaseCocoa::AddAuxiliaryHint( const std::string& hint, const std::string& value )
510 bool WindowBaseCocoa::RemoveAuxiliaryHint( unsigned int id )
515 bool WindowBaseCocoa::SetAuxiliaryHintValue( unsigned int id, const std::string& value )
520 std::string WindowBaseCocoa::GetAuxiliaryHintValue( unsigned int id ) const
522 return std::string();
525 unsigned int WindowBaseCocoa::GetAuxiliaryHintId( const std::string& hint ) const
530 void WindowBaseCocoa::SetInputRegion( const Rect< int >& inputRegion )
534 void WindowBaseCocoa::SetType( Dali::WindowType type )
538 WindowOperationResult WindowBaseCocoa::SetNotificationLevel( WindowNotificationLevel level )
540 return WindowOperationResult::NOT_SUPPORTED;
543 WindowNotificationLevel WindowBaseCocoa::GetNotificationLevel() const
545 return WindowNotificationLevel::NONE;
548 void WindowBaseCocoa::SetOpaqueState( bool opaque )
552 WindowOperationResult WindowBaseCocoa::SetScreenOffMode(WindowScreenOffMode screenOffMode)
554 return WindowOperationResult::NOT_SUPPORTED;
557 WindowScreenOffMode WindowBaseCocoa::GetScreenOffMode() const
559 return WindowScreenOffMode::TIMEOUT;
562 WindowOperationResult WindowBaseCocoa::SetBrightness( int brightness )
564 return WindowOperationResult::NOT_SUPPORTED;
567 int WindowBaseCocoa::GetBrightness() const
572 bool WindowBaseCocoa::GrabKey( Dali::KEY key, KeyGrab::KeyGrabMode grabMode )
577 bool WindowBaseCocoa::UngrabKey( Dali::KEY key )
582 bool WindowBaseCocoa::GrabKeyList(
583 const Dali::Vector< Dali::KEY >& key,
584 const Dali::Vector< KeyGrab::KeyGrabMode >& grabMode,
585 Dali::Vector< bool >& result
591 bool WindowBaseCocoa::UngrabKeyList(
592 const Dali::Vector< Dali::KEY >& key,
593 Dali::Vector< bool >& result
599 void WindowBaseCocoa::GetDpi(
600 unsigned int& dpiHorizontal,
601 unsigned int& dpiVertical
604 auto *screen = [NSScreen mainScreen];
605 NSSize res = [screen.deviceDescription[NSDeviceResolution] sizeValue];
606 dpiHorizontal = res.width;
607 dpiVertical = res.height;
610 int WindowBaseCocoa::GetOrientation() const
615 int WindowBaseCocoa::GetScreenRotationAngle()
620 void WindowBaseCocoa::SetWindowRotationAngle( int degree )
624 void WindowBaseCocoa::WindowRotationCompleted( int degree, int width, int height )
628 void WindowBaseCocoa::SetTransparency( bool transparent )
630 mImpl->mWindow.alphaValue = static_cast<CGFloat>(!transparent);
633 void WindowBaseCocoa::SetParent( WindowBase* parentWinBase )
635 auto &parent = dynamic_cast<WindowBaseCocoa&>(*parentWinBase);
636 [mImpl->mWindow setParentWindow:parent.mImpl->mWindow];
639 int WindowBaseCocoa::CreateFrameRenderedSyncFence()
644 int WindowBaseCocoa::CreateFramePresentedSyncFence()
649 } // namespace Dali::Internal::Adaptor
651 @implementation CocoaView
653 WindowBaseCocoa::Impl *mImpl;
656 - (CocoaView *) initWithFrame:(NSRect) rect withImpl:(WindowBaseCocoa::Impl *) impl
658 self = [super initWithFrame:rect];
662 self.wantsLayer = YES;
663 self.wantsBestResolutionOpenGLSurface = NO;
674 - (BOOL) wantsUpdateLayer
679 - (BOOL) acceptsFirstResponder
684 - (void) mouseDown:(NSEvent *) event
686 mImpl->OnMouse(event, Dali::PointState::DOWN);
689 - (void) mouseUp:(NSEvent *) event
691 mImpl->OnMouse(event, Dali::PointState::UP);
694 - (void) mouseDragged:(NSEvent *) event
696 mImpl->OnMouse(event, Dali::PointState::MOTION);
699 - (void) keyDown:(NSEvent *) event
701 mImpl->OnKey(event, Dali::Integration::KeyEvent::DOWN);
704 - (void) keyUp:(NSEvent *) event
706 mImpl->OnKey(event, Dali::Integration::KeyEvent::UP);
709 - (void) drawRect:(NSRect) dirtyRect
712 Dali::Internal::Adaptor::gWindowBaseLogFilter,
714 "-[CocoaView drawRect:(%.1f, %.1f, %.1f, %.1f)]\n",
717 dirtyRect.size.width,
718 dirtyRect.size.height
721 mImpl->OnWindowDamaged(dirtyRect);
724 - (void) prepareOpenGL
726 auto ctx = CGLGetCurrentContext();
727 DALI_ASSERT_ALWAYS(ctx);
729 // Enable multithreading
730 if (auto err = CGLEnable(ctx, kCGLCEMPEngine); err != kCGLNoError)
732 DALI_LOG_ERROR("%s - %s", __PRETTY_FUNCTION__, CGLErrorString(err));
737 @implementation WindowDelegate
739 WindowBaseCocoa::Impl *mImpl;
742 - (WindowDelegate *) init:(Dali::Internal::Adaptor::WindowBaseCocoa::Impl *) impl
752 - (void) windowDidBecomeKey:(NSNotification *) notification
754 mImpl->OnFocus(true);
757 - (void) windowDidResignKey:(NSNotification *) notification
759 mImpl->OnFocus(false);
762 - (void) windowWillClose:(NSNotification *) notification