diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 6dcc7267ad96..428433d711e7 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -4,8 +4,7 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// Missing features: -// [ ] Renderer: Multi-viewport / platform windows. +// [X] Renderer: Multi-viewport / platform windows. // You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. @@ -15,6 +14,7 @@ // (minor and older changes stripped away, please see git history for details) // 2021-02-18: Metal: Change blending equation to preserve alpha in output buffer. // 2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst. +// 2019-09-17: Metal: Added support for Multi-viewport. // 2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. // 2019-02-11: Metal: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. @@ -25,8 +25,32 @@ #include "imgui_impl_metal.h" #import -// #import // Not supported in XCode 9.2. Maybe a macro to detect the SDK version can be used (something like #if MACOS_SDK >= 10.13 ...) +#if TARGET_OS_SIMULATOR == 0 || __IPHONE_13_0 +#import +#else +// Compatible for Xcode version below 12.X +#import +@protocol CAMetalDrawable +@property(readonly) id texture; +@end +@interface CAMetalLayer : CALayer +@property(nullable, retain) id device; +@property MTLPixelFormat pixelFormat; +@property BOOL framebufferOnly; +@property CGSize drawableSize; +- (nullable id)nextDrawable; +@end +#endif #import +#if TARGET_OS_OSX +#import +#endif + +// Forward Declarations +static void ImGui_ImplMetal_InitPlatformInterface(); +static void ImGui_ImplMetal_ShutdownPlatformInterface(); +static void ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows(); +static void ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows(); #pragma mark - Support classes @@ -83,6 +107,7 @@ bool ImGui_ImplMetal_Init(id device) ImGuiIO& io = ImGui::GetIO(); io.BackendRendererName = "imgui_impl_metal"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -91,11 +116,15 @@ bool ImGui_ImplMetal_Init(id device) ImGui_ImplMetal_CreateDeviceObjects(device); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + ImGui_ImplMetal_InitPlatformInterface(); + return true; } void ImGui_ImplMetal_Shutdown() { + ImGui_ImplMetal_ShutdownPlatformInterface(); ImGui_ImplMetal_DestroyDeviceObjects(); } @@ -133,6 +162,7 @@ bool ImGui_ImplMetal_CreateDeviceObjects(id device) { [g_sharedMetalContext makeDeviceObjectsWithDevice:device]; + ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows(); ImGui_ImplMetal_CreateFontsTexture(device); return true; @@ -141,9 +171,145 @@ bool ImGui_ImplMetal_CreateDeviceObjects(id device) void ImGui_ImplMetal_DestroyDeviceObjects() { ImGui_ImplMetal_DestroyFontsTexture(); + ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows(); + [g_sharedMetalContext emptyRenderPipelineStateCache]; } +//-------------------------------------------------------------------------------------------------------- +// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT +// This is an _advanced_ and _optional_ feature, allowing the back-end to create and handle multiple viewports simultaneously. +// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first.. +//-------------------------------------------------------------------------------------------------------- + +struct ImGuiViewportDataMetal +{ + CAMetalLayer* MetalLayer; + id CommandQueue; + MTLRenderPassDescriptor* RenderPassDescriptor; + void* Handle; + + ImGuiViewportDataMetal() { Handle = NULL; } + ~ImGuiViewportDataMetal() {} +}; + +static void ImGui_ImplMetal_CreateWindow(ImGuiViewport* viewport) +{ + ImGuiViewportDataMetal* data = IM_NEW(ImGuiViewportDataMetal)(); + viewport->RendererUserData = data; + + // PlatformHandleRaw should always be a NSWindow*, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL_Window*). + // Some back-ends will leave PlatformHandleRaw NULL, in which case we assume PlatformHandle will contain the NSWindow*. + void* handle = viewport->PlatformHandleRaw ? viewport->PlatformHandleRaw : viewport->PlatformHandle; + IM_ASSERT(handle != 0); + + id device = [g_sharedMetalContext.depthStencilState device]; + + CAMetalLayer* layer = [CAMetalLayer layer]; + layer.device = device; + layer.framebufferOnly = YES; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm; +#if TARGET_OS_OSX + NSWindow* window = (__bridge NSWindow*)handle; + NSView* view = nil; + if (view == nil) + view = [window contentView]; + if (view == nil) + view = [[window contentViewController] view]; + [view setLayer:layer]; + [view setWantsLayer:YES]; +#endif + data->MetalLayer = layer; + data->CommandQueue = [device newCommandQueue]; + data->RenderPassDescriptor = [[MTLRenderPassDescriptor alloc] init]; + data->Handle = handle; +} + +static void ImGui_ImplMetal_DestroyWindow(ImGuiViewport* viewport) +{ + // The main viewport (owned by the application) will always have RendererUserData == NULL since we didn't create the data for it. + if (ImGuiViewportDataMetal* data = (ImGuiViewportDataMetal*)viewport->RendererUserData) + { + IM_DELETE(data); + } + viewport->RendererUserData = NULL; +} + +static void ImGui_ImplMetal_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) +{ + ImGuiViewportDataMetal* data = (ImGuiViewportDataMetal*)viewport->RendererUserData; + + CGSize drawableSize = CGSizeMake(size.x, size.y); + drawableSize.width *= viewport->DpiScale; + drawableSize.height *= viewport->DpiScale; + data->MetalLayer.drawableSize = drawableSize; +} + +static void ImGui_ImplMetal_RenderWindow(ImGuiViewport* viewport, void*) +{ + ImGuiViewportDataMetal* data = (ImGuiViewportDataMetal*)viewport->RendererUserData; + +#if TARGET_OS_OSX + void* handle = viewport->PlatformHandleRaw ? viewport->PlatformHandleRaw : viewport->PlatformHandle; + NSWindow* window = (__bridge NSWindow*)handle; + viewport->DpiScale = [window backingScaleFactor]; + if (data->MetalLayer.contentsScale != viewport->DpiScale) + { + data->MetalLayer.contentsScale = viewport->DpiScale; + + CGSize drawableSize = [window frame].size; + drawableSize.width *= viewport->DpiScale; + drawableSize.height *= viewport->DpiScale; + data->MetalLayer.drawableSize = drawableSize; + } + viewport->DrawData->FramebufferScale = ImVec2(viewport->DpiScale, viewport->DpiScale); +#endif + + id drawable = [data->MetalLayer nextDrawable]; + + MTLRenderPassDescriptor* renderPassDescriptor = data->RenderPassDescriptor; + renderPassDescriptor.colorAttachments[0].texture = [drawable texture]; + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0); + + id commandBuffer = [data->CommandQueue commandBuffer]; + id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + ImGui_ImplMetal_RenderDrawData(viewport->DrawData, commandBuffer, renderEncoder); + [renderEncoder endEncoding]; + + [commandBuffer presentDrawable:drawable]; + [commandBuffer commit]; +} + +static void ImGui_ImplMetal_InitPlatformInterface() +{ + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Renderer_CreateWindow = ImGui_ImplMetal_CreateWindow; + platform_io.Renderer_DestroyWindow = ImGui_ImplMetal_DestroyWindow; + platform_io.Renderer_SetWindowSize = ImGui_ImplMetal_SetWindowSize; + platform_io.Renderer_RenderWindow = ImGui_ImplMetal_RenderWindow; +} + +static void ImGui_ImplMetal_ShutdownPlatformInterface() +{ + ImGui::DestroyPlatformWindows(); +} + +static void ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows() +{ + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + for (int i = 1; i < platform_io.Viewports.Size; i++) + if (!platform_io.Viewports[i]->RendererUserData) + ImGui_ImplMetal_CreateWindow(platform_io.Viewports[i]); +} + +static void ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows() +{ + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + for (int i = 1; i < platform_io.Viewports.Size; i++) + if (platform_io.Viewports[i]->RendererUserData) + ImGui_ImplMetal_DestroyWindow(platform_io.Viewports[i]); +} + #pragma mark - MetalBuffer implementation @implementation MetalBuffer @@ -522,7 +688,6 @@ - (void)renderDrawData:(ImDrawData *)drawData }; [commandEncoder setScissorRect:scissorRect]; - // Bind texture, Draw if (pcmd->TextureId != NULL) [commandEncoder setFragmentTexture:(__bridge id)(pcmd->TextureId) atIndex:0]; diff --git a/backends/imgui_impl_osx.h b/backends/imgui_impl_osx.h index fe5566d81d17..c16542544f6e 100644 --- a/backends/imgui_impl_osx.h +++ b/backends/imgui_impl_osx.h @@ -5,9 +5,9 @@ // 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). +// [X] Platform: Multi-viewport / platform windows. // Issues: // [ ] Platform: Keys are all generally very broken. Best using [event keycode] and not [event characters].. -// [ ] Platform: Multi-viewport / platform windows. // You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. @@ -17,8 +17,10 @@ @class NSEvent; @class NSView; +@class NSViewController; -IMGUI_IMPL_API bool ImGui_ImplOSX_Init(); +IMGUI_IMPL_API bool ImGui_ImplOSX_Init(NSView* _Nullable view); 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); +IMGUI_IMPL_API void ImGui_ImplOSX_AddTrackingArea(NSViewController* _Nonnull controller); diff --git a/backends/imgui_impl_osx.mm b/backends/imgui_impl_osx.mm index c81abd399f69..5429f6e9846c 100644 --- a/backends/imgui_impl_osx.mm +++ b/backends/imgui_impl_osx.mm @@ -5,9 +5,9 @@ // 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). +// [X] Platform: Multi-viewport / platform windows. // Issues: // [ ] Platform: Keys are all generally very broken. Best using [event keycode] and not [event characters].. -// [ ] Platform: Multi-viewport / platform windows. // You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. @@ -24,6 +24,7 @@ // 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-09-07: Implement Multi-viewport. // 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. @@ -32,11 +33,19 @@ // 2018-07-07: Initial version. // Data +static NSWindow* g_Window = nil; +static id g_Monitor = nil; static CFAbsoluteTime 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 bool g_WantUpdateMonitors = true; + +// Forward Declarations +static void ImGui_ImplOSX_InitPlatformInterface(); +static void ImGui_ImplOSX_ShutdownPlatformInterface(); +static void ImGui_ImplOSX_UpdateMonitors(); // Undocumented methods for creating cursors. @interface NSCursor() @@ -47,17 +56,28 @@ + (id)_windowResizeEastWestCursor; @end // Functions -bool ImGui_ImplOSX_Init() +bool ImGui_ImplOSX_Init(NSView* view) { 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_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"; + // Our mouse update function expect PlatformHandle to be filled for the main viewport + g_Window = [view window]; + if (g_Window == nil) + { + g_Window = [NSApp orderedWindows].firstObject; + } + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + main_viewport->PlatformHandle = main_viewport->PlatformHandleRaw = (__bridge_retained void*)g_Window; + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + ImGui_ImplOSX_InitPlatformInterface(); + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeyDown[] array. const int offset_for_function_keys = 256 - 0xF700; io.KeyMap[ImGuiKey_Tab] = '\t'; @@ -129,6 +149,13 @@ bool ImGui_ImplOSX_Init() void ImGui_ImplOSX_Shutdown() { + ImGui_ImplOSX_ShutdownPlatformInterface(); + g_Window = nil; + if (g_Monitor != nil) + { + [NSEvent removeMonitor:g_Monitor]; + g_Monitor = nil; + } } static void ImGui_ImplOSX_UpdateMouseCursorAndButtons() @@ -169,6 +196,59 @@ static void ImGui_ImplOSX_UpdateMouseCursorAndButtons() void ImGui_ImplOSX_NewFrame(NSView* view) { + // Set other windows to floating when mouse hit the main window + NSArray* orderedWindows = [NSApp orderedWindows]; + if ([NSApp isActive] == false || [g_Window isMiniaturized]) + { + for (NSUInteger i = orderedWindows.count; i > 0; --i) + { + NSWindow* window = orderedWindows[i - 1]; + if (window.parentWindow != g_Window) + continue; + [window setLevel:NSNormalWindowLevel]; + [window setIsVisible:[g_Window isMiniaturized] == NO]; + [window setParentWindow:g_Window]; + } + } + else if (NSPointInRect([NSEvent mouseLocation], [g_Window frame])) + { + if ([NSApp isActive] && [g_Window isMiniaturized] == NO) + { + for (NSUInteger i = orderedWindows.count; i > 0; --i) + { + NSWindow* window = orderedWindows[i - 1]; + if (window.parentWindow != g_Window) + continue; + [window setLevel:NSFloatingWindowLevel]; + [window setIsVisible:YES]; + [window setParentWindow:g_Window]; + } + } + } + else + { + // Reorder other windows when the main window is focused + bool foundMainWindow = false; + for (NSUInteger i = orderedWindows.count; i > 0; --i) + { + NSWindow* window = orderedWindows[i - 1]; + if (window == g_Window) + { + foundMainWindow = true; + continue; + } + if (window.parentWindow != g_Window) + continue; + if (foundMainWindow == false) + { + [window orderWindow:NSWindowAbove relativeTo:0]; + } + [window setLevel:NSNormalWindowLevel]; + [window setIsVisible:YES]; + [window setParentWindow:g_Window]; + } + } + // Setup display size ImGuiIO& io = ImGui::GetIO(); if (view) @@ -177,6 +257,8 @@ void ImGui_ImplOSX_NewFrame(NSView* view) io.DisplaySize = ImVec2((float)view.bounds.size.width, (float)view.bounds.size.height); io.DisplayFramebufferScale = ImVec2(dpi, dpi); } + if (g_WantUpdateMonitors) + ImGui_ImplOSX_UpdateMonitors(); // Setup time step if (g_Time == 0.0) @@ -230,10 +312,22 @@ bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view) 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); + NSSize size; + NSPoint mousePoint; + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + NSScreen* screen = [[view window] screen]; + size = [screen frame].size; + mousePoint = [NSEvent mouseLocation]; + } + else + { + size = [view bounds].size; + mousePoint = event.locationInWindow; + } + mousePoint = NSMakePoint(mousePoint.x, size.height - mousePoint.y); io.MousePos = ImVec2((float)mousePoint.x, (float)mousePoint.y); + return io.WantCaptureMouse; } if (event.type == NSEventTypeScrollWheel) @@ -322,3 +416,312 @@ bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view) return false; } + +void ImGui_ImplOSX_AddTrackingArea(NSViewController* _Nonnull controller) +{ + // Add a tracking area in order to receive mouse events whenever the mouse is within the bounds of our view + NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect + options:NSTrackingMouseMoved | NSTrackingInVisibleRect | NSTrackingActiveAlways + owner:controller + userInfo:nil]; + [controller.view addTrackingArea:trackingArea]; + + // If we want to receive key events, we either need to be in the responder chain of the key view, + // or else we can install a local monitor. The consequence of this heavy-handed approach is that + // we receive events for all controls, not just Dear ImGui widgets. If we had native controls in our + // window, we'd want to be much more careful than just ingesting the complete event stream. + // To match the behavior of other backends, we pass every event down to the OS. + if (g_Monitor) + return; + NSEventMask eventMask = 0; + eventMask |= NSEventMaskMouseMoved | NSEventMaskScrollWheel; + eventMask |= NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged; + eventMask |= NSEventMaskRightMouseDown | NSEventMaskRightMouseUp | NSEventMaskRightMouseDragged; + eventMask |= NSEventMaskOtherMouseDown | NSEventMaskOtherMouseUp | NSEventMaskOtherMouseDragged; + eventMask |= NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged; + g_Monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^NSEvent * _Nullable(NSEvent *event) + { + ImGui_ImplOSX_HandleEvent(event, controller.view); + return event; + }]; +} + +//-------------------------------------------------------------------------------------------------------- +// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT +// This is an _advanced_ and _optional_ feature, allowing the back-end to create and handle multiple viewports simultaneously. +// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first.. +//-------------------------------------------------------------------------------------------------------- + +struct ImGuiViewportDataOSX +{ + NSWindow* Window; + bool WindowOwned; + + ImGuiViewportDataOSX() { WindowOwned = false; } + ~ImGuiViewportDataOSX() { IM_ASSERT(Window == nil); } +}; + +@interface ImGui_ImplOSX_View : NSView +@end + +@implementation ImGui_ImplOSX_View +@end + +@interface ImGui_ImplOSX_ViewController : NSViewController +@end + +@implementation ImGui_ImplOSX_ViewController + +-(void)loadView +{ + self.view = [NSView new]; +} + +@end + +static void ImGui_ImplOSX_CreateWindow(ImGuiViewport* viewport) +{ + ImGuiViewportDataOSX* data = IM_NEW(ImGuiViewportDataOSX)(); + viewport->PlatformUserData = data; + + NSScreen* screen = [g_Window screen]; + NSSize size = [screen frame].size; + NSRect rect = NSMakeRect(viewport->Pos.x, size.height - viewport->Pos.y - viewport->Size.y, viewport->Size.x, viewport->Size.y); + + NSWindow* window = [[NSWindow alloc] initWithContentRect:rect styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:YES]; + [window setTitle:@"Untitled"]; + [window setOpaque:NO]; + [window setParentWindow:g_Window]; + + ImGui_ImplOSX_View* view = [[ImGui_ImplOSX_View alloc] initWithFrame:rect]; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) + [view setWantsBestResolutionOpenGLSurface:YES]; +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + + ImGui_ImplOSX_ViewController* viewController = [ImGui_ImplOSX_ViewController new]; + window.contentViewController = viewController; + window.contentViewController.view = view; + ImGui_ImplOSX_AddTrackingArea(viewController); + + data->Window = window; + data->WindowOwned = true; + viewport->PlatformRequestResize = false; + viewport->PlatformHandle = viewport->PlatformHandleRaw = (__bridge_retained void*)window; +} + +static void ImGui_ImplOSX_DestroyWindow(ImGuiViewport* viewport) +{ + NSWindow* window = (__bridge_transfer NSWindow*)viewport->PlatformHandleRaw; + window = nil; + + if (ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData) + { + NSWindow* window = data->Window; + if (window != nil && data->WindowOwned) + { + [window setContentView:nil]; + [window setContentViewController:nil]; + [window orderOut:nil]; + } + data->Window = nil; + IM_DELETE(data); + } + viewport->PlatformUserData = viewport->PlatformHandle = viewport->PlatformHandleRaw = NULL; +} + +static void ImGui_ImplOSX_ShowWindow(ImGuiViewport* viewport) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != nil); +} + +static void ImGui_ImplOSX_UpdateWindow(ImGuiViewport* viewport) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); +} + +static ImVec2 ImGui_ImplOSX_GetWindowPos(ImGuiViewport* viewport) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); + + NSWindow* window = data->Window; + NSScreen* screen = [window screen]; + NSSize size = [screen frame].size; + NSRect frame = [window frame]; + NSRect rect = [window contentLayoutRect]; + return ImVec2(frame.origin.x, size.height - frame.origin.y - rect.size.height); +} + +static void ImGui_ImplOSX_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); + + NSWindow* window = data->Window; + NSScreen* screen = [window screen]; + NSSize size = [screen frame].size; + NSRect rect = [window contentLayoutRect]; + NSRect origin = NSMakeRect(pos.x, size.height - pos.y - rect.size.height, 0, 0); + [window setFrameOrigin:origin.origin]; +} + +static ImVec2 ImGui_ImplOSX_GetWindowSize(ImGuiViewport* viewport) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); + + NSWindow* window = data->Window; + NSSize size = [window contentLayoutRect].size; + return ImVec2(size.width, size.width); +} + +static void ImGui_ImplOSX_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); + + NSWindow* window = data->Window; + NSRect rect = [window frame]; + rect.origin.y -= (size.y - rect.size.height); + rect.size.width = size.x; + rect.size.height = size.y; + [window setFrame:rect display:YES]; +} + +static void ImGui_ImplOSX_SetWindowFocus(ImGuiViewport* viewport) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); + + [data->Window orderWindow:NSWindowAbove relativeTo:0]; +} + +static bool ImGui_ImplOSX_GetWindowFocus(ImGuiViewport* viewport) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); + + NSArray* array = [NSApp orderedWindows]; + for (NSInteger i = 0; i < array.count; ++i) + { + NSWindow* window = array[i]; + if (window != g_Window && window.parentWindow != g_Window) + continue; + return (window == data->Window); + } + return false; +} + +static bool ImGui_ImplOSX_GetWindowMinimized(ImGuiViewport* viewport) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); + + return [data->Window isMiniaturized]; +} + +static void ImGui_ImplOSX_SetWindowTitle(ImGuiViewport* viewport, const char* title) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); + + [data->Window setTitle:[NSString stringWithUTF8String:title]]; +} + +static void ImGui_ImplOSX_SetWindowAlpha(ImGuiViewport* viewport, float alpha) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); + IM_ASSERT(alpha >= 0.0f && alpha <= 1.0f); + + [data->Window setAlphaValue:alpha]; +} + +static float ImGui_ImplOSX_GetWindowDpiScale(ImGuiViewport* viewport) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); + + return [data->Window backingScaleFactor]; +} + +// FIXME-DPI: Testing DPI related ideas +static void ImGui_ImplOSX_OnChangedViewport(ImGuiViewport* viewport) +{ + (void)viewport; +#if 0 + ImGuiStyle default_style; + //default_style.WindowPadding = ImVec2(0, 0); + //default_style.WindowBorderSize = 0.0f; + //default_style.ItemSpacing.y = 3.0f; + //default_style.FramePadding = ImVec2(0, 0); + default_style.ScaleAllSizes(viewport->DpiScale); + ImGuiStyle& style = ImGui::GetStyle(); + style = default_style; +#endif +} + +static void ImGui_ImplOSX_UpdateMonitors() +{ + ImGui::GetPlatformIO().Monitors.resize(0); + + NSArray* array = [NSScreen screens]; + for (NSUInteger i = 0; i < array.count; ++i) + { + NSScreen* screen = array[i]; + NSRect frame = [screen frame]; + NSRect visibleFrame = [screen visibleFrame]; + + ImGuiPlatformMonitor imgui_monitor; + imgui_monitor.MainPos = ImVec2(frame.origin.x, frame.origin.y); + imgui_monitor.MainSize = ImVec2(frame.size.width, frame.size.height); + imgui_monitor.WorkPos = ImVec2(visibleFrame.origin.x, visibleFrame.origin.y); + imgui_monitor.WorkSize = ImVec2(visibleFrame.size.width, visibleFrame.size.height); + imgui_monitor.DpiScale = [screen backingScaleFactor]; + + ImGuiPlatformIO& io = ImGui::GetPlatformIO(); + io.Monitors.push_back(imgui_monitor); + } + + g_WantUpdateMonitors = false; +} + +static void ImGui_ImplOSX_InitPlatformInterface() +{ + ImGui_ImplOSX_UpdateMonitors(); + + // Register platform interface (will be coupled with a renderer interface) + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Platform_CreateWindow = ImGui_ImplOSX_CreateWindow; + platform_io.Platform_DestroyWindow = ImGui_ImplOSX_DestroyWindow; + platform_io.Platform_ShowWindow = ImGui_ImplOSX_ShowWindow; + platform_io.Platform_SetWindowPos = ImGui_ImplOSX_SetWindowPos; + platform_io.Platform_GetWindowPos = ImGui_ImplOSX_GetWindowPos; + platform_io.Platform_SetWindowSize = ImGui_ImplOSX_SetWindowSize; + platform_io.Platform_GetWindowSize = ImGui_ImplOSX_GetWindowSize; + platform_io.Platform_SetWindowFocus = ImGui_ImplOSX_SetWindowFocus; + platform_io.Platform_GetWindowFocus = ImGui_ImplOSX_GetWindowFocus; + platform_io.Platform_GetWindowMinimized = ImGui_ImplOSX_GetWindowMinimized; + platform_io.Platform_SetWindowTitle = ImGui_ImplOSX_SetWindowTitle; + platform_io.Platform_SetWindowAlpha = ImGui_ImplOSX_SetWindowAlpha; + platform_io.Platform_UpdateWindow = ImGui_ImplOSX_UpdateWindow; + platform_io.Platform_GetWindowDpiScale = ImGui_ImplOSX_GetWindowDpiScale; // FIXME-DPI + platform_io.Platform_OnChangedViewport = ImGui_ImplOSX_OnChangedViewport; // FIXME-DPI + + // Register main window handle (which is owned by the main application, not by us) + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + ImGuiViewportDataOSX* data = IM_NEW(ImGuiViewportDataOSX)(); + data->Window = g_Window; + data->WindowOwned = false; + main_viewport->PlatformUserData = data; + main_viewport->PlatformHandle = (__bridge void*)g_Window; +} + +static void ImGui_ImplOSX_ShutdownPlatformInterface() +{ + +} + diff --git a/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj b/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj index 040fcd642f95..d8cc768522cf 100644 --- a/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj +++ b/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj @@ -29,6 +29,8 @@ 83BBEA0820EB54E700295997 /* imgui_demo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0220EB54E700295997 /* imgui_demo.cpp */; }; 83BBEA0920EB54E700295997 /* imgui.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0320EB54E700295997 /* imgui.cpp */; }; 83BBEA0A20EB54E700295997 /* imgui.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0320EB54E700295997 /* imgui.cpp */; }; + D6F5BA11254D0334007A0769 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6F5BA10254D0334007A0769 /* QuartzCore.framework */; }; + D6F5BA13254D03ED007A0769 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6F5BA12254D03ED007A0769 /* QuartzCore.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -57,6 +59,8 @@ 83BBEA0220EB54E700295997 /* imgui_demo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_demo.cpp; path = ../../imgui_demo.cpp; sourceTree = ""; }; 83BBEA0320EB54E700295997 /* imgui.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui.cpp; path = ../../imgui.cpp; sourceTree = ""; }; 83BBEA0420EB54E700295997 /* imconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imconfig.h; path = ../../imconfig.h; sourceTree = ""; }; + D6F5BA10254D0334007A0769 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + D6F5BA12254D03ED007A0769 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -67,6 +71,7 @@ 8309BD8F253CCAAA0045E2A1 /* UIKit.framework in Frameworks */, 83BBE9E720EB46BD00295997 /* MetalKit.framework in Frameworks */, 83BBE9E520EB46B900295997 /* Metal.framework in Frameworks */, + D6F5BA13254D03ED007A0769 /* QuartzCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -77,6 +82,7 @@ 8309BDC6253CCCFE0045E2A1 /* AppKit.framework in Frameworks */, 83BBE9EC20EB471700295997 /* MetalKit.framework in Frameworks */, 83BBE9ED20EB471700295997 /* Metal.framework in Frameworks */, + D6F5BA11254D0334007A0769 /* QuartzCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -133,6 +139,8 @@ 83BBE9E320EB46B800295997 /* Frameworks */ = { isa = PBXGroup; children = ( + D6F5BA10254D0334007A0769 /* QuartzCore.framework */, + D6F5BA12254D03ED007A0769 /* QuartzCore.framework */, 8309BDC5253CCCFE0045E2A1 /* AppKit.framework */, 8309BD8E253CCAAA0045E2A1 /* UIKit.framework */, 83BBE9EE20EB471C00295997 /* ModelIO.framework */, diff --git a/examples/example_apple_metal/main.mm b/examples/example_apple_metal/main.mm index 273811d376bd..f84d9c89111d 100644 --- a/examples/example_apple_metal/main.mm +++ b/examples/example_apple_metal/main.mm @@ -19,7 +19,7 @@ #if TARGET_OS_OSX #include "imgui_impl_osx.h" -@interface ViewController : NSViewController +@interface ViewController : NSViewController @end #else @interface ViewController : UIViewController @@ -54,11 +54,21 @@ - (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullab ImGuiIO& io = ImGui::GetIO(); (void)io; //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows // Setup Dear ImGui style ImGui::StyleColorsDark(); //ImGui::StyleColorsClassic(); + // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. + ImGuiStyle& style = ImGui::GetStyle(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + style.WindowRounding = 0.0f; + style.Colors[ImGuiCol_WindowBg].w = 1.0f; + } + // Setup Renderer backend ImGui_ImplMetal_Init(_device); @@ -98,27 +108,10 @@ - (void)viewDidLoad self.mtkView.delegate = self; #if TARGET_OS_OSX - // Add a tracking area in order to receive mouse events whenever the mouse is within the bounds of our view - NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect - options:NSTrackingMouseMoved | NSTrackingInVisibleRect | NSTrackingActiveAlways - owner:self - userInfo:nil]; - [self.view addTrackingArea:trackingArea]; - - // If we want to receive key events, we either need to be in the responder chain of the key view, - // or else we can install a local monitor. The consequence of this heavy-handed approach is that - // we receive events for all controls, not just Dear ImGui widgets. If we had native controls in our - // window, we'd want to be much more careful than just ingesting the complete event stream. - // To match the behavior of other backends, we pass every event down to the OS. - NSEventMask eventMask = NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged | NSEventTypeScrollWheel; - [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^NSEvent * _Nullable(NSEvent *event) - { - ImGui_ImplOSX_HandleEvent(event, self.view); - return event; - }]; - - ImGui_ImplOSX_Init(); + ImGui_ImplOSX_AddTrackingArea(self); + ImGui_ImplOSX_Init(self.view); + [NSApp activateIgnoringOtherApps:YES]; #endif } @@ -126,48 +119,17 @@ - (void)viewDidLoad #if TARGET_OS_OSX -- (void)mouseMoved:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} - -- (void)mouseDown:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} - -- (void)rightMouseDown:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} - -- (void)otherMouseDown:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} - -- (void)mouseUp:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} - -- (void)rightMouseUp:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} - -- (void)otherMouseUp:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} - -- (void)mouseDragged:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} - -- (void)rightMouseDragged:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); -} - -- (void)otherMouseDragged:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); +- (void)viewWillAppear +{ + [super viewWillAppear]; + self.view.window.delegate = self; } -- (void)scrollWheel:(NSEvent *)event { - ImGui_ImplOSX_HandleEvent(event, self.view); +- (void)windowWillClose:(NSNotification *)notification +{ + ImGui_ImplMetal_Shutdown(); + ImGui_ImplOSX_Shutdown(); + ImGui::DestroyContext(); } #else @@ -299,6 +261,7 @@ - (void)drawInMTKView:(MTKView*)view // Rendering ImGui::Render(); ImDrawData* draw_data = ImGui::GetDrawData(); + draw_data->FramebufferScale = ImVec2(framebufferScale, framebufferScale); ImGui_ImplMetal_RenderDrawData(draw_data, commandBuffer, renderEncoder); [renderEncoder popDebugGroup]; @@ -308,6 +271,13 @@ - (void)drawInMTKView:(MTKView*)view } [commandBuffer commit]; + + // Update and Render additional Platform Windows + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + } } - (void)mtkView:(MTKView*)view drawableSizeWillChange:(CGSize)size diff --git a/examples/example_apple_opengl2/main.mm b/examples/example_apple_opengl2/main.mm index bb269c8b3e7a..78f2db664eda 100644 --- a/examples/example_apple_opengl2/main.mm +++ b/examples/example_apple_opengl2/main.mm @@ -41,6 +41,12 @@ -(void)prepareOpenGL -(void)updateAndDrawDemoView { +#if TARGET_OS_OSX + CGFloat framebufferScale = self.window.screen.backingScaleFactor ?: NSScreen.mainScreen.backingScaleFactor; +#else + CGFloat framebufferScale = self.window.screen.scale ?: UIScreen.mainScreen.scale; +#endif + // Start the Dear ImGui frame ImGui_ImplOpenGL2_NewFrame(); ImGui_ImplOSX_NewFrame(self); @@ -93,6 +99,7 @@ -(void)updateAndDrawDemoView [[self openGLContext] makeCurrentContext]; ImDrawData* draw_data = ImGui::GetDrawData(); + draw_data->FramebufferScale = ImVec2(framebufferScale, framebufferScale); GLsizei width = (GLsizei)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); GLsizei height = (GLsizei)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); glViewport(0, 0, width, height); @@ -104,6 +111,14 @@ -(void)updateAndDrawDemoView // Present [[self openGLContext] flushBuffer]; + // Update and Render additional Platform Windows + ImGuiIO &io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + } + if (!animationTimer) animationTimer = [NSTimer scheduledTimerWithTimeInterval:0.017 target:self selector:@selector(animationTimerFired:) userInfo:nil repeats:YES]; } @@ -163,7 +178,7 @@ -(void)scrollWheel:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, s // ImGuiExampleAppDelegate //----------------------------------------------------------------------------------- -@interface ImGuiExampleAppDelegate : NSObject +@interface ImGuiExampleAppDelegate : NSObject @property (nonatomic, readonly) NSWindow* window; @end @@ -187,6 +202,7 @@ -(NSWindow*)window [_window setAcceptsMouseMovedEvents:YES]; [_window setOpaque:YES]; [_window makeKeyAndOrderFront:NSApp]; + [_window setDelegate:self]; return (_window); } @@ -251,13 +267,14 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + //io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows // Setup Dear ImGui style ImGui::StyleColorsDark(); //ImGui::StyleColorsClassic(); // Setup Platform/Renderer backends - ImGui_ImplOSX_Init(); + ImGui_ImplOSX_Init(view); ImGui_ImplOpenGL2_Init(); // Load Fonts @@ -276,6 +293,13 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification //IM_ASSERT(font != NULL); } +-(void)windowWillClose:(NSNotification *)notification +{ + ImGui_ImplOpenGL2_Shutdown(); + ImGui_ImplOSX_Shutdown(); + ImGui::DestroyContext(); +} + @end int main(int argc, const char* argv[])