2 * Copyright (c) 2022 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 std::string WindowBaseCocoa::GetNativeWindowResourceId()
381 return std::string();
384 EGLNativeWindowType WindowBaseCocoa::CreateEglWindow(int width, int height)
386 // XXX: this method is called from a secondary thread, but
387 // we can only resize the window from the main thread
388 //PositionSize size(0, 0, width, height);
390 return mImpl->mWindow.contentView.layer;
393 void WindowBaseCocoa::DestroyEglWindow()
397 void WindowBaseCocoa::SetEglWindowRotation( int angle )
401 void WindowBaseCocoa::SetEglWindowBufferTransform( int angle )
405 void WindowBaseCocoa::SetEglWindowTransform( int angle )
409 void WindowBaseCocoa::ResizeEglWindow( PositionSize positionSize )
411 Resize(positionSize);
414 bool WindowBaseCocoa::IsEglWindowRotationSupported()
419 void WindowBaseCocoa::Move( PositionSize positionSize )
422 .x = static_cast<CGFloat>(positionSize.x),
423 .y = static_cast<CGFloat>(positionSize.y),
426 [mImpl->mWindow setFrameTopLeftPoint:p];
429 void WindowBaseCocoa::Resize( PositionSize positionSize )
431 auto r = mImpl->mWindow.frame;
432 r.size.width = static_cast<CGFloat>(positionSize.width);
433 r.size.height = static_cast<CGFloat>(positionSize.height);
434 [mImpl->mWindow setFrame:r display:YES];
438 .width = r.size.width,
439 .height = r.size.height,
442 [mImpl->mWindow.contentView setFrameSize:size];
446 void WindowBaseCocoa::MoveResize( PositionSize positionSize )
448 [mImpl->mWindow setFrame: PositionSizeToRect(positionSize) display:YES];
452 .width = static_cast<CGFloat>(positionSize.width),
453 .height = static_cast<CGFloat>(positionSize.height),
456 [mImpl->mWindow.contentView setFrameSize:size];
459 void WindowBaseCocoa::SetClass( const std::string& name, const std::string& className )
463 void WindowBaseCocoa::Raise()
465 [mImpl->mWindow orderFront:nil];
468 void WindowBaseCocoa::Lower()
470 [mImpl->mWindow orderBack:nil];
473 void WindowBaseCocoa::Activate()
475 [mImpl->mWinController showWindow:nil];
478 void WindowBaseCocoa::Maximize(bool maximize)
482 bool WindowBaseCocoa::IsMaximized() const
487 void WindowBaseCocoa::Minimize(bool minimize)
491 bool WindowBaseCocoa::IsMinimized() const
496 void WindowBaseCocoa::SetAvailableAnlges( const std::vector< int >& angles )
500 void WindowBaseCocoa::SetPreferredAngle( int angle )
504 void WindowBaseCocoa::SetAcceptFocus( bool accept )
508 void WindowBaseCocoa::Show()
510 [mImpl->mWinController showWindow:nil];
513 void WindowBaseCocoa::Hide()
515 [mImpl->mWindow orderOut:nil];
518 unsigned int WindowBaseCocoa::GetSupportedAuxiliaryHintCount() const
523 std::string WindowBaseCocoa::GetSupportedAuxiliaryHint( unsigned int index ) const
525 return std::string();
528 unsigned int WindowBaseCocoa::AddAuxiliaryHint( const std::string& hint, const std::string& value )
533 bool WindowBaseCocoa::RemoveAuxiliaryHint( unsigned int id )
538 bool WindowBaseCocoa::SetAuxiliaryHintValue( unsigned int id, const std::string& value )
543 std::string WindowBaseCocoa::GetAuxiliaryHintValue( unsigned int id ) const
545 return std::string();
548 unsigned int WindowBaseCocoa::GetAuxiliaryHintId( const std::string& hint ) const
553 void WindowBaseCocoa::SetInputRegion( const Rect< int >& inputRegion )
557 void WindowBaseCocoa::SetType( Dali::WindowType type )
561 Dali::WindowType WindowBaseCocoa::GetType() const
563 return Dali::WindowType::NORMAL;
566 WindowOperationResult WindowBaseCocoa::SetNotificationLevel( WindowNotificationLevel level )
568 return WindowOperationResult::NOT_SUPPORTED;
571 WindowNotificationLevel WindowBaseCocoa::GetNotificationLevel() const
573 return WindowNotificationLevel::NONE;
576 void WindowBaseCocoa::SetOpaqueState( bool opaque )
580 WindowOperationResult WindowBaseCocoa::SetScreenOffMode(WindowScreenOffMode screenOffMode)
582 return WindowOperationResult::NOT_SUPPORTED;
585 WindowScreenOffMode WindowBaseCocoa::GetScreenOffMode() const
587 return WindowScreenOffMode::TIMEOUT;
590 WindowOperationResult WindowBaseCocoa::SetBrightness( int brightness )
592 return WindowOperationResult::NOT_SUPPORTED;
595 int WindowBaseCocoa::GetBrightness() const
600 bool WindowBaseCocoa::GrabKey( Dali::KEY key, KeyGrab::KeyGrabMode grabMode )
605 bool WindowBaseCocoa::UngrabKey( Dali::KEY key )
610 bool WindowBaseCocoa::GrabKeyList(
611 const Dali::Vector< Dali::KEY >& key,
612 const Dali::Vector< KeyGrab::KeyGrabMode >& grabMode,
613 Dali::Vector< bool >& result
619 bool WindowBaseCocoa::UngrabKeyList(
620 const Dali::Vector< Dali::KEY >& key,
621 Dali::Vector< bool >& result
627 void WindowBaseCocoa::GetDpi(
628 unsigned int& dpiHorizontal,
629 unsigned int& dpiVertical
632 auto *screen = [NSScreen mainScreen];
633 NSSize res = [screen.deviceDescription[NSDeviceResolution] sizeValue];
634 dpiHorizontal = res.width;
635 dpiVertical = res.height;
638 int WindowBaseCocoa::GetOrientation() const
643 int WindowBaseCocoa::GetScreenRotationAngle()
648 void WindowBaseCocoa::SetWindowRotationAngle( int degree )
652 void WindowBaseCocoa::WindowRotationCompleted( int degree, int width, int height )
656 void WindowBaseCocoa::SetTransparency( bool transparent )
658 mImpl->mWindow.alphaValue = static_cast<CGFloat>(!transparent);
661 void WindowBaseCocoa::SetParent(WindowBase* parentWinBase, bool belowParent)
663 auto &parent = dynamic_cast<WindowBaseCocoa&>(*parentWinBase);
664 [mImpl->mWindow setParentWindow:parent.mImpl->mWindow];
667 int WindowBaseCocoa::CreateFrameRenderedSyncFence()
672 int WindowBaseCocoa::CreateFramePresentedSyncFence()
677 void WindowBaseCocoa::SetPositionSizeWithAngle(PositionSize positionSize, int angle)
681 void WindowBaseCocoa::InitializeIme()
685 void WindowBaseCocoa::ImeWindowReadyToRender()
689 void WindowBaseCocoa::RequestMoveToServer()
693 void WindowBaseCocoa::RequestResizeToServer(WindowResizeDirection direction)
697 void WindowBaseCocoa::EnableFloatingMode(bool enable)
701 bool WindowBaseCocoa::IsFloatingModeEnabled() const
706 void WindowBaseCocoa::IncludeInputRegion(const Rect<int>& inputRegion)
710 void WindowBaseCocoa::ExcludeInputRegion(const Rect<int>& inputRegion)
714 } // namespace Dali::Internal::Adaptor
716 @implementation CocoaView
718 WindowBaseCocoa::Impl *mImpl;
721 - (CocoaView *) initWithFrame:(NSRect) rect withImpl:(WindowBaseCocoa::Impl *) impl
723 self = [super initWithFrame:rect];
727 self.wantsLayer = YES;
728 self.wantsBestResolutionOpenGLSurface = NO;
739 - (BOOL) wantsUpdateLayer
744 - (BOOL) acceptsFirstResponder
749 - (void) mouseDown:(NSEvent *) event
751 mImpl->OnMouse(event, Dali::PointState::DOWN);
754 - (void) mouseUp:(NSEvent *) event
756 mImpl->OnMouse(event, Dali::PointState::UP);
759 - (void) mouseDragged:(NSEvent *) event
761 mImpl->OnMouse(event, Dali::PointState::MOTION);
764 - (void) keyDown:(NSEvent *) event
766 mImpl->OnKey(event, Dali::Integration::KeyEvent::DOWN);
769 - (void) keyUp:(NSEvent *) event
771 mImpl->OnKey(event, Dali::Integration::KeyEvent::UP);
774 - (void) drawRect:(NSRect) dirtyRect
777 Dali::Internal::Adaptor::gWindowBaseLogFilter,
779 "-[CocoaView drawRect:(%.1f, %.1f, %.1f, %.1f)]\n",
782 dirtyRect.size.width,
783 dirtyRect.size.height
786 mImpl->OnWindowDamaged(dirtyRect);
789 - (void) prepareOpenGL
791 auto ctx = CGLGetCurrentContext();
792 DALI_ASSERT_ALWAYS(ctx);
794 // Enable multithreading
795 if (auto err = CGLEnable(ctx, kCGLCEMPEngine); err != kCGLNoError)
797 DALI_LOG_ERROR("%s - %s", __PRETTY_FUNCTION__, CGLErrorString(err));
802 @implementation WindowDelegate
804 WindowBaseCocoa::Impl *mImpl;
807 - (WindowDelegate *) init:(Dali::Internal::Adaptor::WindowBaseCocoa::Impl *) impl
817 - (void) windowDidBecomeKey:(NSNotification *) notification
819 mImpl->OnFocus(true);
822 - (void) windowDidResignKey:(NSNotification *) notification
824 mImpl->OnFocus(false);
827 - (void) windowWillClose:(NSNotification *) notification