diff --git a/GenerateProjectFiles.command b/GenerateProjectFiles.command index b6a9c7c89..9dab57ad3 100755 --- a/GenerateProjectFiles.command +++ b/GenerateProjectFiles.command @@ -12,6 +12,6 @@ cd "`dirname "$0"`" bash ./Development/Scripts/Mac/CallBuildTool.sh --genproject "$@" # Build bindings for all editor configurations -echo Building C# bindings... +#echo Building C# bindings... # TODO: Detect the correct architecture here -Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor +#Binaries/Tools/Flax.Build -build -BuildBindingsOnly -arch=ARM64 -platform=Mac --buildTargets=FlaxEditor diff --git a/Source/Engine/Platform/Mac/MacWindow.DragDrop.cpp b/Source/Engine/Platform/Mac/MacWindow.DragDrop.cpp new file mode 100644 index 000000000..3756ba9de --- /dev/null +++ b/Source/Engine/Platform/Mac/MacWindow.DragDrop.cpp @@ -0,0 +1,1153 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#if (PLATFORM_MAC && !PLATFORM_SDL) || (PLATFORM_MAC && PLATFORM_SDL && INCLUDED_IN_SDL) + +#define INBASEFILE 0 + +#define MACWINDOW Window + +#include "../Window.h" +#include "Engine/Platform/Apple/AppleUtils.h" +#include "Engine/Platform/WindowsManager.h" +#include "Engine/Platform/IGuiData.h" +#if USE_EDITOR +#include "Engine/Platform/CriticalSection.h" +#include "Engine/Platform/Base/DragDropHelper.h" +#endif +#include "Engine/Core/Log.h" +#include "Engine/Input/Input.h" +#include "Engine/Input/Mouse.h" +#include "Engine/Input/Keyboard.h" +#include "Engine/Graphics/RenderTask.h" + +#if !PLATFORM_SDL +#include +#include +#include + +namespace MacImpl +{ +#if USE_EDITOR + // Data for drawing window while doing drag&drop on Mac (engine is paused during platform tick) + CriticalSection MacImpl::MacDragLocker; + NSDraggingSession* MacImpl::MacDragSession = nullptr; + DoDragDropJob* MacImpl::MacDragJob = nullptr; +#endif +} +#endif + +inline bool IsWindowInvalid(Window* win) +{ + WindowsManager::WindowsLocker.Lock(); + const bool hasWindow = WindowsManager::Windows.Contains(win); + WindowsManager::WindowsLocker.Unlock(); + return !hasWindow || !win; +} + +#if INBASEFILE + +KeyboardKeys GetKey(NSEvent* event) +{ + switch ([event keyCode]) + { + case 0x00: return KeyboardKeys::A; + case 0x01: return KeyboardKeys::S; + case 0x02: return KeyboardKeys::D; + case 0x03: return KeyboardKeys::F; + case 0x04: return KeyboardKeys::H; + case 0x05: return KeyboardKeys::G; + case 0x06: return KeyboardKeys::Z; + case 0x07: return KeyboardKeys::X; + case 0x08: return KeyboardKeys::C; + case 0x09: return KeyboardKeys::V; + case 0x0A: return KeyboardKeys::W; + case 0x0B: return KeyboardKeys::B; + case 0x0C: return KeyboardKeys::Q; + case 0x0D: return KeyboardKeys::W; + case 0x0E: return KeyboardKeys::E; + case 0x0F: return KeyboardKeys::R; + + case 0x10: return KeyboardKeys::Y; + case 0x11: return KeyboardKeys::T; + case 0x12: return KeyboardKeys::Alpha1; + case 0x13: return KeyboardKeys::Alpha2; + case 0x14: return KeyboardKeys::Alpha3; + case 0x15: return KeyboardKeys::Alpha4; + case 0x16: return KeyboardKeys::Alpha6; + case 0x17: return KeyboardKeys::Alpha5; + case 0x18: return KeyboardKeys::Plus; + case 0x19: return KeyboardKeys::Alpha9; + case 0x1A: return KeyboardKeys::Alpha7; + case 0x1B: return KeyboardKeys::Minus; + case 0x1C: return KeyboardKeys::Alpha8; + case 0x1D: return KeyboardKeys::Alpha0; + case 0x1E: return KeyboardKeys::RightBracket; + case 0x1F: return KeyboardKeys::O; + + case 0x20: return KeyboardKeys::U; + case 0x21: return KeyboardKeys::LeftBracket; + case 0x22: return KeyboardKeys::I; + case 0x23: return KeyboardKeys::P; + case 0x24: return KeyboardKeys::Return; + case 0x25: return KeyboardKeys::L; + case 0x26: return KeyboardKeys::J; + case 0x27: return KeyboardKeys::Quote; + case 0x28: return KeyboardKeys::K; + case 0x29: return KeyboardKeys::Colon; + case 0x2A: return KeyboardKeys::Backslash; + case 0x2B: return KeyboardKeys::Comma; + case 0x2C: return KeyboardKeys::Slash; + case 0x2D: return KeyboardKeys::N; + case 0x2E: return KeyboardKeys::M; + case 0x2F: return KeyboardKeys::Period; + + case 0x30: return KeyboardKeys::Tab; + case 0x31: return KeyboardKeys::Spacebar; + case 0x32: return KeyboardKeys::BackQuote; + case 0x33: return KeyboardKeys::Backspace; + //case 0x34: + case 0x35: return KeyboardKeys::Escape; + case 0x36: return KeyboardKeys::Control; // Command (right) + case 0x37: return KeyboardKeys::Control; // Command (left) + case 0x38: return KeyboardKeys::Shift; + case 0x39: return KeyboardKeys::Capital; + case 0x3A: return KeyboardKeys::Alt; + case 0x3B: return KeyboardKeys::Control; + case 0x3C: return KeyboardKeys::Shift; + case 0x3D: return KeyboardKeys::Alt; + case 0x3E: return KeyboardKeys::Control; + //case 0x3F: Function + + case 0x40: return KeyboardKeys::F17; + case 0x41: return KeyboardKeys::NumpadDecimal; + //case 0x42: + case 0x43: return KeyboardKeys::NumpadMultiply; + //case 0x44: + case 0x45: return KeyboardKeys::NumpadAdd; + //case 0x46: + //case 0x47: KeypadClear + case 0x48: return KeyboardKeys::VolumeUp; + case 0x49: return KeyboardKeys::VolumeDown; + case 0x4A: return KeyboardKeys::VolumeMute; + case 0x4B: return KeyboardKeys::NumpadDivide; + case 0x4C: return KeyboardKeys::Return; + //case 0x4D: + case 0x4E: return KeyboardKeys::NumpadSubtract; + case 0x4F: return KeyboardKeys::F18; + + case 0x50: return KeyboardKeys::F19; + //case 0x51: NumpadEquals + case 0x52: return KeyboardKeys::Numpad0; + case 0x53: return KeyboardKeys::Numpad1; + case 0x54: return KeyboardKeys::Numpad2; + case 0x55: return KeyboardKeys::Numpad3; + case 0x56: return KeyboardKeys::Numpad4; + case 0x57: return KeyboardKeys::Numpad5; + case 0x58: return KeyboardKeys::Numpad6; + case 0x59: return KeyboardKeys::Numpad7; + case 0x5A: return KeyboardKeys::F20; + case 0x5B: return KeyboardKeys::Numpad8; + case 0x5C: return KeyboardKeys::Numpad9; + //case 0x5D: Yen + //case 0x5E: Underscore + //case 0x5F: KeypadComma + + case 0x60: return KeyboardKeys::F5; + case 0x61: return KeyboardKeys::F6; + case 0x62: return KeyboardKeys::F7; + case 0x63: return KeyboardKeys::F3; + case 0x64: return KeyboardKeys::F8; + case 0x65: return KeyboardKeys::F9; + //case 0x66: Eisu + case 0x67: return KeyboardKeys::F11; + case 0x68: return KeyboardKeys::Kana; + case 0x69: return KeyboardKeys::F13; + case 0x6A: return KeyboardKeys::F16; + case 0x6B: return KeyboardKeys::F14; + //case 0x6C: + case 0x6D: return KeyboardKeys::F10; + //case 0x6E: + case 0x6F: return KeyboardKeys::F12; + + //case 0x70: + case 0x71: return KeyboardKeys::F15; + case 0x72: return KeyboardKeys::Help; + case 0x73: return KeyboardKeys::Home; + case 0x74: return KeyboardKeys::PageUp; + case 0x75: return KeyboardKeys::Delete; + case 0x76: return KeyboardKeys::F4; + case 0x77: return KeyboardKeys::End; + case 0x78: return KeyboardKeys::F2; + case 0x79: return KeyboardKeys::PageDown; + case 0x7A: return KeyboardKeys::F1; + case 0x7B: return KeyboardKeys::ArrowLeft; + case 0x7C: return KeyboardKeys::ArrowRight; + case 0x7D: return KeyboardKeys::ArrowDown; + case 0x7E: return KeyboardKeys::ArrowUp; + //case 0x7F: + + default: + return KeyboardKeys::None; + } +} + +#endif + +Float2 GetWindowTitleSize(const MACWINDOW* window) +{ + Float2 size = Float2::Zero; + if (window->GetSettings().HasBorder) + { + NSRect frameStart = [(NSWindow*)window->GetNativePtr() frameRectForContentRect:NSMakeRect(0, 0, 0, 0)]; + size.Y = frameStart.size.height; + } + return size * MacPlatform::ScreenScale; +} + +Float2 GetMousePosition(MACWINDOW* window, NSEvent* event) +{ + NSRect frame = [(NSWindow*)window->GetNativePtr() frame]; + NSPoint point = [event locationInWindow]; + return Float2(point.x, frame.size.height - point.y) * MacPlatform::ScreenScale - GetWindowTitleSize(window); +} + +class MacDropData : public IGuiData +{ +public: + Type CurrentType; + String AsText; + Array AsFiles; + + Type GetType() const override + { + return CurrentType; + } + String GetAsText() const override + { + return AsText; + } + void GetAsFiles(Array* files) const override + { + files->Add(AsFiles); + } +}; + +void GetDragDropData(const MACWINDOW* window, id sender, Float2& mousePos, MacDropData& dropData) +{ + NSRect frame = [(NSWindow*)window->GetNativePtr() frame]; + NSPoint point = [sender draggingLocation]; + mousePos = Float2(point.x, frame.size.height - point.y) * MacPlatform::ScreenScale - GetWindowTitleSize(window); + NSPasteboard* pasteboard = [sender draggingPasteboard]; + if ([[pasteboard types] containsObject:NSPasteboardTypeString]) + { + dropData.CurrentType = IGuiData::Type::Text; + dropData.AsText = AppleUtils::ToString((CFStringRef)[pasteboard stringForType:NSPasteboardTypeString]); + } + else + { + dropData.CurrentType = IGuiData::Type::Files; + NSArray* files = [pasteboard readObjectsForClasses:@[[NSURL class]] options:nil]; + for (int32 i = 0; i < [files count]; i++) + { + NSString* url = [[files objectAtIndex:i] path]; + NSString* file = [NSURL URLWithString:url].path; + dropData.AsFiles.Add(AppleUtils::ToString((CFStringRef)file)); + } + } +} + +NSDragOperation GetDragDropOperation(DragDropEffect dragDropEffect) +{ + NSDragOperation result = NSDragOperationCopy; + switch (dragDropEffect) + { + case DragDropEffect::None: + //result = NSDragOperationNone; + break; + case DragDropEffect::Copy: + result = NSDragOperationCopy; + break; + case DragDropEffect::Move: + result = NSDragOperationMove; + break; + case DragDropEffect::Link: + result = NSDragOperationLink; + break; + } + return result; +} + +@interface MacWindowImpl : NSWindow +{ + MACWINDOW* Window; +} + +- (void)setWindow:(MACWINDOW*)window; + +@end + +@implementation MacWindowImpl + +- (BOOL)canBecomeKeyWindow +{ + if (Window && !Window->GetSettings().AllowInput) + { + return NO; + } + return YES; +} + +- (void)windowDidBecomeKey:(NSNotification*)notification +{ + // Handle resizing to be sure that content has valid size when window was resized + [self windowDidResize:notification]; + + if (IsWindowInvalid(Window)) return; + Window->OnGotFocus(); +} + +- (void)windowDidResignKey:(NSNotification*)notification +{ + if (IsWindowInvalid(Window)) return; + Window->OnLostFocus(); +} + +- (void)windowWillClose:(NSNotification*)notification +{ + [self setDelegate: nil]; + if (IsWindowInvalid(Window)) return; + Window->Close(ClosingReason::User); +} + +static void ConvertNSRect(NSScreen *screen, NSRect *r) +{ + r->origin.y = CGDisplayPixelsHigh(kCGDirectMainDisplay) - r->origin.y - r->size.height; +} + +#if INBASEFILE + +- (void)windowDidResize:(NSNotification*)notification +{ + NSView* view = [self contentView]; + const float screenScale = MacPlatform::ScreenScale; + NSWindow* nswindow = (NSWindow*)Window->GetNativePtr(); + NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]]; + ConvertNSRect([nswindow screen], &rect); + + // Rescale contents + CALayer* layer = [view layer]; + if (layer) + layer.contentsScale = screenScale; + + // Resize window + Window->CheckForResize((float)rect.size.width * screenScale, (float)rect.size.height * screenScale); +} + +#endif + +- (void)setWindow:(MACWINDOW*)window +{ + Window = window; +} + +@end + +@interface MacViewImpl : NSView +{ + MACWINDOW* Window; + NSTrackingArea* TrackingArea; + bool IsMouseOver; +} + +- (void)setWindow:(MACWINDOW*)window; +- (CALayer*)makeBackingLayer; +- (BOOL)wantsUpdateLayer; + +@end + +@implementation MacViewImpl + +- (void)dealloc +{ + if (TrackingArea != nil) + { + [self removeTrackingArea:TrackingArea]; + [TrackingArea release]; + } + [super dealloc]; +} + +- (void)setWindow:(MACWINDOW*)window +{ + Window = window; + TrackingArea = nil; + IsMouseOver = false; +} + +- (CALayer*)makeBackingLayer +{ + return [[CAMetalLayer class] layer]; +} + +- (BOOL)wantsUpdateLayer +{ + return YES; +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +#if INBASEFILE + +- (void)updateTrackingAreas +{ + [super updateTrackingAreas]; + + if (TrackingArea != nil) + { + [self removeTrackingArea:TrackingArea]; + [TrackingArea release]; + } + + NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways; + TrackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; + [self addTrackingArea:TrackingArea]; +} + +- (void)keyDown:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + KeyboardKeys key = GetKey(event); + if (key != KeyboardKeys::None) + Input::Keyboard->OnKeyDown(key, Window); + + // Send a text input event + switch (key) + { + // Ignore text from special keys + case KeyboardKeys::Delete: + case KeyboardKeys::Backspace: + case KeyboardKeys::ArrowLeft: + case KeyboardKeys::ArrowRight: + case KeyboardKeys::ArrowUp: + case KeyboardKeys::ArrowDown: + return; + } + NSString* text = [event characters]; + int32 length = [text length]; + if (length > 0) + { + unichar buffer[16]; + if (length >= 16) + length = 15; + [text getCharacters:buffer range:NSMakeRange(0, length)]; + Input::Keyboard->OnCharInput((Char)buffer[0], Window); + } +} + +- (void)keyUp:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + KeyboardKeys key = GetKey(event); + if (key != KeyboardKeys::None) + Input::Keyboard->OnKeyUp(key, Window); +} + + +- (void)flagsChanged:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + int32 modMask; + int32 keyCode = [event keyCode]; + if (keyCode == 0x36 || keyCode == 0x37) + modMask = NSEventModifierFlagCommand; + else if (keyCode == 0x38 || keyCode == 0x3c) + modMask = NSEventModifierFlagShift; + else if (keyCode == 0x3a || keyCode == 0x3d) + modMask = NSEventModifierFlagOption; + else if (keyCode == 0x3b || keyCode == 0x3e) + modMask = NSEventModifierFlagControl; + else + return; + KeyboardKeys key = GetKey(event); + if (key != KeyboardKeys::None) + { + int32 modifierFlags = [event modifierFlags]; + if ((modifierFlags & modMask) == modMask) + Input::Keyboard->OnKeyDown(key, Window); + else + Input::Keyboard->OnKeyUp(key, Window); + } +} + +- (void)scrollWheel:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + Float2 mousePos = GetMousePosition(Window, event); + double deltaX = [event scrollingDeltaX]; + double deltaY = [event scrollingDeltaY]; + if ([event hasPreciseScrollingDeltas]) + { + deltaX *= 0.03; + deltaY *= 0.03; + } + // TODO: add support for scroll delta as Float2 in the engine + Input::Mouse->OnMouseWheel(Window->ClientToScreen(mousePos), deltaY, Window); +} + +- (void)mouseMoved:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + Float2 mousePos = GetMousePosition(Window, event); + if (Window->IsMouseTracking()) + return; // Skip mouse events when tracking mouse (handled in MacWindow::OnUpdate) + if (!IsMouseOver) + return; + Input::Mouse->OnMouseMove(Window->ClientToScreen(mousePos), Window); +} + +- (void)mouseEntered:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + IsMouseOver = true; + Window->SetIsMouseOver(true); +} + +- (void)mouseExited:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + IsMouseOver = false; + Window->SetIsMouseOver(false); +} + +- (void)mouseDown:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + Float2 mousePos = GetMousePosition(Window, event); + mousePos = Window->ClientToScreen(mousePos); + MouseButton mouseButton = MouseButton::Left; + if ([event clickCount] == 2 && !Input::Mouse->IsRelative()) + Input::Mouse->OnMouseDoubleClick(mousePos, mouseButton, Window); + else + Input::Mouse->OnMouseDown(mousePos, mouseButton, Window); +} + +- (void)mouseDragged:(NSEvent*)event +{ + [self mouseMoved:event]; +} + +- (void)mouseUp:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + Float2 mousePos = GetMousePosition(Window, event); + mousePos = Window->ClientToScreen(mousePos); + MouseButton mouseButton = MouseButton::Left; + Input::Mouse->OnMouseUp(mousePos, mouseButton, Window); + + // Redirect event to any window that tracks the mouse (eg. dock window in Editor) + WindowsManager::WindowsLocker.Lock(); + for (auto* win : WindowsManager::Windows) + { + if (win->IsVisible() && win->IsMouseTracking() && win != Window) + { + Input::Mouse->OnMouseUp(mousePos, mouseButton, win); + break; + } + } + WindowsManager::WindowsLocker.Unlock(); +} + +- (void)rightMouseDown:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + Float2 mousePos = GetMousePosition(Window, event); + MouseButton mouseButton = MouseButton::Right; + if ([event clickCount] == 2 && !Input::Mouse->IsRelative()) + Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window); + else + Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window); +} + +- (void)rightMouseDragged:(NSEvent*)event +{ + [self mouseMoved:event]; +} + +- (void)rightMouseUp:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + Float2 mousePos = GetMousePosition(Window, event); + MouseButton mouseButton = MouseButton::Right; + Input::Mouse->OnMouseUp(Window->ClientToScreen(mousePos), mouseButton, Window); +} + +- (void)otherMouseDown:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + Float2 mousePos = GetMousePosition(Window, event); + MouseButton mouseButton; + switch ([event buttonNumber]) + { + case 2: + mouseButton = MouseButton::Middle; + break; + case 3: + mouseButton = MouseButton::Extended1; + break; + case 4: + mouseButton = MouseButton::Extended2; + break; + default: + return; + } + if ([event clickCount] == 2 && !Input::Mouse->IsRelative()) + Input::Mouse->OnMouseDoubleClick(Window->ClientToScreen(mousePos), mouseButton, Window); + else + Input::Mouse->OnMouseDown(Window->ClientToScreen(mousePos), mouseButton, Window); +} + +- (void)otherMouseDragged:(NSEvent*)event +{ + [self mouseMoved:event]; +} + +- (void)otherMouseUp:(NSEvent*)event +{ + if (IsWindowInvalid(Window)) return; + Float2 mousePos = GetMousePosition(Window, event); + MouseButton mouseButton; + switch ([event buttonNumber]) + { + case 2: + mouseButton = MouseButton::Middle; + break; + case 3: + mouseButton = MouseButton::Extended1; + break; + case 4: + mouseButton = MouseButton::Extended2; + break; + default: + return; + } + Input::Mouse->OnMouseUp(Window->ClientToScreen(mousePos), mouseButton, Window); +} + +#endif + +- (NSDragOperation)draggingEntered:(id)sender +{ + if (IsWindowInvalid(Window)) return NSDragOperationNone; + Float2 mousePos; + MacDropData dropData; + GetDragDropData(Window, sender, mousePos, dropData); + DragDropEffect dragDropEffect = DragDropEffect::None; + Window->OnDragEnter(&dropData, mousePos, dragDropEffect); + return GetDragDropOperation(dragDropEffect); +} + +- (BOOL)wantsPeriodicDraggingUpdates:(id)sender +{ + return YES; +} + +- (NSDragOperation)draggingUpdated:(id)sender +{ + if (IsWindowInvalid(Window)) return NSDragOperationNone; + Float2 mousePos; + MacDropData dropData; + GetDragDropData(Window, sender, mousePos, dropData); + DragDropEffect dragDropEffect = DragDropEffect::None; + Window->OnDragOver(&dropData, mousePos, dragDropEffect); + return GetDragDropOperation(dragDropEffect); +} + +- (BOOL)performDragOperation:(id)sender +{ + if (IsWindowInvalid(Window)) return NO; + Float2 mousePos; + MacDropData dropData; + GetDragDropData(Window, sender, mousePos, dropData); + DragDropEffect dragDropEffect = DragDropEffect::None; + Window->OnDragDrop(&dropData, mousePos, dragDropEffect); + return NO; +} + +- (void)draggingExited:(id)sender +{ + if (IsWindowInvalid(Window)) return; + Window->OnDragLeave(); +} + +- (NSDragOperation)draggingSession:(NSDraggingSession*)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context +{ + if (IsWindowInvalid(Window)) return NSDragOperationNone; + return NSDragOperationMove; +} + +- (void)draggingSession:(NSDraggingSession*)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation +{ +#if USE_EDITOR + // Stop background worker once the drag ended + MacImpl::MacDragLocker.Lock(); + if (MacImpl::MacDragSession && MacImpl::MacDragSession == session) + { + Platform::AtomicStore(&MacImpl::MacDragJob->ExitFlag, 1); + MacImpl::MacDragJob->Wait(); + MacImpl::MacDragSession = nullptr; + MacImpl::MacDragJob = nullptr; + } + MacImpl::MacDragLocker.Unlock(); +#endif +} + +#if !PLATFORM_MAC + +- (void)pasteboard:(nullable NSPasteboard*)pasteboard item:(NSPasteboardItem*)item provideDataForType:(NSPasteboardType)type +{ + if (IsWindowInvalid(Window)) return; + [pasteboard setString:(NSString*)AppleUtils::ToString(Window->GetDragText()) forType:NSPasteboardTypeString]; +} + +#endif + +@end + +#if INBASEFILE + +MacWindow::MacWindow(const CreateWindowSettings& settings) + : WindowBase(settings) +{ + _clientSize = Float2(settings.Size.X, settings.Size.Y); + Float2 pos = AppleUtils::PosToCoca(settings.Position); + NSRect frame = NSMakeRect(pos.X, pos.Y - settings.Size.Y, settings.Size.X, settings.Size.Y); + NSUInteger styleMask = NSWindowStyleMaskClosable; + if (settings.IsRegularWindow) + { + styleMask |= NSWindowStyleMaskTitled; + if (settings.AllowMinimize) + styleMask |= NSWindowStyleMaskMiniaturizable; + if (settings.HasSizingFrame || settings.AllowMaximize) + styleMask |= NSWindowStyleMaskResizable; + } + else + { + styleMask |= NSWindowStyleMaskBorderless; + } + if (settings.Fullscreen) + styleMask |= NSWindowStyleMaskFullScreen; + if (settings.HasBorder) + { + styleMask |= NSWindowStyleMaskTitled; + styleMask &= ~NSWindowStyleMaskFullSizeContentView; + } + + const float screenScale = MacPlatform::ScreenScale; + frame.origin.x /= screenScale; + frame.origin.y /= screenScale; + frame.size.width /= screenScale; + frame.size.height /= screenScale; + + MacWindowImpl* window = [[MacWindowImpl alloc] initWithContentRect:frame + styleMask:(styleMask) + backing:NSBackingStoreBuffered + defer:NO]; + MacViewImpl* view = [[MacViewImpl alloc] init]; + view.wantsLayer = YES; + [view setWindow:this]; + window.title = (__bridge NSString*)AppleUtils::ToString(settings.Title); + [window setWindow:this]; + [window setReleasedWhenClosed:NO]; + [window setMinSize:NSMakeSize(settings.MinimumSize.X, settings.MinimumSize.Y)]; + if (settings.MaximumSize.SumValues() > 0) + [window setMaxSize:NSMakeSize(settings.MaximumSize.X, settings.MaximumSize.Y)]; + [window setOpaque:!settings.SupportsTransparency]; + [window setContentView:view]; + if (settings.AllowInput) + [window setAcceptsMouseMovedEvents:YES]; + [window setDelegate:window]; + _window = window; + _view = view; + if (settings.AllowDragAndDrop) + { + [view registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]]; + } + + // Rescale contents + CALayer* layer = [view layer]; + if (layer) + layer.contentsScale = screenScale; + + // TODO: impl Parent for MacWindow + // TODO: impl ShowInTaskbar for MacWindow + // TODO: impl IsTopmost for MacWindow +} + +MacWindow::~MacWindow() +{ + NSWindow* window = (NSWindow*)_window; + [window close]; + [window release]; + _window = nullptr; + _view = nullptr; +} + +void MacWindow::CheckForResize(float width, float height) +{ + const Float2 clientSize(width, height); + if (clientSize != _clientSize) + { + _clientSize = clientSize; + OnResize(width, height); + } +} + +void MacWindow::SetIsMouseOver(bool value) +{ + if (_isMouseOver == value) + return; + _isMouseOver = value; + CursorType cursor = _cursor; + if (value) + { + // Refresh cursor typet + SetCursor(CursorType::Default); + SetCursor(cursor); + } + else + { + Input::Mouse->OnMouseLeave(this); + SetCursor(CursorType::Default); + _cursor = cursor; + } +} + +void* MacWindow::GetNativePtr() const +{ + return _window; +} + +void MacWindow::OnUpdate(float dt) +{ + if (IsMouseTracking()) + { + // Keep sending mouse movement events no matter if window has focus + Float2 mousePos = Platform::GetMousePosition(); + if (_mouseTrackPos != mousePos) + { + _mouseTrackPos = mousePos; + Input::Mouse->OnMouseMove(mousePos, this); + } + } + + WindowBase::OnUpdate(dt); +} + +void MacWindow::Show() +{ + if (!_visible) + { + InitSwapChain(); + if (_showAfterFirstPaint) + { + if (RenderTask) + RenderTask->Enabled = true; + return; + } + + // Show + NSWindow* window = (NSWindow*)_window; + if (_settings.AllowInput) + [window makeKeyAndOrderFront:window]; + else + [window orderFront:window]; + if (_settings.ActivateWhenFirstShown) + [NSApp activateIgnoringOtherApps:YES]; + _focused = true; + + // Base + WindowBase::Show(); + } +} + +void MacWindow::Hide() +{ + if (_visible) + { + SetCursor(CursorType::Default); + + // Hide + NSWindow* window = (NSWindow*)_window; + [window orderOut:window]; + + // Base + WindowBase::Hide(); + } +} + +void MacWindow::Minimize() +{ + if (!_settings.AllowMinimize) + return; + NSWindow* window = (NSWindow*)_window; + if (!window) + return; + if (!window.miniaturized) + [window miniaturize:nil]; +} + +void MacWindow::Maximize() +{ + if (!_settings.AllowMaximize) + return; + NSWindow* window = (NSWindow*)_window; + if (!window.zoomed) + [window zoom:nil]; +} + +void MacWindow::Restore() +{ + NSWindow* window = (NSWindow*)_window; + if (window.miniaturized) + [window deminiaturize:nil]; + else if (window.zoomed) + [window zoom:nil]; +} + +bool MacWindow::IsForegroundWindow() const +{ + return IsFocused() && Platform::GetHasFocus(); +} + +void MacWindow::BringToFront(bool force) +{ + Focus(); + [NSApp activateIgnoringOtherApps: NO]; +} + +void MacWindow::SetIsFullscreen(bool isFullscreen) +{ + // TODO: fullscreen mode on Mac +} + +void MacWindow::SetClientBounds(const Rectangle& clientArea) +{ + NSWindow* window = (NSWindow*)_window; + if (!window) + return; + const float screenScale = MacPlatform::ScreenScale; + + NSRect oldRect = [window frame]; + NSRect newRect = NSMakeRect(0, 0, clientArea.Size.X / screenScale, clientArea.Size.Y / screenScale); + newRect = [window frameRectForContentRect:newRect]; + + Float2 pos = AppleUtils::PosToCoca(clientArea.Location) / screenScale; + Float2 titleSize = GetWindowTitleSize(this); + newRect.origin.x = pos.X + titleSize.X; + newRect.origin.y = pos.Y - newRect.size.height + titleSize.Y; + + [window setFrame:newRect display:YES]; +} + +void MacWindow::SetPosition(const Float2& position) +{ + NSWindow* window = (NSWindow*)_window; + if (!window) + return; + Float2 pos = AppleUtils::PosToCoca(position) / MacPlatform::ScreenScale; + NSRect rect = [window frame]; + [window setFrameOrigin:NSMakePoint(pos.X, pos.Y - rect.size.height)]; +} + +Float2 MacWindow::GetPosition() const +{ + NSWindow* window = (NSWindow*)_window; + if (!window) + return Float2::Zero; + NSRect rect = [window frame]; + return AppleUtils::CocaToPos(Float2(rect.origin.x, rect.origin.y + rect.size.height) * MacPlatform::ScreenScale); +} + +Float2 MacWindow::GetSize() const +{ + NSWindow* window = (NSWindow*)_window; + if (!window) + return Float2::Zero; + NSRect rect = [window frame]; + return Float2(rect.size.width, rect.size.height) * MacPlatform::ScreenScale; +} + +Float2 MacWindow::GetClientSize() const +{ + return _clientSize; +} + +Float2 MacWindow::ScreenToClient(const Float2& screenPos) const +{ + NSWindow* window = (NSWindow*)_window; + if (!window) + return screenPos; + Float2 titleSize = GetWindowTitleSize(this); + return screenPos - GetPosition() - titleSize; +} + +Float2 MacWindow::ClientToScreen(const Float2& clientPos) const +{ + NSWindow* window = (NSWindow*)_window; + if (!window) + return clientPos; + Float2 titleSize = GetWindowTitleSize(this); + return GetPosition() + titleSize + clientPos; +} + +void MacWindow::FlashWindow() +{ + NSWindow* window = (NSWindow*)_window; + if (!window) + return; + [NSApp requestUserAttention:NSInformationalRequest]; +} + +void MacWindow::SetOpacity(float opacity) +{ + NSWindow* window = (NSWindow*)_window; + if (!window) + return; + [window setAlphaValue:opacity]; +} + +void MacWindow::Focus() +{ + NSWindow* window = (NSWindow*)_window; + if (!window) + return; + [window makeKeyAndOrderFront:window]; +} + +void MacWindow::SetTitle(const StringView& title) +{ + _title = title; + NSWindow* window = (NSWindow*)_window; + if (!window) + return; + [window setTitle:(__bridge NSString*)AppleUtils::ToString(_title)]; +} + +DragDropEffect MacWindow::DoDragDrop(const StringView& data) +{ + NSWindow* window = (NSWindow*)_window; + MacViewImpl* view = (MacViewImpl*)_view; + _dragText = data; + + // Create mouse drag event + NSEvent* event = [NSEvent + mouseEventWithType:NSEventTypeLeftMouseDragged + location:window.mouseLocationOutsideOfEventStream + modifierFlags:0 + timestamp:NSApp.currentEvent.timestamp + windowNumber:window.windowNumber + context:nil + eventNumber:0 + clickCount:1 + pressure:1.0]; + + // Create drag item + NSPasteboardItem* pasteItem = [NSPasteboardItem new]; + [pasteItem setDataProvider:view forTypes:[NSArray arrayWithObjects:NSPasteboardTypeString, nil]]; + NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pasteItem]; + [dragItem setDraggingFrame:NSMakeRect(event.locationInWindow.x, event.locationInWindow.y, 1, 1) contents:nil]; + + // Start dragging session + NSDraggingSession* draggingSession = [view beginDraggingSessionWithItems:[NSArray arrayWithObject:dragItem] event:event source:view]; + DragDropEffect result = DragDropEffect::None; + +#if USE_EDITOR + // Create background worker that will keep updating GUI (perform rendering) + MacImpl::MacDragLocker.Lock(); + ASSERT(!MacImpl::MacDragSession && !MacImpl::MacDragJob); + MacImpl::MacDragSession = draggingSession; + MacImpl::MacDragJob = New(); + Task::StartNew(MacImpl::MacDragJob); + MacImpl::MacDragLocker.Unlock(); + while (MacImpl::MacDragJob->GetState() == TaskState::Queued) + Platform::Sleep(1); + // TODO: maybe wait for the drag end to return result? +#endif + + return result; +} + +void MacWindow::StartTrackingMouse(bool useMouseScreenOffset) +{ + if (_isTrackingMouse || !_window) + return; + _isTrackingMouse = true; + _trackingMouseOffset = Float2::Zero; + _isUsingMouseOffset = useMouseScreenOffset; + _mouseTrackPos = Float2::Minimum; +} + +void MacWindow::EndTrackingMouse() +{ + if (!_isTrackingMouse || !_window) + return; + _isTrackingMouse = false; +} + +void MacWindow::SetCursor(CursorType type) +{ + CursorType prev = _cursor; + if (prev == type) + return; + WindowBase::SetCursor(type); + //if (!_isMouseOver) + // return; + NSCursor* cursor = nullptr; + switch (type) + { + case CursorType::Cross: + cursor = [NSCursor crosshairCursor]; + break; + case CursorType::Hand: + cursor = [NSCursor pointingHandCursor]; + break; + case CursorType::IBeam: + cursor = [NSCursor IBeamCursor]; + break; + case CursorType::No: + cursor = [NSCursor operationNotAllowedCursor]; + break; + case CursorType::SizeAll: + case CursorType::SizeNESW: + case CursorType::SizeNWSE: + cursor = [NSCursor resizeUpDownCursor]; + break; + case CursorType::SizeNS: + cursor = [NSCursor resizeUpDownCursor]; + break; + case CursorType::SizeWE: + cursor = [NSCursor resizeLeftRightCursor]; + break; + case CursorType::Hidden: + [NSCursor hide]; + return; + default: + cursor = [NSCursor arrowCursor]; + break; + } + if (cursor) + { + if (prev == CursorType::Hidden) + { + [NSCursor unhide]; + } + [cursor set]; + } +} + +#endif + +#endif diff --git a/Source/Engine/Platform/SDL/SDLPlatform.Mac.cpp b/Source/Engine/Platform/SDL/SDLPlatform.Mac.cpp index 6cecca574..cff9d1887 100644 --- a/Source/Engine/Platform/SDL/SDLPlatform.Mac.cpp +++ b/Source/Engine/Platform/SDL/SDLPlatform.Mac.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#include "SDLInput.h" #if PLATFORM_SDL && PLATFORM_MAC #include "SDLWindow.h" @@ -20,7 +21,10 @@ #include "Engine/Platform/Unix/UnixFile.h" #include "Engine/Profiler/ProfilerCPU.h" -#include "Engine/Platform/Linux/IncludeX11.h" +#include "Engine/Platform/Apple/AppleUtils.h" +#include +#include +#include #include #include @@ -30,6 +34,43 @@ #include #include +namespace MacImpl +{ + Window* DraggedWindow = nullptr; + String DraggingData = String(); + Float2 DraggingPosition; + Nullable LastMouseDragPosition; +#if USE_EDITOR + //CriticalSection MacDragLocker; + bool DraggingActive = false; + bool DraggingIgnoreEvent = false; + NSDraggingSession* MacDragSession = nullptr; + //DoDragDropJob* MacDragJob = nullptr; + int64 MacDragExitFlag = 0; +#endif +} + +class MacDropData : public IGuiData +{ +public: + Type CurrentType; + String AsText; + Array AsFiles; + + Type GetType() const override + { + return CurrentType; + } + String GetAsText() const override + { + return AsText; + } + void GetAsFiles(Array* files) const override + { + files->Add(AsFiles); + } +}; + bool SDLPlatform::InitInternal() { return false; @@ -50,16 +91,282 @@ bool SDLPlatform::UsesX11() return false; } +bool SDLPlatform::EventFilterCallback(void* userdata, SDL_Event* event) +{ + Window* draggedWindow = *(Window**)userdata; + if (draggedWindow == nullptr) + { + if (MacImpl::DraggingActive) + { + // Handle events during drag operation here since the normal event loop is blocked + if (event->type == SDL_EVENT_WINDOW_EXPOSED) + { + LOG(Info, "Window exposed event"); + // The internal timer is sending exposed events every ~16ms +#if USE_EDITOR + // Flush any single-frame shapes to prevent memory leaking (eg. via terrain collision debug during scene drawing with PhysicsColliders or PhysicsDebug flag) + DebugDraw::UpdateContext(nullptr, 0.0f); +#endif + Engine::OnUpdate(); // For docking updates + Engine::OnDraw(); + } + else + { + SDLWindow* window = SDLWindow::GetWindowFromEvent(*event); + if (window) + window->HandleEvent(*event); + + // We do not receive events at steady rate to keep the engine updated... +#if USE_EDITOR + // Flush any single-frame shapes to prevent memory leaking (eg. via terrain collision debug during scene drawing with PhysicsColliders or PhysicsDebug flag) + DebugDraw::UpdateContext(nullptr, 0.0f); +#endif + Engine::OnUpdate(); // For docking updates + Engine::OnDraw(); + + if (event->type == SDL_EVENT_DROP_BEGIN || event->type == SDL_EVENT_DROP_FILE || event->type == SDL_EVENT_DROP_TEXT) + return true; // Filtering these event stops other following events from getting added to the queue + else + LOG(Info, "Unhandled event: {}", event->type); + } + return false; + } + return true; + } + return true; + + // When the window is being dragged on Windows, the internal message loop is blocking + // the SDL event queue. We need to handle all relevant events in this event watch callback + // to ensure dragging related functionality doesn't break due to engine not getting updated. + // This also happens to fix the engine freezing during the dragging operation. +#if false + SDLWindow* window = SDLWindow::GetWindowFromEvent(*event); + if (event->type == SDL_EVENT_WINDOW_EXPOSED) + { + // The internal timer is sending exposed events every ~16ms + Engine::OnUpdate(); // For docking updates + Engine::OnDraw(); + return false; + } + else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) + { + if (window) + { + bool result = false; + window->OnLeftButtonHit(WindowHitCodes::Caption, result); + //if (result) + // return false; + window->HandleEvent(*event); + } + return false; + } + else if (event->type == SDL_EVENT_WINDOW_MOVED) + { + if (window) + window->HandleEvent(*event); + + /*if (WinImpl::DraggedWindowSize != window->GetClientSize()) + { + // The window size changed while dragging, most likely due to maximized window restoring back to previous size. + WinImpl::DraggedWindowMousePosition = WinImpl::DraggedWindowStartPosition + WinImpl::DraggedWindowMousePosition - window->GetClientPosition(); + WinImpl::DraggedWindowStartPosition = window->GetClientPosition(); + WinImpl::DraggedWindowSize = window->GetClientSize(); + } + Float2 windowPosition = Float2(static_cast(event->window.data1), static_cast(event->window.data2)); + Float2 mousePosition = WinImpl::DraggedWindowMousePosition; + + // Generate mouse movement events while dragging the window around + SDL_Event mouseMovedEvent { 0 }; + mouseMovedEvent.motion.type = SDL_EVENT_MOUSE_MOTION; + mouseMovedEvent.motion.windowID = SDL_GetWindowID(WinImpl::DraggedWindow->GetSDLWindow()); + mouseMovedEvent.motion.timestamp = SDL_GetTicksNS(); + mouseMovedEvent.motion.state = SDL_BUTTON_LEFT; + mouseMovedEvent.motion.x = mousePosition.X; + mouseMovedEvent.motion.y = mousePosition.Y; + if (window) + window->HandleEvent(mouseMovedEvent);*/ + + return false; + } + if (window) + window->HandleEvent(*event); + + return false; +#endif +} + void SDLPlatform::PreHandleEvents() { + SDL_SetEventFilter(EventFilterCallback, &MacImpl::DraggedWindow); } void SDLPlatform::PostHandleEvents() { + SDL_SetEventFilter(EventFilterCallback, &MacImpl::DraggedWindow); + + // Handle window dragging release here + if (MacImpl::DraggedWindow != nullptr) + { + Float2 mousePosition; + auto buttons = SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y); + bool buttonReleased = (buttons & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)) == 0; + if (buttonReleased) + { + // Send simulated mouse up event + SDL_Event buttonUpEvent { 0 }; + buttonUpEvent.motion.type = SDL_EVENT_MOUSE_BUTTON_UP; + buttonUpEvent.button.down = false; + buttonUpEvent.motion.windowID = SDL_GetWindowID(MacImpl::DraggedWindow->GetSDLWindow()); + buttonUpEvent.motion.timestamp = SDL_GetTicksNS(); + buttonUpEvent.motion.state = SDL_BUTTON_LEFT; + buttonUpEvent.button.clicks = 1; + buttonUpEvent.motion.x = mousePosition.X; + buttonUpEvent.motion.y = mousePosition.Y; + MacImpl::DraggedWindow->HandleEvent(buttonUpEvent); + MacImpl::DraggedWindow = nullptr; + } + } } bool SDLWindow::HandleEventInternal(SDL_Event& event) { + switch (event.type) + { + case SDL_EVENT_WINDOW_MOVED: + { + // Quartz doesn't report any mouse events when mouse is over the caption area, send a simulated event instead... + Float2 mousePosition; + auto buttons = SDL_GetGlobalMouseState(&mousePosition.X, &mousePosition.Y); + if ((buttons & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)) != 0) + { + if (MacImpl::DraggedWindow == nullptr) + { + // TODO: verify mouse position, window focus + bool result = false; + OnLeftButtonHit(WindowHitCodes::Caption, result); + if (result) + MacImpl::DraggedWindow = this; + } + else + { + Float2 mousePos = Platform::GetMousePosition(); + Input::Mouse->OnMouseMove(mousePos, this); + } + } + break; + } + case SDL_EVENT_MOUSE_BUTTON_UP: + case SDL_EVENT_MOUSE_BUTTON_DOWN: + { + if (MacImpl::LastMouseDragPosition.HasValue()) + { + // SDL reports wrong mouse position after dragging has ended + Float2 mouseClientPosition = ScreenToClient(MacImpl::LastMouseDragPosition.GetValue()); + event.button.x = mouseClientPosition.X; + event.button.y = mouseClientPosition.Y; + } + break; + } + case SDL_EVENT_MOUSE_MOTION: + { + if (MacImpl::LastMouseDragPosition.HasValue()) + MacImpl::LastMouseDragPosition.Reset(); + if (MacImpl::DraggedWindow != nullptr) + return true; + break; + } + case SDL_EVENT_WINDOW_MOUSE_LEAVE: + { + OnDragLeave(); // Check for release of mouse button too? + break; + } + //case SDL_EVENT_CLIPBOARD_UPDATE: + case SDL_EVENT_DROP_BEGIN: + case SDL_EVENT_DROP_POSITION: + case SDL_EVENT_DROP_FILE: + case SDL_EVENT_DROP_TEXT: + case SDL_EVENT_DROP_COMPLETE: + { + { + // HACK: We can't use Wayland listeners due to SDL also using them at the same time causes + // some of the events to drop and make it impossible to implement dragging on application side. + // We can get enough information through SDL_EVENT_DROP_* events to fill in the blanks for the + // drag and drop implementation. + + auto dpiScale = GetDpiScale(); + Float2 mousePos = Float2(event.drop.x * dpiScale, event.drop.y * dpiScale); + DragDropEffect effect = DragDropEffect::None; + String text(event.drop.data); + MacDropData dropData; + + if (MacImpl::DraggingActive) + { + // We don't have the window dragging data during these events... + text = MacImpl::DraggingData; + mousePos = ScreenToClient(MacImpl::DraggingPosition); + + // Ensure mouse position is updated while dragging + Input::Mouse->OnMouseMove(MacImpl::DraggingPosition, this); + MacImpl::LastMouseDragPosition = MacImpl::DraggingPosition; + } + dropData.AsText = text; + + if (event.type == SDL_EVENT_DROP_BEGIN) + { + // We don't know the type of dragged data at this point, so call the events for both types + if (!MacImpl::DraggingActive) + { + dropData.CurrentType = IGuiData::Type::Files; + OnDragEnter(&dropData, mousePos, effect); + } + if (effect == DragDropEffect::None) + { + dropData.CurrentType = IGuiData::Type::Text; + OnDragEnter(&dropData, mousePos, effect); + } + } + else if (event.type == SDL_EVENT_DROP_POSITION) + { + Input::Mouse->OnMouseMove(ClientToScreen(mousePos), this); + + // We don't know the type of dragged data at this point, so call the events for both types + if (!MacImpl::DraggingActive) + { + dropData.CurrentType = IGuiData::Type::Files; + OnDragOver(&dropData, mousePos, effect); + } + if (effect == DragDropEffect::None) + { + dropData.CurrentType = IGuiData::Type::Text; + OnDragOver(&dropData, mousePos, effect); + } + } + else if (event.type == SDL_EVENT_DROP_FILE) + { + text.Split('\n', dropData.AsFiles); + dropData.CurrentType = IGuiData::Type::Files; + OnDragDrop(&dropData, mousePos, effect); + } + else if (event.type == SDL_EVENT_DROP_TEXT) + { + dropData.CurrentType = IGuiData::Type::Text; + OnDragDrop(&dropData, mousePos, effect); + } + else if (event.type == SDL_EVENT_DROP_COMPLETE) + { + OnDragLeave(); + if (MacImpl::DraggingActive) + { + // The previous drop events needs to be flushed to avoid processing them twice + SDL_FlushEvents(SDL_EVENT_DROP_FILE, SDL_EVENT_DROP_POSITION); + } + } + + // TODO: Implement handling for feedback effect result (https://github.com/libsdl-org/SDL/issues/10448) + } + break; + } + } return false; } @@ -68,9 +375,207 @@ void SDLPlatform::SetHighDpiAwarenessEnabled(bool enable) // TODO: This is now called before Platform::Init, ensure the scaling is changed accordingly during Platform::Init (see ApplePlatform::SetHighDpiAwarenessEnabled) } + +inline bool IsWindowInvalid(Window* win) +{ + WindowsManager::WindowsLocker.Lock(); + const bool hasWindow = WindowsManager::Windows.Contains(win); + WindowsManager::WindowsLocker.Unlock(); + return !hasWindow || !win; +} + +Float2 GetWindowTitleSize(const SDLWindow* window) +{ + Float2 size = Float2::Zero; + if (window->GetSettings().HasBorder) + { + NSRect frameStart = [(NSWindow*)window->GetNativePtr() frameRectForContentRect:NSMakeRect(0, 0, 0, 0)]; + size.Y = frameStart.size.height; + } + return size * MacPlatform::ScreenScale; +} + +Float2 GetMousePosition(SDLWindow* window, NSEvent* event) +{ + NSRect frame = [(NSWindow*)window->GetNativePtr() frame]; + NSPoint point = [event locationInWindow]; + return Float2(point.x, frame.size.height - point.y) * MacPlatform::ScreenScale - GetWindowTitleSize(window); +} + +Float2 GetMousePosition(SDLWindow* window, const NSPoint& point) +{ + NSRect frame = [(NSWindow*)window->GetNativePtr() frame]; + CGRect screenBounds = CGDisplayBounds(CGMainDisplayID()); + return Float2(point.x, screenBounds.size.height - point.y) * MacPlatform::ScreenScale; +} + +void GetDragDropData(const SDLWindow* window, id sender, Float2& mousePos, MacDropData& dropData) +{ + NSRect frame = [(NSWindow*)window->GetNativePtr() frame]; + NSPoint point = [sender draggingLocation]; + mousePos = Float2(point.x, frame.size.height - point.y) * MacPlatform::ScreenScale - GetWindowTitleSize(window); + NSPasteboard* pasteboard = [sender draggingPasteboard]; + if ([[pasteboard types] containsObject:NSPasteboardTypeString]) + { + dropData.CurrentType = IGuiData::Type::Text; + dropData.AsText = AppleUtils::ToString((CFStringRef)[pasteboard stringForType:NSPasteboardTypeString]); + } + else + { + dropData.CurrentType = IGuiData::Type::Files; + NSArray* files = [pasteboard readObjectsForClasses:@[[NSURL class]] options:nil]; + for (int32 i = 0; i < [files count]; i++) + { + NSString* url = [[files objectAtIndex:i] path]; + NSString* file = [NSURL URLWithString:url].path; + dropData.AsFiles.Add(AppleUtils::ToString((CFStringRef)file)); + } + } +} + +NSDragOperation GetDragDropOperation(DragDropEffect dragDropEffect) +{ + NSDragOperation result = NSDragOperationCopy; + switch (dragDropEffect) + { + case DragDropEffect::None: + //result = NSDragOperationNone; + break; + case DragDropEffect::Copy: + result = NSDragOperationCopy; + break; + case DragDropEffect::Move: + result = NSDragOperationMove; + break; + case DragDropEffect::Link: + result = NSDragOperationLink; + break; + } + return result; +} + +#undef INCLUDED_IN_SDL + + +@interface ClipboardDataProviderImpl : NSObject +{ +@public + SDLWindow* Window; +} +@end + +@implementation ClipboardDataProviderImpl + +// NSPasteboardItemDataProvider +// --- + +- (void)pasteboard:(nullable NSPasteboard*)pasteboard item:(NSPasteboardItem*)item provideDataForType:(NSPasteboardType)type +{ + LOG(Info, "pasteboard"); + if (IsWindowInvalid(Window)) return; + [pasteboard setString:(NSString*)AppleUtils::ToString(MacImpl::DraggingData) forType:NSPasteboardTypeString]; +} + +- (void)pasteboardFinishedWithDataProvider:(NSPasteboard*)pasteboard +{ + LOG(Info, "pasteboardFinishedWithDataProvider"); +} + +// NSDraggingSource +// --- + +- (NSDragOperation)draggingSession:(NSDraggingSession*)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context +{ + if (IsWindowInvalid(Window)) + return NSDragOperationNone; + + switch(context) + { + case NSDraggingContextOutsideApplication: + LOG(Info, "draggingSession sourceOperationMaskForDraggingContext: outside"); + return NSDragOperationCopy; + case NSDraggingContextWithinApplication: + LOG(Info, "draggingSession sourceOperationMaskForDraggingContext: inside"); + return NSDragOperationCopy; + default: + LOG(Info, "draggingSession sourceOperationMaskForDraggingContext: unknown"); + return NSDragOperationMove; + } +} + +- (void)draggingSession:(NSDraggingSession*)session willBeginAtPoint:(NSPoint)screenPoint +{ + LOG(Info, "draggingSession willBeginAtPoint"); + MacImpl::DraggingPosition = GetMousePosition(Window, screenPoint); +} + +- (void)draggingSession:(NSDraggingSession*)session movedToPoint:(NSPoint)screenPoint +{ + //LOG(Info, "draggingSession movedToPoint"); + MacImpl::DraggingPosition = GetMousePosition(Window, screenPoint); +} + +- (void)draggingSession:(NSDraggingSession*)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation +{ + LOG(Info, "draggingSession endedAtPoint"); + MacImpl::DraggingPosition = GetMousePosition(Window, screenPoint); +#if USE_EDITOR + // Stop background worker once the drag ended + if (MacImpl::MacDragSession && MacImpl::MacDragSession == session) + Platform::AtomicStore(&MacImpl::MacDragExitFlag, 1); +#endif +} + +@end + DragDropEffect SDLWindow::DoDragDrop(const StringView& data) { - return DragDropEffect::None; + NSWindow* window = (NSWindow*)_handle; + ClipboardDataProviderImpl* clipboardDataProvider = [ClipboardDataProviderImpl alloc]; + clipboardDataProvider->Window = this; + + // Create mouse drag event + NSEvent* event = [NSEvent + mouseEventWithType:NSEventTypeLeftMouseDragged + location:window.mouseLocationOutsideOfEventStream + modifierFlags:0 + timestamp:NSApp.currentEvent.timestamp + windowNumber:window.windowNumber + context:nil + eventNumber:0 + clickCount:1 + pressure:1.0]; + + // Create drag item + NSPasteboardItem* pasteItem = [NSPasteboardItem new]; + [pasteItem setDataProvider:clipboardDataProvider forTypes:[NSArray arrayWithObjects:NSPasteboardTypeString, nil]]; + NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pasteItem]; + [dragItem setDraggingFrame:NSMakeRect(event.locationInWindow.x, event.locationInWindow.y, 100, 100) contents:nil]; + + // Start dragging session + NSDraggingSession* draggingSession = [window.contentView beginDraggingSessionWithItems:[NSArray arrayWithObject:dragItem] event:event source:clipboardDataProvider]; + DragDropEffect result = DragDropEffect::None; + +#if USE_EDITOR + // Create background worker that will keep updating GUI (perform rendering) + ASSERT(!MacImpl::MacDragSession); + MacImpl::MacDragSession = draggingSession; + MacImpl::MacDragExitFlag = 0; + MacImpl::DraggingData = data; + MacImpl::DraggingActive = true; + while (Platform::AtomicRead(&MacImpl::MacDragExitFlag) == 0) + { + // The internal event loop will block here during the drag operation, + // events are processed in the event filter callback instead. + SDLPlatform::Tick(); + Platform::Sleep(1); + } + MacImpl::DraggingActive = false; + MacImpl::DraggingData.Clear(); + MacImpl::MacDragSession = nullptr; +#endif + + return result; } DragDropEffect SDLWindow::DoDragDrop(const StringView& data, const Float2& offset, Window* dragSourceWindow) diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/SDL.cs b/Source/Tools/Flax.Build/Deps/Dependencies/SDL.cs index daac4a976..d9c7cf35d 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/SDL.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/SDL.cs @@ -47,7 +47,7 @@ namespace Flax.Deps.Dependencies public override void Build(BuildOptions options) { string root = options.IntermediateFolder; - string configuration = "Release"; + string configuration = "Debug"; const bool buildStatic = true; var configs = new string[] { @@ -88,9 +88,9 @@ namespace Flax.Deps.Dependencies Path.Combine(root, "include", "SDL3"), }; - CloneGitRepo(root, "https://github.com/libsdl-org/SDL"); - GitFetch(root); - GitResetToCommit(root, "877399b2b2cf21e67554ed9046410f268ce1d1b2"); // 3.2.10 + //CloneGitRepo(root, "https://github.com/libsdl-org/SDL"); + //GitFetch(root); + //GitResetToCommit(root, "337f012de28fff96dc3ad023d08345b6f7cad1ef"); // 3.2.10 foreach (var platform in options.Platforms) {