From b685b625c302c0cb1844e0155e0a4fbb4b3d967b Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Thu, 23 Dec 2021 07:24:56 +1100 Subject: [PATCH] macOS docking and viewports --- backends/imgui_impl_metal.mm | 213 ++++++++- backends/imgui_impl_osx.h | 3 +- backends/imgui_impl_osx.mm | 413 ++++++++++++++++-- .../project.pbxproj | 25 +- examples/example_apple_metal/main.mm | 179 ++++---- 5 files changed, 677 insertions(+), 156 deletions(-) diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index df0725b4b56bb..acb7cd7046ce1 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -26,17 +26,21 @@ #include "imgui.h" #include "imgui_impl_metal.h" - +#import #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 ...) -#import + +// 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 // A wrapper around a MTLBuffer object that knows the last time it was reused @interface MetalBuffer : NSObject @property (nonatomic, strong) id buffer; -@property (nonatomic, assign) NSTimeInterval lastReuseTime; +@property (nonatomic, assign) double lastReuseTime; - (instancetype)initWithBuffer:(id)buffer; @end @@ -59,7 +63,7 @@ @interface MetalContext : NSObject @property (nonatomic, strong) NSMutableDictionary *renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors @property (nonatomic, strong, nullable) id fontTexture; @property (nonatomic, strong) NSMutableArray *bufferCache; -@property (nonatomic, assign) NSTimeInterval lastBufferCachePurge; +@property (nonatomic, assign) double lastBufferCachePurge; - (void)makeDeviceObjectsWithDevice:(id)device; - (void)makeFontTextureWithDevice:(id)device; - (MetalBuffer *)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device; @@ -79,6 +83,11 @@ - (void)renderDrawData:(ImDrawData *)drawData static MetalContext *g_sharedMetalContext = nil; +static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() +{ + return static_cast(static_cast(clock_gettime_nsec_np(CLOCK_UPTIME_RAW)) / 1e9); +} + #pragma mark - ImGui API implementation bool ImGui_ImplMetal_Init(id device) @@ -86,6 +95,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, ^{ @@ -94,11 +104,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(); } @@ -136,6 +150,7 @@ bool ImGui_ImplMetal_CreateDeviceObjects(id device) { [g_sharedMetalContext makeDeviceObjectsWithDevice:device]; + ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows(); ImGui_ImplMetal_CreateFontsTexture(device); return true; @@ -144,9 +159,159 @@ bool ImGui_ImplMetal_CreateDeviceObjects(id device) void ImGui_ImplMetal_DestroyDeviceObjects() { ImGui_ImplMetal_DestroyFontsTexture(); + ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows(); + [g_sharedMetalContext emptyRenderPipelineStateCache]; } +#pragma mark - Multi-viewport support + +#import + +#if TARGET_OS_OSX +#import +#endif + +//-------------------------------------------------------------------------------------------------------- +// 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 = nullptr; + bool firstFrame = true; +}; + +static void ImGui_ImplMetal_CreateWindow(ImGuiViewport* viewport) +{ + auto 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 != nullptr); + + 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 = window.contentView; + view.layer = layer; + view.wantsLayer = 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 (auto data = (ImGuiViewportDataMetal*)viewport->RendererUserData) + { + IM_DELETE(data); + } + viewport->RendererUserData = nullptr; +} + +inline static CGSize MakeScaledSize(CGSize size, CGFloat scale) { + return CGSizeMake(size.width * scale, size.height * scale); +} + +static void ImGui_ImplMetal_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) +{ + auto data = (ImGuiViewportDataMetal*)viewport->RendererUserData; + data->MetalLayer.drawableSize = MakeScaledSize(CGSizeMake(size.x, size.y), viewport->DpiScale); +} + +static void ImGui_ImplMetal_RenderWindow(ImGuiViewport* viewport, void*) +{ + auto data = (ImGuiViewportDataMetal *)viewport->RendererUserData; + +#if TARGET_OS_OSX + void* handle = viewport->PlatformHandleRaw ? viewport->PlatformHandleRaw : viewport->PlatformHandle; + auto window = (__bridge NSWindow *)handle; + + // Always render the firstFrame, regardless of occlusionState, to avoid an initial flicker + if ((window.occlusionState & NSWindowOcclusionStateVisible) == 0 && !data->firstFrame) + { + // Do not render windows which are completely occluded. + // FIX: Calling -[CAMetalLayer nextDrawable] will hang for approximately 1 second + // if the Metal layer is completely occluded. + return; + } + + data->firstFrame = false; + + viewport->DpiScale = static_cast(window.backingScaleFactor); + if (data->MetalLayer.contentsScale != viewport->DpiScale) + { + data->MetalLayer.contentsScale = viewport->DpiScale; + data->MetalLayer.drawableSize = MakeScaledSize(window.frame.size, viewport->DpiScale); + } + viewport->DrawData->FramebufferScale = ImVec2(viewport->DpiScale, viewport->DpiScale); +#endif + + id drawable = [data->MetalLayer nextDrawable]; + if (drawable == nil) + { + return; + } + + 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 @@ -155,7 +320,7 @@ - (instancetype)initWithBuffer:(id)buffer if ((self = [super init])) { _buffer = buffer; - _lastReuseTime = [NSDate date].timeIntervalSince1970; + _lastReuseTime = GetMachAbsoluteTimeInSeconds(); } return self; } @@ -217,7 +382,7 @@ - (instancetype)init { { _renderPipelineStateCache = [NSMutableDictionary dictionary]; _bufferCache = [NSMutableArray array]; - _lastBufferCachePurge = [NSDate date].timeIntervalSince1970; + _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); } return self; } @@ -245,8 +410,14 @@ - (void)makeFontTextureWithDevice:(id)device height:(NSUInteger)height mipmapped:NO]; textureDescriptor.usage = MTLTextureUsageShaderRead; + // Only override the storageMode for macOS with GPUs supporting unified memory, + // as the default value, per Apple docs, is already set correctly. #if TARGET_OS_OSX || TARGET_OS_MACCATALYST - textureDescriptor.storageMode = MTLStorageModeManaged; + if (@available(macOS 10.15, macCatalyst 13.0, *)) { + if (device.hasUnifiedMemory) { + textureDescriptor.storageMode = MTLStorageModeShared; + } + } #else textureDescriptor.storageMode = MTLStorageModeShared; #endif @@ -257,32 +428,32 @@ - (void)makeFontTextureWithDevice:(id)device - (MetalBuffer *)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device { - NSTimeInterval now = [NSDate date].timeIntervalSince1970; + uint64_t now = GetMachAbsoluteTimeInSeconds(); // Purge old buffers that haven't been useful for a while - if (now - self.lastBufferCachePurge > 1.0) + if (now - _lastBufferCachePurge > 1.0) { NSMutableArray *survivors = [NSMutableArray array]; - for (MetalBuffer *candidate in self.bufferCache) + for (MetalBuffer *candidate in _bufferCache) { - if (candidate.lastReuseTime > self.lastBufferCachePurge) + if (candidate.lastReuseTime > _lastBufferCachePurge) { [survivors addObject:candidate]; } } - self.bufferCache = [survivors mutableCopy]; - self.lastBufferCachePurge = now; + _bufferCache = [survivors mutableCopy]; + _lastBufferCachePurge = now; } // See if we have a buffer we can reuse MetalBuffer *bestCandidate = nil; - for (MetalBuffer *candidate in self.bufferCache) + for (MetalBuffer *candidate in _bufferCache) if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime)) bestCandidate = candidate; if (bestCandidate != nil) { - [self.bufferCache removeObject:bestCandidate]; + [_bufferCache removeObject:bestCandidate]; bestCandidate.lastReuseTime = now; return bestCandidate; } @@ -294,21 +465,21 @@ - (MetalBuffer *)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)renderPipelineStateForFrameAndDevice:(id)device { // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame // The hit rate for this cache should be very near 100%. - id renderPipelineState = self.renderPipelineStateCache[self.framebufferDescriptor]; + id renderPipelineState = _renderPipelineStateCache[_framebufferDescriptor]; if (renderPipelineState == nil) { // No luck; make a new render pipeline state - renderPipelineState = [self _renderPipelineStateForFramebufferDescriptor:self.framebufferDescriptor device:device]; + renderPipelineState = [self _renderPipelineStateForFramebufferDescriptor:_framebufferDescriptor device:device]; // Cache render pipeline state for later reuse - self.renderPipelineStateCache[self.framebufferDescriptor] = renderPipelineState; + _renderPipelineStateCache[_framebufferDescriptor] = renderPipelineState; } return renderPipelineState; @@ -504,7 +675,7 @@ - (void)renderDrawData:(ImDrawData *)drawData else pcmd->UserCallback(cmd_list, pcmd); } - else + else if (pcmd->ElemCount > 0) { // Project scissor/clipping rectangles into framebuffer space ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); diff --git a/backends/imgui_impl_osx.h b/backends/imgui_impl_osx.h index d553d42bfe43f..b334a550a11ba 100644 --- a/backends/imgui_impl_osx.h +++ b/backends/imgui_impl_osx.h @@ -7,8 +7,7 @@ // [X] Platform: OSX clipboard is supported within core Dear ImGui (no specific code in this backend). // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Keyboard arrays indexed using kVK_* codes, e.g. ImGui::IsKeyPressed(kVK_Space). -// Issues: -// [ ] Platform: Multi-viewport / platform windows. +// [X] Platform: Multi-viewport / platform windows. // 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. diff --git a/backends/imgui_impl_osx.mm b/backends/imgui_impl_osx.mm index 0fc156d18b692..5696f0d58f91a 100644 --- a/backends/imgui_impl_osx.mm +++ b/backends/imgui_impl_osx.mm @@ -7,7 +7,6 @@ // [X] Platform: OSX clipboard is supported within core Dear ImGui (no specific code in this backend). // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Keyboard arrays indexed using kVK_* codes, e.g. ImGui::IsKeyPressed(kVK_Space). -// Issues: // [ ] Platform: Multi-viewport / platform windows. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -18,9 +17,9 @@ #import "imgui.h" #import "imgui_impl_osx.h" #import -#import #import #import +#import // CHANGELOG // (minor and older changes stripped away, please see git history for details) @@ -46,14 +45,22 @@ @class KeyEventResponder; // Data -static double g_HostClockPeriod = 0.0; -static double g_Time = 0.0; +static CFTimeInterval 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 = nil; static KeyEventResponder* g_KeyEventResponder = nil; +static NSWindow* g_Window = nil; +static id g_Monitor = nil; +static bool g_WantUpdateMonitors = true; + +// Forward Declarations +static void ImGui_ImplOSX_InitPlatformInterface(); +static void ImGui_ImplOSX_ShutdownPlatformInterface(); +static void ImGui_ImplOSX_UpdateMonitors(); +static void ImGui_ImplOSX_AddTrackingArea(NSView* _Nonnull view); // Undocumented methods for creating cursors. @interface NSCursor() @@ -63,16 +70,11 @@ + (id)_windowResizeNorthSouthCursor; + (id)_windowResizeEastWestCursor; @end -static void InitHostClockPeriod() +static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { - struct mach_timebase_info info; - mach_timebase_info(&info); - g_HostClockPeriod = 1e-9 * ((double)info.denom / (double)info.numer); // Period is the reciprocal of frequency. -} - -static double GetMachAbsoluteTimeInSeconds() -{ - return (double)mach_absolute_time() * g_HostClockPeriod; + // As recommended by the Apple documentation, https://developer.apple.com/documentation/kernel/1462446-mach_absolute_time + // "Prefer to use the equivalent clock_gettime_nsec_np(CLOCK_UPTIME_RAW) in nanoseconds." + return static_cast(static_cast(clock_gettime_nsec_np(CLOCK_UPTIME_RAW)) / 1e9); } static void resetKeys() @@ -214,10 +216,16 @@ bool ImGui_ImplOSX_Init(NSView* view) // 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"; + g_Window = view.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. io.KeyMap[ImGuiKey_Tab] = kVK_Tab; io.KeyMap[ImGuiKey_LeftArrow] = kVK_LeftArrow; @@ -298,12 +306,21 @@ bool ImGui_ImplOSX_Init(NSView* view) g_KeyEventResponder = [[KeyEventResponder alloc] initWithFrame:NSZeroRect]; [view addSubview:g_KeyEventResponder]; + ImGui_ImplOSX_AddTrackingArea(view); + return true; } void ImGui_ImplOSX_Shutdown() { - g_FocusObserver = NULL; + g_FocusObserver = nil; + ImGui_ImplOSX_ShutdownPlatformInterface(); + g_Window = nil; + if (g_Monitor != nil) + { + [NSEvent removeMonitor:g_Monitor]; + g_Monitor = nil; + } } static void ImGui_ImplOSX_UpdateMouseCursorAndButtons() @@ -401,10 +418,14 @@ void ImGui_ImplOSX_NewFrame(NSView* view) io.DisplayFramebufferScale = ImVec2(dpi, dpi); } + if (g_WantUpdateMonitors) + { + ImGui_ImplOSX_UpdateMonitors(); + } + // Setup time step if (g_Time == 0.0) { - InitHostClockPeriod(); g_Time = GetMachAbsoluteTimeInSeconds(); } double current_time = GetMachAbsoluteTimeInSeconds(); @@ -415,22 +436,9 @@ void ImGui_ImplOSX_NewFrame(NSView* view) ImGui_ImplOSX_UpdateGamepads(); } -NSString* NSStringFromPhase(NSEventPhase phase) +static void ConvertNSRect(NSScreen *screen, NSRect *r) { - static NSString* strings[] = - { - @"none", - @"began", - @"stationary", - @"changed", - @"ended", - @"cancelled", - @"mayBegin", - }; - - int pos = phase == NSEventPhaseNone ? 0 : __builtin_ctzl((NSUInteger)phase) + 1; - - return strings[pos]; + r->origin.y = CGDisplayPixelsHigh(kCGDirectMainDisplay) - r->origin.y - r->size.height; } bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view) @@ -455,10 +463,24 @@ 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); + NSPoint mousePoint; + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + mousePoint = NSEvent.mouseLocation; + // normalize y coordinate to top-left of main display. + mousePoint.y = CGDisplayPixelsHigh(kCGDirectMainDisplay) - mousePoint.y; + } + else + { + mousePoint = event.locationInWindow; + // convert to local coordinates of view + mousePoint = [view convertPoint:mousePoint fromView:nil]; + CGSize size = view.bounds.size; + mousePoint.y = size.height - mousePoint.y; + } + io.MousePos = ImVec2((float)mousePoint.x, (float)mousePoint.y); + return io.WantCaptureMouse; } if (event.type == NSEventTypeScrollWheel) @@ -499,8 +521,6 @@ bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view) wheel_dy = [event deltaY]; } - //NSLog(@"dx=%0.3ff, dy=%0.3f, phase=%@", wheel_dx, wheel_dy, NSStringFromPhase(event.phase)); - if (fabs(wheel_dx) > 0.0) io.MouseWheelH += (float)wheel_dx * 0.1f; if (fabs(wheel_dy) > 0.0) @@ -544,3 +564,324 @@ bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view) return false; } + +static void ImGui_ImplOSX_AddTrackingArea(NSView* _Nonnull view) +{ +// // 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, 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_Window: NSWindow +@end + +@implementation ImGui_ImplOSX_Window + +- (BOOL)canBecomeKeyWindow +{ + return YES; +} + +@end + +static void ImGui_ImplOSX_CreateWindow(ImGuiViewport* viewport) +{ + ImGuiViewportDataOSX* data = IM_NEW(ImGuiViewportDataOSX)(); + viewport->PlatformUserData = data; + + NSScreen* screen = g_Window.screen; + NSRect rect = NSMakeRect(viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y); + ConvertNSRect(screen, &rect); + + NSWindowStyleMask styleMask = 0; + if (viewport->Flags & ImGuiViewportFlags_NoDecoration) + { + styleMask |= NSWindowStyleMaskBorderless; + } + else + { + styleMask |= NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; + } + + NSWindow* window = [[ImGui_ImplOSX_Window alloc] initWithContentRect:rect + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:YES + screen:screen]; + if (viewport->Flags & ImGuiViewportFlags_TopMost) + { + [window setLevel:NSFloatingWindowLevel]; + } + + window.title = @"Untitled"; + window.opaque = NO; + window.parentWindow = g_Window; + if (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing) + { + [window orderFront:nil]; + } + else + { + [window makeKeyAndOrderFront:nil]; + } + + [window setIsVisible:YES]; + + KeyEventResponder* view = [[KeyEventResponder 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 + + window.contentView = view; + ImGui_ImplOSX_AddTrackingArea(view); + + 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.contentView = nil; + window.contentViewController = 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; + NSSize size = window.frame.size; + + NSRect r = NSMakeRect(pos.x, pos.y, size.width, size.height); + ConvertNSRect(window.screen, &r); + [window setFrameOrigin:r.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 makeKeyAndOrderFront:g_Window]; +} + +static bool ImGui_ImplOSX_GetWindowFocus(ImGuiViewport* viewport) +{ + ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; + IM_ASSERT(data->Window != 0); + + return data->Window.isKeyWindow; +} + +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.title = [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.alphaValue = 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(static_cast(NSScreen.screens.count)); + + int i = 0; + for (NSScreen *screen in NSScreen.screens) + { + 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[i] = imgui_monitor; + i += 1; + } + + 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 4bb4fc2887918..3ebf9ccf9eccf 100644 --- a/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj +++ b/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj @@ -7,7 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 050450AB2768052600AB6805 /* imgui_tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5079822D257677DB0038A28D /* imgui_tables.cpp */; }; + 050450AD276863B000AB6805 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 050450AC276863B000AB6805 /* QuartzCore.framework */; }; 05318E0F274C397200A8DE2E /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05318E0E274C397200A8DE2E /* GameController.framework */; }; + 05A275442773BEA20084EF39 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05A275432773BEA20084EF39 /* QuartzCore.framework */; }; 07A82ED82139413D0078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82ED72139413C0078D120 /* imgui_widgets.cpp */; }; 07A82ED92139418F0078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82ED72139413C0078D120 /* imgui_widgets.cpp */; }; 5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5079822D257677DB0038A28D /* imgui_tables.cpp */; }; @@ -33,7 +36,11 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 050450AC276863B000AB6805 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 05318E0E274C397200A8DE2E /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; }; + 05A2754027728F5B0084EF39 /* imgui_impl_metal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_impl_metal.h; path = ../../backends/imgui_impl_metal.h; sourceTree = ""; }; + 05A2754127728F5B0084EF39 /* imgui_impl_osx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_impl_osx.h; path = ../../backends/imgui_impl_osx.h; sourceTree = ""; }; + 05A275432773BEA20084EF39 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.2.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; 07A82ED62139413C0078D120 /* imgui_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_internal.h; path = ../../imgui_internal.h; sourceTree = ""; }; 07A82ED72139413C0078D120 /* imgui_widgets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_widgets.cpp; path = ../../imgui_widgets.cpp; sourceTree = ""; }; 5079822D257677DB0038A28D /* imgui_tables.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_tables.cpp; path = ../../imgui_tables.cpp; sourceTree = ""; }; @@ -66,6 +73,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 05A275442773BEA20084EF39 /* QuartzCore.framework in Frameworks */, 8309BD8F253CCAAA0045E2A1 /* UIKit.framework in Frameworks */, 83BBE9E720EB46BD00295997 /* MetalKit.framework in Frameworks */, 83BBE9E520EB46B900295997 /* Metal.framework in Frameworks */, @@ -76,6 +84,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 050450AD276863B000AB6805 /* QuartzCore.framework in Frameworks */, 8309BDC6253CCCFE0045E2A1 /* AppKit.framework in Frameworks */, 83BBE9EC20EB471700295997 /* MetalKit.framework in Frameworks */, 05318E0F274C397200A8DE2E /* GameController.framework in Frameworks */, @@ -136,6 +145,8 @@ 83BBE9E320EB46B800295997 /* Frameworks */ = { isa = PBXGroup; children = ( + 050450AC276863B000AB6805 /* QuartzCore.framework */, + 05A275432773BEA20084EF39 /* QuartzCore.framework */, 05318E0E274C397200A8DE2E /* GameController.framework */, 8309BDC5253CCCFE0045E2A1 /* AppKit.framework */, 8309BD8E253CCAAA0045E2A1 /* UIKit.framework */, @@ -153,7 +164,9 @@ isa = PBXGroup; children = ( 5079822D257677DB0038A28D /* imgui_tables.cpp */, + 05A2754027728F5B0084EF39 /* imgui_impl_metal.h */, 8309BDB5253CCC9D0045E2A1 /* imgui_impl_metal.mm */, + 05A2754127728F5B0084EF39 /* imgui_impl_osx.h */, 8309BDB6253CCC9D0045E2A1 /* imgui_impl_osx.mm */, 83BBEA0420EB54E700295997 /* imconfig.h */, 83BBEA0320EB54E700295997 /* imgui.cpp */, @@ -268,9 +281,9 @@ 8309BDBB253CCCAD0045E2A1 /* imgui_impl_metal.mm in Sources */, 83BBEA0920EB54E700295997 /* imgui.cpp in Sources */, 83BBEA0720EB54E700295997 /* imgui_demo.cpp in Sources */, - 83BBEA0520EB54E700295997 /* imgui_draw.cpp in Sources */, + 83BBEA0520EB54E700295997 /* imgui_draw.cpp in Sources */, 5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */, - 07A82ED82139413D0078D120 /* imgui_widgets.cpp in Sources */, + 07A82ED82139413D0078D120 /* imgui_widgets.cpp in Sources */, 8309BDA5253CCC070045E2A1 /* main.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -281,10 +294,10 @@ files = ( 8309BDBE253CCCB60045E2A1 /* imgui_impl_metal.mm in Sources */, 8309BDBF253CCCB60045E2A1 /* imgui_impl_osx.mm in Sources */, - 83BBEA0A20EB54E700295997 /* imgui.cpp in Sources */, - 83BBEA0820EB54E700295997 /* imgui_demo.cpp in Sources */, - 83BBEA0620EB54E700295997 /* imgui_draw.cpp in Sources */, - 5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */, + 83BBEA0A20EB54E700295997 /* imgui.cpp in Sources */, + 83BBEA0820EB54E700295997 /* imgui_demo.cpp in Sources */, + 83BBEA0620EB54E700295997 /* imgui_draw.cpp in Sources */, + 050450AB2768052600AB6805 /* imgui_tables.cpp in Sources */, 07A82ED92139418F0078D120 /* imgui_widgets.cpp in Sources */, 8309BDA8253CCC080045E2A1 /* main.mm in Sources */, ); diff --git a/examples/example_apple_metal/main.mm b/examples/example_apple_metal/main.mm index bbe51d31f294b..673488a5594d0 100644 --- a/examples/example_apple_metal/main.mm +++ b/examples/example_apple_metal/main.mm @@ -17,7 +17,7 @@ #include "imgui_impl_metal.h" #if TARGET_OS_OSX #include "imgui_impl_osx.h" -@interface AppViewController : NSViewController +@interface AppViewController : NSViewController @end #else @interface AppViewController : UIViewController @@ -56,11 +56,21 @@ -(instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullabl 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); @@ -100,27 +110,11 @@ -(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; - [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^NSEvent * _Nullable(NSEvent *event) - { - ImGui_ImplOSX_HandleEvent(event, self.view); - return event; - }]; - ImGui_ImplOSX_Init(self.view); + [NSApp activateIgnoringOtherApps:YES]; + + #endif } @@ -137,80 +131,87 @@ -(void)drawInMTKView:(MTKView*)view #endif io.DisplayFramebufferScale = ImVec2(framebufferScale, framebufferScale); - io.DeltaTime = 1 / float(view.preferredFramesPerSecond ?: 60); - id commandBuffer = [self.commandQueue commandBuffer]; + // Our state (make them static = more or less global) as a convenience to keep the example terse. + static bool show_demo_window = true; + static bool show_another_window = false; + static float clear_color[4] = { 0.28f, 0.36f, 0.5f, 1.0f }; + MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor; - if (renderPassDescriptor == nil) + if (renderPassDescriptor != nil) { - [commandBuffer commit]; - return; - } + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0] * clear_color[3], clear_color[1] * clear_color[3], clear_color[2] * clear_color[3], clear_color[3]); + + // Here, you could do additional rendering work, including other passes as necessary. - // Start the Dear ImGui frame - ImGui_ImplMetal_NewFrame(renderPassDescriptor); + id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + [renderEncoder pushDebugGroup:@"ImGui demo"]; + + // Start the Dear ImGui frame + ImGui_ImplMetal_NewFrame(renderPassDescriptor); #if TARGET_OS_OSX - ImGui_ImplOSX_NewFrame(view); + ImGui_ImplOSX_NewFrame(view); #endif - ImGui::NewFrame(); + ImGui::NewFrame(); - // Our state (make them static = more or less global) as a convenience to keep the example terse. - static bool show_demo_window = true; - static bool show_another_window = false; - static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); - // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). - if (show_demo_window) - ImGui::ShowDemoWindow(&show_demo_window); + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window. + { + static float f = 0.0f; + static int counter = 0; - // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window. - { - static float f = 0.0f; - static int counter = 0; + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. - ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); - ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) - ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state - ImGui::Checkbox("Another Window", &show_another_window); + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color - ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f - ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); - if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) - counter++; - ImGui::SameLine(); - ImGui::Text("counter = %d", counter); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + ImGui::End(); + } - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); - ImGui::End(); - } + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } - // 3. Show another simple window. - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) - ImGui::Text("Hello from another window!"); - if (ImGui::Button("Close Me")) - show_another_window = false; - ImGui::End(); - } + // Rendering + ImGui::Render(); + ImDrawData* draw_data = ImGui::GetDrawData(); + draw_data->FramebufferScale = ImVec2(framebufferScale, framebufferScale); + ImGui_ImplMetal_RenderDrawData(draw_data, commandBuffer, renderEncoder); - // Rendering - ImGui::Render(); - ImDrawData* draw_data = ImGui::GetDrawData(); + [renderEncoder popDebugGroup]; + [renderEncoder endEncoding]; - renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); - id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; - [renderEncoder pushDebugGroup:@"Dear ImGui rendering"]; - ImGui_ImplMetal_RenderDrawData(draw_data, commandBuffer, renderEncoder); - [renderEncoder popDebugGroup]; - [renderEncoder endEncoding]; + [commandBuffer presentDrawable:view.currentDrawable]; + } - // Present - [commandBuffer presentDrawable:view.currentDrawable]; [commandBuffer commit]; + + // Update and Render additional Platform Windows + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + } } -(void)mtkView:(MTKView*)view drawableSizeWillChange:(CGSize)size @@ -223,21 +224,18 @@ -(void)mtkView:(MTKView*)view drawableSizeWillChange:(CGSize)size #if TARGET_OS_OSX -// Forward Mouse/Keyboard events to Dear ImGui OSX backend. -// Other events are registered via addLocalMonitorForEventsMatchingMask() --(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)mouseMoved:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } --(void)mouseDragged:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } --(void)rightMouseMoved:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } --(void)rightMouseDragged:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } --(void)otherMouseMoved:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } --(void)otherMouseDragged:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } --(void)scrollWheel:(NSEvent *)event { ImGui_ImplOSX_HandleEvent(event, self.view); } +- (void)viewWillAppear +{ + [super viewWillAppear]; + self.view.window.delegate = self; +} + +- (void)windowWillClose:(NSNotification *)notification +{ + ImGui_ImplMetal_Shutdown(); + ImGui_ImplOSX_Shutdown(); + ImGui::DestroyContext(); +} #else @@ -301,9 +299,8 @@ -(instancetype)init backing:NSBackingStoreBuffered defer:NO]; self.window.contentViewController = rootViewController; - [self.window orderFront:self]; [self.window center]; - [self.window becomeKeyWindow]; + [self.window makeKeyAndOrderFront:self]; } return self; }