diff --git a/Imgui/CMakeLists.txt b/Imgui/CMakeLists.txt index e91fa671..1a2ba556 100644 --- a/Imgui/CMakeLists.txt +++ b/Imgui/CMakeLists.txt @@ -5,6 +5,7 @@ project(Diligent-Imgui CXX) set(SOURCE src/ImGuiDiligentRenderer.cpp src/ImGuiImplDiligent.cpp + src/ImGuiDiligentFileIO.cpp src/ImGuiUtils.cpp ) @@ -126,7 +127,7 @@ if(PLATFORM_UNIVERSAL_WINDOWS) target_compile_definitions(Diligent-Imgui PRIVATE IMGUI_DISABLE_WIN32_FUNCTIONS) endif() -target_compile_definitions(Diligent-Imgui PUBLIC IMGUI_DEFINE_MATH_OPERATORS) +target_compile_definitions(Diligent-Imgui PUBLIC IMGUI_USER_CONFIG="ImGuiDiligentConfig.h") if(PLATFORM_WIN32 AND MINGW_BUILD) # Link with dwmapi.lib as imgui_impl_win32.cpp skips diff --git a/Imgui/interface/ImGuiDiligentConfig.h b/Imgui/interface/ImGuiDiligentConfig.h new file mode 100644 index 00000000..1c40de11 --- /dev/null +++ b/Imgui/interface/ImGuiDiligentConfig.h @@ -0,0 +1,58 @@ +/* + * Copyright 2026 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#pragma once + +#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS +#define IMGUI_DEFINE_MATH_OPERATORS + +namespace Diligent +{ + +#if PLATFORM_WIN32 +class WindowsFile; +using ImGuiFile = WindowsFile; +#elif PLATFORM_UNIVERSAL_WINDOWS +class WindowsStoreFile; +using ImGuiFile = WindowsStoreFile; +#elif PLATFORM_ANDROID +class AndroidFile; +using ImGuiFile = AndroidFile; +#else +class StandardFile; +using ImGuiFile = StandardFile; +#endif + +} // namespace Diligent + +using ImFileHandle = Diligent::ImGuiFile*; +using ImU64 = unsigned long long; + +ImFileHandle ImFileOpen(const char* pFileName, const char* pMode); +bool ImFileClose(ImFileHandle pFile); +ImU64 ImFileGetSize(ImFileHandle pFile); +ImU64 ImFileRead(void* pData, ImU64 Size, ImU64 Count, ImFileHandle pFile); +ImU64 ImFileWrite(const void* pData, ImU64 Size, ImU64 Count, ImFileHandle pFile); diff --git a/Imgui/src/ImGuiDiligentFileIO.cpp b/Imgui/src/ImGuiDiligentFileIO.cpp new file mode 100644 index 00000000..be67022b --- /dev/null +++ b/Imgui/src/ImGuiDiligentFileIO.cpp @@ -0,0 +1,85 @@ +/* + * Copyright 2026 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#include + +#include "ImGuiDiligentConfig.h" +#include "imgui.h" +#include "imgui_internal.h" +#include "FileSystem.hpp" + +using namespace Diligent; + +ImFileHandle ImFileOpen(const char* pFileName, const char* pMode) +{ + if (!pFileName || !pMode || !pMode[0]) + return nullptr; + + const bool HasPlus = std::strchr(pMode, '+') != nullptr; + + EFileAccessMode AccessMode = EFileAccessMode::Read; + + switch (pMode[0]) + { + case 'w': AccessMode = HasPlus ? EFileAccessMode::OverwriteUpdate : EFileAccessMode::Overwrite; break; + case 'a': AccessMode = HasPlus ? EFileAccessMode::AppendUpdate : EFileAccessMode::Append; break; + case 'r': AccessMode = HasPlus ? EFileAccessMode::ReadUpdate : EFileAccessMode::Read; break; + default: AccessMode = HasPlus ? EFileAccessMode::ReadUpdate : EFileAccessMode::Read; break; + } + + return static_cast(FileSystem::OpenFile({pFileName, AccessMode})); +} + +bool ImFileClose(ImFileHandle pFile) +{ + if (!pFile) + return false; + delete pFile; + return true; +} + +ImU64 ImFileGetSize(ImFileHandle pFile) +{ + if (!pFile) + return static_cast(-1); + return static_cast(pFile->GetSize()); +} + +ImU64 ImFileRead(void* pData, ImU64 ElemSize, ImU64 ElemCount, ImFileHandle pFile) +{ + if (!pFile || ElemSize == 0 || ElemCount == 0) + return 0; + const size_t TotalSize = static_cast(ElemSize * ElemCount); + return pFile->Read(pData, TotalSize) ? ElemCount : 0; +} + +ImU64 ImFileWrite(const void* pData, ImU64 ElemSize, ImU64 ElemCount, ImFileHandle pFile) +{ + if (!pFile || ElemSize == 0 || ElemCount == 0) + return 0; + const size_t TotalSize = static_cast(ElemSize * ElemCount); + return pFile->Write(pData, TotalSize) ? ElemCount : 0; +} 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...