diff --git a/Imgui/CMakeLists.txt b/Imgui/CMakeLists.txt index c84db939..855ddcc3 100644 --- a/Imgui/CMakeLists.txt +++ b/Imgui/CMakeLists.txt @@ -94,14 +94,8 @@ else() if(PLATFORM_WIN32) list(APPEND DEAR_IMGUI_SOURCE ${DILIGENT_DEAR_IMGUI_PATH}/backends/imgui_impl_win32.cpp) list(APPEND DEAR_IMGUI_INCLUDE ${DILIGENT_DEAR_IMGUI_PATH}/backends/imgui_impl_win32.h) - elseif(PLATFORM_MACOS) - set(DEAR_IMGUI_V185_SOURCE - ../ThirdParty/imgui_v1.85/imgui_impl_osx_v1.85.mm - ../ThirdParty/imgui_v1.85/imgui_impl_osx_v1.85.h - ) - target_sources(Diligent-Imgui PRIVATE ${DEAR_IMGUI_V185_SOURCE}) - source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/../ThirdParty/imgui_v1.85 PREFIX "dear_imgui_v1.85" FILES ${DEAR_IMGUI_V185_SOURCE}) endif() + # MacOS uses the self-contained ImGuiImplMacOS.mm (no dear imgui platform backend). target_sources(Diligent-Imgui PRIVATE ${DEAR_IMGUI_SOURCE} diff --git a/Imgui/interface/ImGuiImplMacOS.hpp b/Imgui/interface/ImGuiImplMacOS.hpp index ce542b04..e12d13a2 100644 --- a/Imgui/interface/ImGuiImplMacOS.hpp +++ b/Imgui/interface/ImGuiImplMacOS.hpp @@ -27,11 +27,17 @@ #pragma once +#include +#include #include +#include + +#include "imgui.h" #include "ImGuiImplDiligent.hpp" @class NSEvent; @class NSView; +@class NSCursor; namespace Diligent { @@ -58,7 +64,21 @@ class ImGuiImplMacOS final : public ImGuiImplDiligent bool HandleOSXEvent(NSEvent* _Nonnull event, NSView* _Nonnull view); private: - std::mutex m_Mtx; + void InitMouseCursors(); + void UpdateMouseCursor(); + + // clang-format off + NSCursor* _Nullable m_MouseCursors[ImGuiMouseCursor_COUNT] = {}; + + int32_t m_LastMouseCursor = ImGuiMouseCursor_Arrow; + id _Nullable m_FocusObserver = nullptr; + std::atomic m_AppActive = true; + std::atomic m_FocusDirty = false; + + std::vector m_KeysPressedWithCmd; + std::chrono::high_resolution_clock::time_point m_LastTime = {}; + std::mutex m_Mtx; + // clang-format on }; } // namespace Diligent diff --git a/Imgui/src/ImGuiImplMacOS.mm b/Imgui/src/ImGuiImplMacOS.mm index 018fc4a3..fd98389c 100644 --- a/Imgui/src/ImGuiImplMacOS.mm +++ b/Imgui/src/ImGuiImplMacOS.mm @@ -23,12 +23,149 @@ #include "imgui.h" #include "ImGuiImplMacOS.hpp" -#include "../../ThirdParty/imgui_v1.85/imgui_impl_osx_v1.85.h" + #import +#include + +// Undocumented methods for creating cursors. +@interface NSCursor () ++ (id)_windowResizeNorthWestSouthEastCursor; ++ (id)_windowResizeNorthEastSouthWestCursor; ++ (id)busyButClickableCursor; +@end + +@interface DgImGuiMacFocusObserver : NSObject +- (instancetype)initWithActive:(std::atomic*)pActive dirty:(std::atomic*)pDirty; +@end + +@implementation DgImGuiMacFocusObserver +{ + std::atomic* _pActive; + std::atomic* _pDirty; +} +- (instancetype)initWithActive:(std::atomic*)pActive dirty:(std::atomic*)pDirty +{ + if (self = [super init]) + { + _pActive = pActive; + _pDirty = pDirty; + } + return self; +} +- (void)onApplicationBecomeActive:(NSNotification*)aNotification +{ + _pActive->store(true); + _pDirty->store(true); +} +- (void)onApplicationBecomeInactive:(NSNotification*)aNotification +{ + _pActive->store(false); + _pDirty->store(true); +} +@end + namespace Diligent { +namespace +{ + +ImGuiKey MapCharacterToImGuiKey(int32_t c) +{ + if (c >= 'a' && c <= 'z') + return static_cast(ImGuiKey_A + (c - 'a')); + if (c >= 'A' && c <= 'Z') + return static_cast(ImGuiKey_A + (c - 'A')); + if (c >= '0' && c <= '9') + return static_cast(ImGuiKey_0 + (c - '0')); + + switch (c) + { + case 9: return ImGuiKey_Tab; + case 13: return ImGuiKey_Enter; + case 27: return ImGuiKey_Escape; + case 127: return ImGuiKey_Backspace; + case 25: return ImGuiKey_Tab; + + case 0xF700: return ImGuiKey_UpArrow; + case 0xF701: return ImGuiKey_DownArrow; + case 0xF702: return ImGuiKey_LeftArrow; + case 0xF703: return ImGuiKey_RightArrow; + case 0xF704: return ImGuiKey_F1; + case 0xF705: return ImGuiKey_F2; + case 0xF706: return ImGuiKey_F3; + case 0xF707: return ImGuiKey_F4; + case 0xF708: return ImGuiKey_F5; + case 0xF709: return ImGuiKey_F6; + case 0xF70A: return ImGuiKey_F7; + case 0xF70B: return ImGuiKey_F8; + case 0xF70C: return ImGuiKey_F9; + case 0xF70D: return ImGuiKey_F10; + case 0xF70E: return ImGuiKey_F11; + case 0xF70F: return ImGuiKey_F12; + case 0xF729: return ImGuiKey_Home; + case 0xF72B: return ImGuiKey_End; + case 0xF72C: return ImGuiKey_PageUp; + case 0xF72D: return ImGuiKey_PageDown; + } + + return ImGuiKey_None; +} + +// 4-way move cursor matching CSS "move"; macOS has no built-in equivalent. +NSCursor* CreateMoveCursor() +{ + const CGFloat S = 24.0; + const CGFloat c = S * 0.5; + const CGFloat H = 10.0; + const CGFloat h = 5.0; + const CGFloat a = 4.0; + const CGFloat w = 1.5; + + NSImage* image = [NSImage imageWithSize:NSMakeSize(S, S) + flipped:NO + drawingHandler:^BOOL(NSRect dstRect) { + NSBezierPath* glyph = [NSBezierPath bezierPath]; + + [glyph moveToPoint:NSMakePoint(c, c + H)]; + [glyph lineToPoint:NSMakePoint(c + a, c + H - h)]; + [glyph lineToPoint:NSMakePoint(c + w, c + H - h)]; + [glyph lineToPoint:NSMakePoint(c + w, c - H + h)]; + [glyph lineToPoint:NSMakePoint(c + a, c - H + h)]; + [glyph lineToPoint:NSMakePoint(c, c - H)]; + [glyph lineToPoint:NSMakePoint(c - a, c - H + h)]; + [glyph lineToPoint:NSMakePoint(c - w, c - H + h)]; + [glyph lineToPoint:NSMakePoint(c - w, c + H - h)]; + [glyph lineToPoint:NSMakePoint(c - a, c + H - h)]; + [glyph closePath]; + + [glyph moveToPoint:NSMakePoint(c + H, c)]; + [glyph lineToPoint:NSMakePoint(c + H - h, c + a)]; + [glyph lineToPoint:NSMakePoint(c + H - h, c + w)]; + [glyph lineToPoint:NSMakePoint(c - H + h, c + w)]; + [glyph lineToPoint:NSMakePoint(c - H + h, c + a)]; + [glyph lineToPoint:NSMakePoint(c - H, c)]; + [glyph lineToPoint:NSMakePoint(c - H + h, c - a)]; + [glyph lineToPoint:NSMakePoint(c - H + h, c - w)]; + [glyph lineToPoint:NSMakePoint(c + H - h, c - w)]; + [glyph lineToPoint:NSMakePoint(c + H - h, c - a)]; + [glyph closePath]; + + [[NSColor whiteColor] setStroke]; + glyph.lineWidth = 2.0; + glyph.miterLimit = 2.0; + [glyph stroke]; + [[NSColor blackColor] setFill]; + [glyph fill]; + return YES; + }]; + + return [[[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(c, c)] autorelease]; +} + +} // namespace + std::unique_ptr ImGuiImplMacOS::Create(const ImGuiDiligentCreateInfo& CI, void* _Nullable view) { return std::make_unique(CI, view); @@ -37,50 +174,238 @@ ImGuiImplMacOS::ImGuiImplMacOS(const ImGuiDiligentCreateInfo& CI, void* _Nullable view) : ImGuiImplDiligent{CI} { - ImGui_ImplOSX_Init(); - ImGuiIO& io = ImGui::GetIO(); + ImGuiIO& io = ImGui::GetIO(); io.BackendPlatformName = "Diligent-ImGuiImplMacOS"; + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; + + const auto framebufferScale = ((NSView*)view).window.screen.backingScaleFactor ?: NSScreen.mainScreen.backingScaleFactor; + io.DisplayFramebufferScale = ImVec2(framebufferScale, framebufferScale); + + InitMouseCursors(); + + io.SetClipboardTextFn = [](void*, const char* str) { + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard declareTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil]; + [pasteboard setString:[NSString stringWithUTF8String:str] forType:NSPasteboardTypeString]; + }; + io.GetClipboardTextFn = [](void*) -> const char* { + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + NSString* available = [pasteboard availableTypeFromArray:[NSArray arrayWithObject:NSPasteboardTypeString]]; + if (![available isEqualToString:NSPasteboardTypeString]) + return nullptr; + NSString* string = [pasteboard stringForType:NSPasteboardTypeString]; + if (string == nil) + return nullptr; + const char* string_c = [string UTF8String]; + static ImVector s_clipboard; + s_clipboard.resize(static_cast(strlen(string_c)) + 1); + strcpy(s_clipboard.Data, string_c); + return s_clipboard.Data; + }; + + m_FocusObserver = [[DgImGuiMacFocusObserver alloc] initWithActive:&m_AppActive dirty:&m_FocusDirty]; + [[NSNotificationCenter defaultCenter] addObserver:m_FocusObserver + selector:@selector(onApplicationBecomeActive:) + name:NSApplicationDidBecomeActiveNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:m_FocusObserver + selector:@selector(onApplicationBecomeInactive:) + name:NSApplicationDidResignActiveNotification + object:nil]; - const auto framebufferScale = ((NSView*)view).window.screen.backingScaleFactor ?: NSScreen.mainScreen.backingScaleFactor;; - io.DisplayFramebufferScale = ImVec2(framebufferScale, framebufferScale); + m_LastTime = std::chrono::high_resolution_clock::now(); } ImGuiImplMacOS::~ImGuiImplMacOS() { - ImGui_ImplOSX_Shutdown(); + if (m_FocusObserver != nullptr) + { + [[NSNotificationCenter defaultCenter] removeObserver:m_FocusObserver]; + [m_FocusObserver release]; + m_FocusObserver = nullptr; + } + for (NSCursor* Cursor : m_MouseCursors) + [Cursor release]; } -void ImGuiImplMacOS::NewFrame(Uint32 RenderSurfaceWidth, - Uint32 RenderSurfaceHeight, - SURFACE_TRANSFORM SurfacePreTransform) +void ImGuiImplMacOS::InitMouseCursors() +{ + m_MouseCursors[ImGuiMouseCursor_Arrow] = [NSCursor arrowCursor]; + m_MouseCursors[ImGuiMouseCursor_TextInput] = [NSCursor IBeamCursor]; + m_MouseCursors[ImGuiMouseCursor_Hand] = [NSCursor pointingHandCursor]; + m_MouseCursors[ImGuiMouseCursor_NotAllowed] = [NSCursor operationNotAllowedCursor]; + m_MouseCursors[ImGuiMouseCursor_ResizeNS] = [NSCursor resizeUpDownCursor]; + m_MouseCursors[ImGuiMouseCursor_ResizeEW] = [NSCursor resizeLeftRightCursor]; + m_MouseCursors[ImGuiMouseCursor_ResizeNESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] + ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor]; + m_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] + ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor]; + m_MouseCursors[ImGuiMouseCursor_ResizeAll] = CreateMoveCursor() ?: [NSCursor closedHandCursor]; + m_MouseCursors[ImGuiMouseCursor_Wait] = m_MouseCursors[ImGuiMouseCursor_Progress] = + [NSCursor respondsToSelector:@selector(busyButClickableCursor)] ? [NSCursor busyButClickableCursor] : [NSCursor arrowCursor]; + + for (NSCursor* Cursor : m_MouseCursors) + [Cursor retain]; +} + +void ImGuiImplMacOS::UpdateMouseCursor() { - std::lock_guard Lock(m_Mtx); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2(RenderSurfaceWidth / io.DisplayFramebufferScale.x, RenderSurfaceHeight / io.DisplayFramebufferScale.y); - ImGui_ImplOSX_NewFrame(nil); - ImGuiImplDiligent::NewFrame(RenderSurfaceWidth, RenderSurfaceHeight, SurfacePreTransform); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) + return; + + const ImGuiMouseCursor Cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor(); + if (m_LastMouseCursor == static_cast(Cursor)) + return; + const bool WasNone = m_LastMouseCursor == ImGuiMouseCursor_None; + m_LastMouseCursor = static_cast(Cursor); + + // NSCursor is main-thread state; Render() runs on the display link thread. + if (Cursor == ImGuiMouseCursor_None) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [NSCursor hide]; + }); + } + else + { + NSCursor* NewCursor = m_MouseCursors[Cursor] ?: m_MouseCursors[ImGuiMouseCursor_Arrow]; + dispatch_async(dispatch_get_main_queue(), ^{ + [NewCursor set]; + if (WasNone) + [NSCursor unhide]; + }); + } } -bool ImGuiImplMacOS::HandleOSXEvent(NSEvent *_Nonnull event, NSView *_Nonnull view) +void ImGuiImplMacOS::NewFrame(Uint32 RenderSurfaceWidth, + Uint32 RenderSurfaceHeight, + SURFACE_TRANSFORM SurfacePreTransform) { std::lock_guard Lock(m_Mtx); ImGuiIO& io = ImGui::GetIO(); - if (event.type == NSEventTypeMouseMoved || event.type == NSEventTypeLeftMouseDragged) + + if (m_FocusDirty.exchange(false)) { - NSRect viewRectPoints = [view bounds]; - NSPoint curPoint = [view convertPoint:[event locationInWindow] fromView:nil]; - io.MousePos.x = curPoint.x; - io.MousePos.y = viewRectPoints.size.height-1 - curPoint.y; - return io.WantCaptureMouse; + const bool Active = m_AppActive.load(); + io.AddFocusEvent(Active); + if (!Active) + { + io.ClearInputKeys(); + m_KeysPressedWithCmd.clear(); + } } - return ImGui_ImplOSX_HandleEvent((NSEvent*)event, (NSView*)view); + // macOS sends no key-up for keys pressed while Cmd is held; release them here. + for (ImGuiKey Key : m_KeysPressedWithCmd) + io.AddKeyEvent(Key, false); + m_KeysPressedWithCmd.clear(); + + const auto Now = std::chrono::high_resolution_clock::now(); + const float Elapsed = std::chrono::duration(Now - m_LastTime).count(); + m_LastTime = Now; + io.DeltaTime = Elapsed > 0.0f ? Elapsed : 1.0f / 60.0f; + + io.DisplaySize = ImVec2(RenderSurfaceWidth / io.DisplayFramebufferScale.x, + RenderSurfaceHeight / io.DisplayFramebufferScale.y); + + ImGuiImplDiligent::NewFrame(RenderSurfaceWidth, RenderSurfaceHeight, SurfacePreTransform); } void ImGuiImplMacOS::Render(IDeviceContext* pCtx) { std::lock_guard Lock(m_Mtx); + UpdateMouseCursor(); ImGuiImplDiligent::Render(pCtx); } +bool ImGuiImplMacOS::HandleOSXEvent(NSEvent* _Nonnull event, NSView* _Nonnull view) +{ + std::lock_guard Lock(m_Mtx); + ImGuiIO& io = ImGui::GetIO(); + + switch (event.type) + { + case NSEventTypeLeftMouseDown: + case NSEventTypeRightMouseDown: + case NSEventTypeOtherMouseDown: + { + const int Button = static_cast(event.buttonNumber); + if (Button >= 0 && Button < ImGuiMouseButton_COUNT) + io.AddMouseButtonEvent(Button, true); + return io.WantCaptureMouse; + } + + case NSEventTypeLeftMouseUp: + case NSEventTypeRightMouseUp: + case NSEventTypeOtherMouseUp: + { + const int Button = static_cast(event.buttonNumber); + if (Button >= 0 && Button < ImGuiMouseButton_COUNT) + io.AddMouseButtonEvent(Button, false); + return io.WantCaptureMouse; + } + + case NSEventTypeMouseMoved: + case NSEventTypeLeftMouseDragged: + case NSEventTypeRightMouseDragged: + case NSEventTypeOtherMouseDragged: + { + const NSPoint Pos = [view convertPoint:event.locationInWindow fromView:nil]; + io.AddMousePosEvent(static_cast(Pos.x), + static_cast(view.bounds.size.height - Pos.y)); + return io.WantCaptureMouse; + } + + case NSEventTypeScrollWheel: + { + double dx = event.scrollingDeltaX; + double dy = event.scrollingDeltaY; + if (event.hasPreciseScrollingDeltas) + { + dx *= 0.01; + dy *= 0.01; + } + if (dx != 0.0 || dy != 0.0) + io.AddMouseWheelEvent(static_cast(dx), static_cast(dy)); + return io.WantCaptureMouse; + } + + case NSEventTypeKeyDown: + case NSEventTypeKeyUp: + { + const bool Down = event.type == NSEventTypeKeyDown; + NSString* str = event.characters; + for (NSUInteger i = 0; i < str.length; ++i) + { + const int c = [str characterAtIndex:i]; + if (Down && !io.KeySuper && !(c >= 0xF700 && c <= 0xFFFF) && c != 127) + io.AddInputCharacter(static_cast(c)); + + const ImGuiKey Key = MapCharacterToImGuiKey(c); + if (Key != ImGuiKey_None) + { + io.AddKeyEvent(Key, Down); + if (Down && io.KeySuper) + m_KeysPressedWithCmd.push_back(Key); + } + } + return io.WantCaptureKeyboard; + } + + case NSEventTypeFlagsChanged: + { + const NSEventModifierFlags flags = event.modifierFlags; + io.AddKeyEvent(ImGuiKey_ModCtrl, (flags & NSEventModifierFlagControl) != 0); + io.AddKeyEvent(ImGuiKey_ModShift, (flags & NSEventModifierFlagShift) != 0); + io.AddKeyEvent(ImGuiKey_ModAlt, (flags & NSEventModifierFlagOption) != 0); + io.AddKeyEvent(ImGuiKey_ModSuper, (flags & NSEventModifierFlagCommand) != 0); + return io.WantCaptureKeyboard; + } + + default: + return false; + } } + +} // namespace Diligent diff --git a/NativeApp/Apple/Source/Classes/OSX/AppDelegate.m b/NativeApp/Apple/Source/Classes/OSX/AppDelegate.m index fd52cc43..e6e8bcc6 100644 --- a/NativeApp/Apple/Source/Classes/OSX/AppDelegate.m +++ b/NativeApp/Apple/Source/Classes/OSX/AppDelegate.m @@ -17,6 +17,9 @@ @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { NSWindow* mainWindow = [[NSApplication sharedApplication]mainWindow]; [mainWindow setAcceptsMouseMovedEvents:YES]; + + // Force dark appearance for the entire application + [NSApp setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]]; } - (void)applicationWillTerminate:(NSNotification *)aNotification { diff --git a/NativeApp/Apple/Source/Classes/OSX/ModeSelectionViewController.mm b/NativeApp/Apple/Source/Classes/OSX/ModeSelectionViewController.mm index 7121647d..8f1e6cc1 100644 --- a/NativeApp/Apple/Source/Classes/OSX/ModeSelectionViewController.mm +++ b/NativeApp/Apple/Source/Classes/OSX/ModeSelectionViewController.mm @@ -33,10 +33,19 @@ @implementation ModeSelectionViewController { } -- (void) setWindowTitle:(NSString*) title +- (void) setWindowTitle:(NSString*) title forWindow:(NSWindow*) window { - NSWindow* mainWindow = [[NSApplication sharedApplication]mainWindow]; - [mainWindow setTitle:title]; + [window setTitle:title]; +} + +- (void) maximizeWindow:(NSWindow*)window +{ + if (window) + { + // Fill the entire visible screen area (excluding Dock and menu bar) + NSRect screenFrame = window.screen.visibleFrame; + [window setFrame:screenFrame display:YES animate:YES]; + } } - (void)viewDidLoad @@ -97,8 +106,9 @@ - (void) terminateApp:(NSString*) error - (IBAction)goOpenGL:(id)sender { + NSWindow* window = self.view.window; ViewController* glViewController = [self.storyboard instantiateControllerWithIdentifier:@"GLViewControllerID"]; - self.view.window.contentViewController = glViewController; + window.contentViewController = glViewController; GLView* glView = (GLView*)[glViewController view]; NSString* error = [glView getError]; @@ -108,13 +118,15 @@ - (IBAction)goOpenGL:(id)sender } NSString* name = [glView getAppName]; - [self setWindowTitle:name]; + [self setWindowTitle:name forWindow:window]; + [self maximizeWindow:window]; } - (IBAction)goVulkan:(id)sender { + NSWindow* window = self.view.window; ViewController* metalViewController = [self.storyboard instantiateControllerWithIdentifier:@"MoltenVKViewControllerID"]; - self.view.window.contentViewController = metalViewController; + window.contentViewController = metalViewController; MetalView* mtlView = (MetalView*)[metalViewController view]; NSString* error = [mtlView getError]; @@ -124,14 +136,16 @@ - (IBAction)goVulkan:(id)sender } NSString* name = [mtlView getAppName]; - [self setWindowTitle:name]; + [self setWindowTitle:name forWindow:window]; + [self maximizeWindow:window]; } - (IBAction)goMetal:(id)sender { + NSWindow* window = self.view.window; ViewController* metalViewController = [self.storyboard instantiateControllerWithIdentifier:@"MetalViewControllerID"]; MetalView* mtlView = (MetalView*)[metalViewController view]; - self.view.window.contentViewController = metalViewController; + window.contentViewController = metalViewController; NSString* error = [mtlView getError]; if(error != nil) @@ -140,14 +154,16 @@ - (IBAction)goMetal:(id)sender } NSString* name = [mtlView getAppName]; - [self setWindowTitle:name]; + [self setWindowTitle:name forWindow:window]; + [self maximizeWindow:window]; } - (IBAction)goWebGPU:(id)sender { + NSWindow* window = self.view.window; ViewController* webgpuViewController = [self.storyboard instantiateControllerWithIdentifier:@"WebGPUViewControllerID"]; WebGPUView* webgpuView = (WebGPUView*)[webgpuViewController view]; - self.view.window.contentViewController = webgpuViewController; + window.contentViewController = webgpuViewController; NSString* error = [webgpuView getError]; if(error != nil) @@ -156,7 +172,8 @@ - (IBAction)goWebGPU:(id)sender } NSString* name = [webgpuView getAppName]; - [self setWindowTitle:name]; + [self setWindowTitle:name forWindow:window]; + [self maximizeWindow:window]; } @end diff --git a/NativeApp/Apple/Source/Classes/OSX/WindowController.mm b/NativeApp/Apple/Source/Classes/OSX/WindowController.mm index cdb15fec..45e4f2b6 100644 --- a/NativeApp/Apple/Source/Classes/OSX/WindowController.mm +++ b/NativeApp/Apple/Source/Classes/OSX/WindowController.mm @@ -40,6 +40,25 @@ - (instancetype)initWithWindow:(NSWindow *)window return self; } +- (void)windowDidLoad +{ + [super windowDidLoad]; + + NSWindow* window = self.window; + + // Force dark appearance to match the application UI + window.appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]; + + // Make the titlebar transparent so it blends with the window background + window.titlebarAppearsTransparent = YES; + + // Set window background color matching ImGuiColors::TitleBg (~0.07, 0.08, 0.11) + window.backgroundColor = [NSColor colorWithRed:0.07 green:0.08 blue:0.11 alpha:1.0]; + + // Hide the title text (the app name is shown in the content area) + window.titleVisibility = NSWindowTitleHidden; +} + - (void) goFullscreen { // If app is already fullscreen... diff --git a/ThirdParty/imgui_v1.85/imgui_impl_osx_v1.85.h b/ThirdParty/imgui_v1.85/imgui_impl_osx_v1.85.h deleted file mode 100644 index e4c1d04a..00000000 --- a/ThirdParty/imgui_v1.85/imgui_impl_osx_v1.85.h +++ /dev/null @@ -1,24 +0,0 @@ -// dear imgui: Platform Backend for OSX / Cocoa -// This needs to be used along with a Renderer (e.g. OpenGL2, OpenGL3, Vulkan, Metal..) -// [ALPHA] Early backend, not well tested. If you want a portable application, prefer using the GLFW or SDL platform Backends on Mac. - -// Implemented features: -// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. -// [X] Platform: OSX clipboard is supported within core Dear ImGui (no specific code in this backend). -// Issues: -// [ ] Platform: Keys are all generally very broken. Best using [event keycode] and not [event characters].. - -// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. -// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. -// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. -// Read online: https://github.com/ocornut/imgui/tree/master/docs - -#include "imgui.h" // IMGUI_IMPL_API - -@class NSEvent; -@class NSView; - -IMGUI_IMPL_API bool ImGui_ImplOSX_Init(); -IMGUI_IMPL_API void ImGui_ImplOSX_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplOSX_NewFrame(NSView* _Nullable view); -IMGUI_IMPL_API bool ImGui_ImplOSX_HandleEvent(NSEvent* _Nonnull event, NSView* _Nullable view); diff --git a/ThirdParty/imgui_v1.85/imgui_impl_osx_v1.85.mm b/ThirdParty/imgui_v1.85/imgui_impl_osx_v1.85.mm deleted file mode 100644 index 25befec8..00000000 --- a/ThirdParty/imgui_v1.85/imgui_impl_osx_v1.85.mm +++ /dev/null @@ -1,417 +0,0 @@ -// dear imgui: Platform Backend for OSX / Cocoa -// This needs to be used along with a Renderer (e.g. OpenGL2, OpenGL3, Vulkan, Metal..) -// [ALPHA] Early backend, not well tested. If you want a portable application, prefer using the GLFW or SDL platform Backends on Mac. - -// Implemented features: -// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. -// [X] Platform: OSX clipboard is supported within core Dear ImGui (no specific code in this backend). -// Issues: -// [ ] Platform: Keys are all generally very broken. Best using [event keycode] and not [event characters].. - -// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. -// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. -// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. -// Read online: https://github.com/ocornut/imgui/tree/master/docs - -#include "imgui.h" -#include "imgui_impl_osx_v1.85.h" -#import -#include -#include - -// CHANGELOG -// (minor and older changes stripped away, please see git history for details) -// 2021-09-21: Use mach_absolute_time as CFAbsoluteTimeGetCurrent can jump backwards. -// 2021-08-17: Calling io.AddFocusEvent() on NSApplicationDidBecomeActiveNotification/NSApplicationDidResignActiveNotification events. -// 2021-06-23: Inputs: Added a fix for shortcuts using CTRL key instead of CMD key. -// 2021-04-19: Inputs: Added a fix for keys remaining stuck in pressed state when CMD-tabbing into different application. -// 2021-01-27: Inputs: Added a fix for mouse position not being reported when mouse buttons other than left one are down. -// 2020-10-28: Inputs: Added a fix for handling keypad-enter key. -// 2020-05-25: Inputs: Added a fix for missing trackpad clicks when done with "soft tap". -// 2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor. -// 2019-10-11: Inputs: Fix using Backspace key. -// 2019-07-21: Re-added clipboard handlers as they are not enabled by default in core imgui.cpp (reverted 2019-05-18 change). -// 2019-05-28: Inputs: Added mouse cursor shape and visibility support. -// 2019-05-18: Misc: Removed clipboard handlers as they are now supported by core imgui.cpp. -// 2019-05-11: Inputs: Don't filter character values before calling AddInputCharacter() apart from 0xF700..0xFFFF range. -// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window. -// 2018-07-07: Initial version. - -@class ImFocusObserver; - -// Data -static double g_Time = 0.0; -static NSCursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {}; -static bool g_MouseCursorHidden = false; -static bool g_MouseJustPressed[ImGuiMouseButton_COUNT] = {}; -static bool g_MouseDown[ImGuiMouseButton_COUNT] = {}; - -static ImFocusObserver* g_FocusObserver = NULL; - -static std::vector g_KeysPressedWithCmd; - -// Undocumented methods for creating cursors. -@interface NSCursor() -+ (id)_windowResizeNorthWestSouthEastCursor; -+ (id)_windowResizeNorthEastSouthWestCursor; -+ (id)_windowResizeNorthSouthCursor; -+ (id)_windowResizeEastWestCursor; -@end - -static CFTimeInterval GetMachAbsoluteTimeInSeconds() -{ - return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9);; -} - -static void resetKeys() -{ - ImGuiIO& io = ImGui::GetIO(); - io.ClearInputKeys(); - io.KeyCtrl = io.KeyShift = io.KeyAlt = io.KeySuper = false; - g_KeysPressedWithCmd.clear(); -} - -@interface ImFocusObserver : NSObject - -- (void)onApplicationBecomeActive:(NSNotification*)aNotification; -- (void)onApplicationBecomeInactive:(NSNotification*)aNotification; - -@end - -@implementation ImFocusObserver - -- (void)onApplicationBecomeActive:(NSNotification*)aNotification -{ - ImGuiIO& io = ImGui::GetIO(); - io.AddFocusEvent(true); -} - -- (void)onApplicationBecomeInactive:(NSNotification*)aNotification -{ - ImGuiIO& io = ImGui::GetIO(); - io.AddFocusEvent(false); - - // Unfocused applications do not receive input events, therefore we must manually - // release any pressed keys when application loses focus, otherwise they would remain - // stuck in a pressed state. https://github.com/ocornut/imgui/issues/3832 - resetKeys(); -} - -@end - -// Functions -bool ImGui_ImplOSX_Init() -{ - ImGuiIO& io = ImGui::GetIO(); - - // Setup backend capabilities flags - io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) - //io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) - //io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) - //io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport; // We can set io.MouseHoveredViewport correctly (optional, not easy) - io.BackendPlatformName = "imgui_impl_osx"; - - // Load cursors. Some of them are undocumented. - g_MouseCursorHidden = false; - g_MouseCursors[ImGuiMouseCursor_Arrow] = [NSCursor arrowCursor]; - g_MouseCursors[ImGuiMouseCursor_TextInput] = [NSCursor IBeamCursor]; - g_MouseCursors[ImGuiMouseCursor_ResizeAll] = [NSCursor closedHandCursor]; - g_MouseCursors[ImGuiMouseCursor_Hand] = [NSCursor pointingHandCursor]; - g_MouseCursors[ImGuiMouseCursor_NotAllowed] = [NSCursor operationNotAllowedCursor]; - g_MouseCursors[ImGuiMouseCursor_ResizeNS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor]; - g_MouseCursors[ImGuiMouseCursor_ResizeEW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor]; - g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor]; - g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor]; - - // Note that imgui.cpp also include default OSX clipboard handlers which can be enabled - // by adding '#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS' in imconfig.h and adding '-framework ApplicationServices' to your linker command-line. - // Since we are already in ObjC land here, it is easy for us to add a clipboard handler using the NSPasteboard api. - io.SetClipboardTextFn = [](void*, const char* str) -> void - { - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard declareTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil]; - [pasteboard setString:[NSString stringWithUTF8String:str] forType:NSPasteboardTypeString]; - }; - - io.GetClipboardTextFn = [](void*) -> const char* - { - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - NSString* available = [pasteboard availableTypeFromArray: [NSArray arrayWithObject:NSPasteboardTypeString]]; - if (![available isEqualToString:NSPasteboardTypeString]) - return NULL; - - NSString* string = [pasteboard stringForType:NSPasteboardTypeString]; - if (string == nil) - return NULL; - - const char* string_c = (const char*)[string UTF8String]; - size_t string_len = strlen(string_c); - static ImVector s_clipboard; - s_clipboard.resize((int)string_len + 1); - strcpy(s_clipboard.Data, string_c); - return s_clipboard.Data; - }; - - g_FocusObserver = [[ImFocusObserver alloc] init]; - [[NSNotificationCenter defaultCenter] addObserver:g_FocusObserver - selector:@selector(onApplicationBecomeActive:) - name:NSApplicationDidBecomeActiveNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:g_FocusObserver - selector:@selector(onApplicationBecomeInactive:) - name:NSApplicationDidResignActiveNotification - object:nil]; - - return true; -} - -void ImGui_ImplOSX_Shutdown() -{ - g_FocusObserver = NULL; -} - -static void ImGui_ImplOSX_UpdateMouseCursorAndButtons() -{ - // Update buttons - ImGuiIO& io = ImGui::GetIO(); - for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) - { - // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. - io.MouseDown[i] = g_MouseJustPressed[i] || g_MouseDown[i]; - g_MouseJustPressed[i] = false; - } - - if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) - return; - - ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); - if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) - { - // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor - if (!g_MouseCursorHidden) - { - g_MouseCursorHidden = true; - [NSCursor hide]; - } - } - else - { - // Show OS mouse cursor - [g_MouseCursors[g_MouseCursors[imgui_cursor] ? imgui_cursor : ImGuiMouseCursor_Arrow] set]; - if (g_MouseCursorHidden) - { - g_MouseCursorHidden = false; - [NSCursor unhide]; - } - } -} - -void ImGui_ImplOSX_NewFrame(NSView* view) -{ - // Setup display size - ImGuiIO& io = ImGui::GetIO(); - if (view) - { - const float dpi = (float)[view.window backingScaleFactor]; - io.DisplaySize = ImVec2((float)view.bounds.size.width, (float)view.bounds.size.height); - io.DisplayFramebufferScale = ImVec2(dpi, dpi); - } - - // Setup time step - if (g_Time == 0.0) - { - g_Time = GetMachAbsoluteTimeInSeconds(); - } - double current_time = GetMachAbsoluteTimeInSeconds(); - io.DeltaTime = (float)(current_time - g_Time); - g_Time = current_time; - - ImGui_ImplOSX_UpdateMouseCursorAndButtons(); - - // Generate KeyUp event for all keys pressed while Cmd was pressed. - for (ImGuiKey key : g_KeysPressedWithCmd) - { - io.AddKeyEvent(key, false); - } - g_KeysPressedWithCmd.clear(); -} - -static ImGuiKey mapCharacterToKey(int c) -{ - if (c >= 'a' && c <= 'z') - return (ImGuiKey)(ImGuiKey_A + (c - 'a')); - if (c >= 'A' && c <= 'Z') - return (ImGuiKey)(ImGuiKey_A + (c - 'A')); - if (c >= '0' && c <= '9') - return (ImGuiKey)(ImGuiKey_0 + (c - '0')); - - switch (c) - { - case 9: return ImGuiKey_Tab; - case 13: return ImGuiKey_Enter; - case 27: return ImGuiKey_Escape; - case 127: return ImGuiKey_Backspace; - case 25: return ImGuiKey_Tab; // Shift+Tab workaround - } - - // macOS NSEvent function keys (0xF700+) - switch (c) - { - case 0xF700: return ImGuiKey_UpArrow; - case 0xF701: return ImGuiKey_DownArrow; - case 0xF702: return ImGuiKey_LeftArrow; - case 0xF703: return ImGuiKey_RightArrow; - - case 0xF704: return ImGuiKey_F1; - case 0xF705: return ImGuiKey_F2; - case 0xF706: return ImGuiKey_F3; - case 0xF707: return ImGuiKey_F4; - case 0xF708: return ImGuiKey_F5; - case 0xF709: return ImGuiKey_F6; - case 0xF70A: return ImGuiKey_F7; - case 0xF70B: return ImGuiKey_F8; - case 0xF70C: return ImGuiKey_F9; - case 0xF70D: return ImGuiKey_F10; - case 0xF70E: return ImGuiKey_F11; - case 0xF70F: return ImGuiKey_F12; - - case 0xF729: return ImGuiKey_Home; - case 0xF72B: return ImGuiKey_End; - case 0xF72C: return ImGuiKey_PageUp; - case 0xF72D: return ImGuiKey_PageDown; - } - - return ImGuiKey_None; -} - -bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view) -{ - ImGuiIO& io = ImGui::GetIO(); - - if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown) - { - int button = (int)[event buttonNumber]; - if (button >= 0 && button < IM_ARRAYSIZE(g_MouseDown)) - g_MouseDown[button] = g_MouseJustPressed[button] = true; - return io.WantCaptureMouse; - } - - if (event.type == NSEventTypeLeftMouseUp || event.type == NSEventTypeRightMouseUp || event.type == NSEventTypeOtherMouseUp) - { - int button = (int)[event buttonNumber]; - if (button >= 0 && button < IM_ARRAYSIZE(g_MouseDown)) - g_MouseDown[button] = false; - return io.WantCaptureMouse; - } - - if (event.type == NSEventTypeMouseMoved || event.type == NSEventTypeLeftMouseDragged || event.type == NSEventTypeRightMouseDragged || event.type == NSEventTypeOtherMouseDragged) - { - NSPoint mousePoint = event.locationInWindow; - mousePoint = [view convertPoint:mousePoint fromView:nil]; - mousePoint = NSMakePoint(mousePoint.x, view.bounds.size.height - mousePoint.y); - io.MousePos = ImVec2((float)mousePoint.x, (float)mousePoint.y); - } - - if (event.type == NSEventTypeScrollWheel) - { - double wheel_dx = 0.0; - double wheel_dy = 0.0; - - #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 - if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) - { - wheel_dx = [event scrollingDeltaX]; - wheel_dy = [event scrollingDeltaY]; - if ([event hasPreciseScrollingDeltas]) - { - wheel_dx *= 0.1; - wheel_dy *= 0.1; - } - } - else - #endif // MAC_OS_X_VERSION_MAX_ALLOWED - { - wheel_dx = [event deltaX]; - wheel_dy = [event deltaY]; - } - - if (fabs(wheel_dx) > 0.0) - io.MouseWheelH += (float)wheel_dx * 0.1f; - if (fabs(wheel_dy) > 0.0) - io.MouseWheel += (float)wheel_dy * 0.1f; - return io.WantCaptureMouse; - } - - // FIXME: All the key handling is wrong and broken. Refer to GLFW's cocoa_init.mm and cocoa_window.mm. - if (event.type == NSEventTypeKeyDown) - { - NSString* str = [event characters]; - NSUInteger len = [str length]; - for (NSUInteger i = 0; i < len; i++) - { - int c = [str characterAtIndex:i]; - if (!io.KeySuper && !(c >= 0xF700 && c <= 0xFFFF) && c != 127) - io.AddInputCharacter((unsigned int)c); - - ImGuiKey key = mapCharacterToKey(c); - if (key != ImGuiKey_None) - { - io.AddKeyEvent(key, true); - - // NB: AddKeyEvent swaps Ctrl and Cmd (aka Super) on Mac - if (io.KeyCtrl) - { - // MacOS does not generate KeyUp event when Cmd is pressed. - g_KeysPressedWithCmd.push_back(key); - } - } - } - - return io.WantCaptureKeyboard; - } - - if (event.type == NSEventTypeKeyUp) - { - NSString* str = [event characters]; - NSUInteger len = [str length]; - for (NSUInteger i = 0; i < len; i++) - { - int c = [str characterAtIndex:i]; - ImGuiKey key = mapCharacterToKey(c); - if (key != ImGuiKey_None) - { - io.AddKeyEvent(key, false); - } - } - return io.WantCaptureKeyboard; - } - - if (event.type == NSEventTypeFlagsChanged) - { - unsigned int flags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; - - bool oldKeyCtrl = io.KeyCtrl; - bool oldKeyShift = io.KeyShift; - bool oldKeyAlt = io.KeyAlt; - bool oldKeySuper = io.KeySuper; - - bool KeyCtrl = flags & NSEventModifierFlagControl; - bool KeyShift = flags & NSEventModifierFlagShift; - bool KeyAlt = flags & NSEventModifierFlagOption; - bool KeySuper = flags & NSEventModifierFlagCommand; - - // NB: AddKeyEvent swaps Ctrl and Cmd (aka Super) on Mac - io.AddKeyEvent(ImGuiKey_ModCtrl, KeyCtrl); - io.AddKeyEvent(ImGuiKey_ModShift, KeyShift); - io.AddKeyEvent(ImGuiKey_ModAlt, KeyAlt); - io.AddKeyEvent(ImGuiKey_ModSuper, KeySuper); - - // We must reset keys as we will not receive any keyUp event if they where pressed with a modifier - if ((oldKeyShift && !io.KeyShift) || (oldKeyCtrl && !io.KeyCtrl) || (oldKeyAlt && !io.KeyAlt) || (oldKeySuper && !io.KeySuper)) - resetKeys(); - - return io.WantCaptureKeyboard; - } - - return false; -}