From 9e5954cf0ab324049d2d2b39e41bd5c2411a0fc5 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 15 Apr 2026 08:24:06 +0800 Subject: [PATCH] refactor(new_editor/app): reorganize host structure and add smoke test --- new_editor/CMakeLists.txt | 82 +- new_editor/app/Application.cpp | 3026 ----------------- new_editor/app/Application.h | 255 -- new_editor/app/Bootstrap/Application.h | 70 + .../app/Bootstrap/ApplicationBootstrap.cpp | 139 + .../Bootstrap/ApplicationBootstrapSupport.h | 71 + .../app/Bootstrap/ApplicationLifecycle.cpp | 58 + .../app/Bootstrap/ApplicationRunLoop.cpp | 67 + .../app/Bootstrap/ApplicationWindowClass.cpp | 114 + new_editor/app/{ => Bootstrap}/EditorApp.rc | 2 +- .../app/{ => Bootstrap}/EditorResources.h | 0 new_editor/app/{ => Bootstrap}/main.cpp | 2 +- .../Composition/EditorHostCommandBridge.cpp | 33 +- .../app/Composition/EditorHostCommandBridge.h | 9 +- .../Composition/EditorShellAssetBuilder.cpp | 494 +-- .../app/Composition/EditorShellAssetBuilder.h | 11 +- .../EditorShellAssetBuilderSupport.h | 23 + .../Composition/EditorShellAssetCommands.cpp | 151 + .../EditorShellAssetDefinition.cpp | 75 + .../Composition/EditorShellAssetLayout.cpp | 76 + .../app/Composition/EditorShellAssetMenu.cpp | 167 + .../app/Composition/EditorShellRuntime.cpp | 115 + .../EditorShellRuntime.h} | 50 +- .../EditorShellRuntimeRendering.cpp | 81 + .../Composition/EditorShellRuntimeSupport.h | 33 + .../Composition/EditorShellRuntimeUpdate.cpp | 157 + .../EditorShellRuntimeViewport.cpp | 82 + .../app/Composition/WorkspaceEventSync.cpp | 180 + .../app/Composition/WorkspaceEventSync.h | 21 + new_editor/app/Core/EditorSession.cpp | 79 - new_editor/app/Core/EditorSession.h | 67 - .../app/Features/Console/ConsolePanel.cpp | 19 +- .../app/Features/Console/ConsolePanel.h | 11 +- .../app/Features/Hierarchy/HierarchyModel.cpp | 119 +- .../app/Features/Hierarchy/HierarchyModel.h | 19 +- .../app/Features/Hierarchy/HierarchyPanel.cpp | 501 +-- .../app/Features/Hierarchy/HierarchyPanel.h | 17 +- .../Hierarchy/HierarchyPanelInteraction.cpp | 293 ++ .../Hierarchy/HierarchyPanelRendering.cpp | 60 + .../Hierarchy/HierarchyPanelSupport.h | 133 + .../app/Features/Inspector/InspectorPanel.cpp | 27 +- .../app/Features/Inspector/InspectorPanel.h | 11 +- .../Features/Project/ProjectBrowserModel.cpp | 273 +- .../Features/Project/ProjectBrowserModel.h | 8 +- .../Project/ProjectBrowserModelAssets.cpp | 56 + .../Project/ProjectBrowserModelFolders.cpp | 68 + .../Project/ProjectBrowserModelSupport.h | 101 + .../app/Features/Project/ProjectPanel.cpp | 738 +--- .../app/Features/Project/ProjectPanel.h | 17 +- .../Project/ProjectPanelInteraction.cpp | 229 ++ .../Features/Project/ProjectPanelLayout.cpp | 162 + .../Project/ProjectPanelRendering.cpp | 112 + .../Features/Project/ProjectPanelSupport.h | 219 ++ .../app/Features/Shared/BuiltInIcons.cpp | 147 - .../app/Features/Shared/EditorTreeViewStyle.h | 44 - .../app/Host/BorderlessWindowChrome.cpp | 471 --- .../app/Host/D3D12WindowInteropContext.cpp | 444 --- .../Host/D3D12WindowSwapChainPresenter.cpp | 388 --- new_editor/app/Host/NativeRenderer.cpp | 1241 ------- .../app/Host/WindowMessageDispatcher.cpp | 155 - .../Platform/Win32/BorderlessWindowChrome.cpp | 72 + .../Win32}/BorderlessWindowChrome.h | 4 +- .../Win32/BorderlessWindowChromeDwm.cpp | 195 ++ .../Win32/BorderlessWindowChromeRendering.cpp | 193 ++ .../Win32}/BorderlessWindowFrame.cpp | 0 .../Win32}/BorderlessWindowFrame.h | 0 new_editor/app/Platform/Win32/EditorWindow.h | 191 ++ .../Win32/EditorWindowBorderlessPlacement.cpp | 140 + .../Win32/EditorWindowBorderlessResize.cpp | 161 + .../Platform/Win32/EditorWindowConstants.h | 10 + .../app/Platform/Win32/EditorWindowFrame.cpp | 235 ++ .../Win32/EditorWindowInitialization.cpp | 128 + .../app/Platform/Win32/EditorWindowInput.cpp | 163 + .../Platform/Win32/EditorWindowInputSupport.h | 109 + .../Platform/Win32/EditorWindowLifecycle.cpp | 173 + .../app/Platform/Win32/EditorWindowManager.h | 135 + .../EditorWindowManagerCrossWindowDrop.cpp | 241 ++ .../Win32/EditorWindowManagerDetach.cpp | 68 + .../Win32/EditorWindowManagerLifecycle.cpp | 245 ++ .../Win32/EditorWindowManagerTabDrag.cpp | 145 + .../Win32/EditorWindowManagerWindowSet.cpp | 171 + .../Platform/Win32/EditorWindowMetrics.cpp | 95 + .../Win32/EditorWindowPlatformSupport.h | 42 + .../Win32/EditorWindowResizeLifecycle.cpp | 73 + .../Win32/EditorWindowRuntimeSupport.h | 36 + .../app/Platform/Win32/EditorWindowState.h | 68 + .../app/Platform/Win32/EditorWindowStyle.h | 12 + .../Platform/Win32/EditorWindowTitleBar.cpp | 8 + .../Win32/EditorWindowTitleBarInteraction.cpp | 261 ++ .../Win32/EditorWindowTitleBarRendering.cpp | 96 + .../Win32}/HostRuntimeState.h | 0 .../Win32}/InputModifierTracker.h | 0 .../Win32/WindowMessageDispatchHandlers.h | 55 + .../Win32/WindowMessageDispatcher.cpp | 26 + .../Win32}/WindowMessageDispatcher.h | 15 +- .../Win32/WindowMessageDispatcherChrome.cpp | 75 + .../Win32/WindowMessageDispatcherInput.cpp | 73 + .../WindowMessageDispatcherLifecycle.cpp | 77 + .../Win32/WindowMessageDispatcherPointer.cpp | 160 + .../app/Platform/Win32/WindowMessageHost.h | 32 + .../app/Rendering/Assets/BuiltInIcons.cpp | 97 + .../Assets}/BuiltInIcons.h | 9 +- .../app/Rendering/D3D12/D3D12HostDevice.cpp | 8 + .../D3D12}/D3D12HostDevice.h | 0 .../Rendering/D3D12/D3D12HostDeviceFence.cpp | 42 + .../Rendering/D3D12/D3D12HostDeviceFrame.cpp | 113 + .../D3D12/D3D12HostDeviceLifecycle.cpp} | 142 - ...D3D12ShaderResourceDescriptorAllocator.cpp | 0 .../D3D12ShaderResourceDescriptorAllocator.h | 0 .../D3D12/D3D12WindowInteropContext.cpp | 100 + .../D3D12}/D3D12WindowInteropContext.h | 0 .../D3D12/D3D12WindowInteropDevice.cpp | 138 + .../D3D12/D3D12WindowInteropResources.cpp | 128 + .../D3D12WindowInteropSourceTextures.cpp | 100 + .../D3D12/D3D12WindowInteropSupport.h | 19 + .../D3D12}/D3D12WindowRenderLoop.cpp | 0 .../D3D12}/D3D12WindowRenderLoop.h | 4 +- .../D3D12}/D3D12WindowRenderer.cpp | 0 .../D3D12}/D3D12WindowRenderer.h | 0 .../D3D12/D3D12WindowSwapChainPresenter.cpp | 8 + .../D3D12}/D3D12WindowSwapChainPresenter.h | 0 ...D12WindowSwapChainPresenterBackBuffers.cpp | 92 + ...D3D12WindowSwapChainPresenterLifecycle.cpp | 170 + ...12WindowSwapChainPresenterPresentation.cpp | 50 + .../D3D12WindowSwapChainPresenterResize.cpp | 100 + .../Native}/AutoScreenshot.cpp | 49 +- .../Native}/AutoScreenshot.h | 0 .../app/Rendering/Native/NativeRenderer.cpp | 9 + .../Native}/NativeRenderer.h | 4 +- .../Native/NativeRendererCapture.cpp | 154 + .../Rendering/Native/NativeRendererDraw.cpp | 260 ++ .../Native/NativeRendererLifecycle.cpp | 255 ++ .../Native/NativeRendererRendering.cpp | 150 + .../Rendering/Native/NativeRendererSupport.h | 88 + .../Rendering/Native/NativeRendererText.cpp | 122 + .../Native/NativeRendererTextures.cpp | 259 ++ .../Viewport/ViewportHostService.cpp | 9 + .../Viewport/ViewportHostService.h | 25 +- .../Viewport/ViewportHostServiceFrame.cpp} | 94 +- .../Viewport/ViewportHostServiceLifecycle.cpp | 71 + .../Viewport/ViewportRenderTargetManager.cpp | 8 + .../ViewportRenderTargetManagerResources.cpp} | 102 +- .../ViewportRenderTargetManagerSurfaces.cpp | 63 + .../Viewport/ViewportRenderTargetSupport.h} | 25 +- .../Viewport/ViewportRenderTargets.cpp | 2 + .../Viewport/ViewportRenderTargets.h | 33 +- .../{ => Rendering}/Viewport/ViewportTypes.h | 5 +- .../app/{Core => State}/EditorContext.cpp | 51 +- .../app/{Core => State}/EditorContext.h | 21 +- new_editor/app/State/EditorSession.cpp | 80 + new_editor/app/State/EditorSession.h | 68 + new_editor/app/Support/EmbeddedPngLoader.h | 73 + new_editor/app/Support/EnvironmentFlags.h | 54 + new_editor/app/Support/ExecutablePath.h | 31 + new_editor/app/Support/StringEncoding.h | 46 + new_editor/app/Support/TextFormat.h | 19 + .../app/UI/Styles/EditorTreeViewStyle.h | 45 + .../app/Viewport/ViewportRenderTargets.cpp | 1 - new_editor/app/Workspace/EditorWorkspace.cpp | 411 --- .../Workspace/EditorWorkspaceEventRouter.cpp | 178 - .../Workspace/EditorWorkspaceEventRouter.h | 20 - .../XCEditor/Collections/UIEditorListView.h | 14 +- .../XCEditor/Collections/UIEditorScrollView.h | 14 +- .../XCEditor/Collections/UIEditorTabStrip.h | 40 +- .../XCEditor/Collections/UIEditorTreeView.h | 16 +- .../{Shell => Docking}/UIEditorDockHost.h | 28 +- .../UIEditorDockHostInteraction.h | 4 +- .../XCEditor/Fields/UIEditorAssetField.h | 46 +- .../XCEditor/Fields/UIEditorBoolField.h | 16 +- .../XCEditor/Fields/UIEditorColorField.h | 42 +- .../XCEditor/Fields/UIEditorEnumField.h | 18 +- .../Fields/UIEditorEnumFieldInteraction.h | 2 +- .../XCEditor/Fields/UIEditorNumberField.h | 20 +- .../XCEditor/Fields/UIEditorObjectField.h | 28 +- .../XCEditor/Fields/UIEditorPropertyGrid.h | 58 +- .../XCEditor/Fields/UIEditorTextField.h | 20 +- .../XCEditor/Fields/UIEditorVector2Field.h | 28 +- .../XCEditor/Fields/UIEditorVector3Field.h | 30 +- .../XCEditor/Fields/UIEditorVector4Field.h | 32 +- .../Foundation/UIEditorCommandRegistry.h | 2 +- .../XCEditor/Foundation/UIEditorTheme.h | 10 +- .../{Shell => Menu}/UIEditorMenuBar.h | 12 +- .../{Shell => Menu}/UIEditorMenuModel.h | 0 .../{Shell => Menu}/UIEditorMenuPopup.h | 28 +- .../{Shell => Menu}/UIEditorMenuSession.h | 0 .../UIEditorPanelContentHost.h | 4 +- .../{Shell => Panels}/UIEditorPanelFrame.h | 30 +- .../UIEditorPanelHostLifecycle.h | 4 +- .../{Shell => Panels}/UIEditorPanelRegistry.h | 0 .../XCEditor/Shell/UIEditorShellAsset.h | 8 +- .../XCEditor/Shell/UIEditorShellCompose.h | 20 +- .../XCEditor/Shell/UIEditorShellInteraction.h | 10 +- .../XCEditor/Shell/UIEditorStatusBar.h | 20 +- .../XCEditor/Shell/UIEditorStructuredShell.h | 2 +- .../UIEditorViewportInputBridge.h | 0 .../UIEditorViewportShell.h | 4 +- .../UIEditorViewportSlot.h | 40 +- .../XCEditor/Widgets/UIEditorFieldRowLayout.h | 62 +- .../UIEditorWindowWorkspaceController.h | 2 +- .../UIEditorWindowWorkspaceModel.h | 4 +- .../UIEditorWorkspaceCompose.h | 10 +- .../UIEditorWorkspaceController.h | 4 +- .../UIEditorWorkspaceInteraction.h | 4 +- .../UIEditorWorkspaceLayoutPersistence.h | 2 +- .../UIEditorWorkspaceModel.h | 0 .../UIEditorWorkspaceSession.h | 4 +- .../UIEditorWorkspaceTransfer.h | 2 +- .../{Shell => Docking}/UIEditorDockHost.cpp | 10 +- .../UIEditorDockHostInteraction.cpp | 2 +- new_editor/src/Fields/UIEditorFieldStyle.cpp | 4 +- .../src/Fields/UIEditorPropertyGrid.cpp | 2 +- .../UIEditorPropertyGridInteraction.cpp | 2 +- .../src/Foundation/UIEditorRuntimeTrace.cpp | 1 + new_editor/src/Foundation/UIEditorTheme.cpp | 281 +- .../src/{Shell => Menu}/UIEditorMenuBar.cpp | 2 +- .../src/{Shell => Menu}/UIEditorMenuModel.cpp | 2 +- .../src/{Shell => Menu}/UIEditorMenuPopup.cpp | 2 +- .../{Shell => Menu}/UIEditorMenuSession.cpp | 2 +- .../UIEditorPanelContentHost.cpp | 2 +- .../{Shell => Panels}/UIEditorPanelFrame.cpp | 2 +- .../UIEditorPanelHostLifecycle.cpp | 2 +- .../UIEditorPanelRegistry.cpp | 2 +- .../UIEditorViewportInputBridge.cpp | 2 +- .../UIEditorViewportShell.cpp | 2 +- .../UIEditorViewportSlot.cpp | 2 +- .../src/Widgets/UIEditorFieldRowLayout.cpp | 32 +- .../UIEditorWindowWorkspaceController.cpp | 4 +- .../UIEditorWindowWorkspaceModel.cpp | 2 +- .../UIEditorWorkspaceCompose.cpp | 2 +- .../UIEditorWorkspaceController.cpp | 2 +- .../UIEditorWorkspaceInteraction.cpp | 2 +- .../UIEditorWorkspaceLayoutPersistence.cpp | 2 +- .../UIEditorWorkspaceModel.cpp | 6 +- .../UIEditorWorkspaceSession.cpp | 2 +- .../UIEditorWorkspaceTransfer.cpp | 2 +- 235 files changed, 11157 insertions(+), 10028 deletions(-) delete mode 100644 new_editor/app/Application.cpp delete mode 100644 new_editor/app/Application.h create mode 100644 new_editor/app/Bootstrap/Application.h create mode 100644 new_editor/app/Bootstrap/ApplicationBootstrap.cpp create mode 100644 new_editor/app/Bootstrap/ApplicationBootstrapSupport.h create mode 100644 new_editor/app/Bootstrap/ApplicationLifecycle.cpp create mode 100644 new_editor/app/Bootstrap/ApplicationRunLoop.cpp create mode 100644 new_editor/app/Bootstrap/ApplicationWindowClass.cpp rename new_editor/app/{ => Bootstrap}/EditorApp.rc (91%) rename new_editor/app/{ => Bootstrap}/EditorResources.h (100%) rename new_editor/app/{ => Bootstrap}/main.cpp (81%) create mode 100644 new_editor/app/Composition/EditorShellAssetBuilderSupport.h create mode 100644 new_editor/app/Composition/EditorShellAssetCommands.cpp create mode 100644 new_editor/app/Composition/EditorShellAssetDefinition.cpp create mode 100644 new_editor/app/Composition/EditorShellAssetLayout.cpp create mode 100644 new_editor/app/Composition/EditorShellAssetMenu.cpp create mode 100644 new_editor/app/Composition/EditorShellRuntime.cpp rename new_editor/app/{Workspace/EditorWorkspace.h => Composition/EditorShellRuntime.h} (56%) create mode 100644 new_editor/app/Composition/EditorShellRuntimeRendering.cpp create mode 100644 new_editor/app/Composition/EditorShellRuntimeSupport.h create mode 100644 new_editor/app/Composition/EditorShellRuntimeUpdate.cpp create mode 100644 new_editor/app/Composition/EditorShellRuntimeViewport.cpp create mode 100644 new_editor/app/Composition/WorkspaceEventSync.cpp create mode 100644 new_editor/app/Composition/WorkspaceEventSync.h delete mode 100644 new_editor/app/Core/EditorSession.cpp delete mode 100644 new_editor/app/Core/EditorSession.h create mode 100644 new_editor/app/Features/Hierarchy/HierarchyPanelInteraction.cpp create mode 100644 new_editor/app/Features/Hierarchy/HierarchyPanelRendering.cpp create mode 100644 new_editor/app/Features/Hierarchy/HierarchyPanelSupport.h create mode 100644 new_editor/app/Features/Project/ProjectBrowserModelAssets.cpp create mode 100644 new_editor/app/Features/Project/ProjectBrowserModelFolders.cpp create mode 100644 new_editor/app/Features/Project/ProjectBrowserModelSupport.h create mode 100644 new_editor/app/Features/Project/ProjectPanelInteraction.cpp create mode 100644 new_editor/app/Features/Project/ProjectPanelLayout.cpp create mode 100644 new_editor/app/Features/Project/ProjectPanelRendering.cpp create mode 100644 new_editor/app/Features/Project/ProjectPanelSupport.h delete mode 100644 new_editor/app/Features/Shared/BuiltInIcons.cpp delete mode 100644 new_editor/app/Features/Shared/EditorTreeViewStyle.h delete mode 100644 new_editor/app/Host/BorderlessWindowChrome.cpp delete mode 100644 new_editor/app/Host/D3D12WindowInteropContext.cpp delete mode 100644 new_editor/app/Host/D3D12WindowSwapChainPresenter.cpp delete mode 100644 new_editor/app/Host/NativeRenderer.cpp delete mode 100644 new_editor/app/Host/WindowMessageDispatcher.cpp create mode 100644 new_editor/app/Platform/Win32/BorderlessWindowChrome.cpp rename new_editor/app/{Host => Platform/Win32}/BorderlessWindowChrome.h (95%) create mode 100644 new_editor/app/Platform/Win32/BorderlessWindowChromeDwm.cpp create mode 100644 new_editor/app/Platform/Win32/BorderlessWindowChromeRendering.cpp rename new_editor/app/{Host => Platform/Win32}/BorderlessWindowFrame.cpp (100%) rename new_editor/app/{Host => Platform/Win32}/BorderlessWindowFrame.h (100%) create mode 100644 new_editor/app/Platform/Win32/EditorWindow.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowConstants.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowFrame.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowInitialization.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowInput.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowInputSupport.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowManager.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowManagerCrossWindowDrop.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowManagerDetach.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowManagerLifecycle.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowManagerTabDrag.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowManagerWindowSet.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowMetrics.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowPlatformSupport.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowResizeLifecycle.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowRuntimeSupport.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowState.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowStyle.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowTitleBar.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp rename new_editor/app/{Host => Platform/Win32}/HostRuntimeState.h (100%) rename new_editor/app/{Host => Platform/Win32}/InputModifierTracker.h (100%) create mode 100644 new_editor/app/Platform/Win32/WindowMessageDispatchHandlers.h create mode 100644 new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp rename new_editor/app/{Host => Platform/Win32}/WindowMessageDispatcher.h (60%) create mode 100644 new_editor/app/Platform/Win32/WindowMessageDispatcherChrome.cpp create mode 100644 new_editor/app/Platform/Win32/WindowMessageDispatcherInput.cpp create mode 100644 new_editor/app/Platform/Win32/WindowMessageDispatcherLifecycle.cpp create mode 100644 new_editor/app/Platform/Win32/WindowMessageDispatcherPointer.cpp create mode 100644 new_editor/app/Platform/Win32/WindowMessageHost.h create mode 100644 new_editor/app/Rendering/Assets/BuiltInIcons.cpp rename new_editor/app/{Features/Shared => Rendering/Assets}/BuiltInIcons.h (75%) create mode 100644 new_editor/app/Rendering/D3D12/D3D12HostDevice.cpp rename new_editor/app/{Host => Rendering/D3D12}/D3D12HostDevice.h (100%) create mode 100644 new_editor/app/Rendering/D3D12/D3D12HostDeviceFence.cpp create mode 100644 new_editor/app/Rendering/D3D12/D3D12HostDeviceFrame.cpp rename new_editor/app/{Host/D3D12HostDevice.cpp => Rendering/D3D12/D3D12HostDeviceLifecycle.cpp} (53%) rename new_editor/app/{Host => Rendering/D3D12}/D3D12ShaderResourceDescriptorAllocator.cpp (100%) rename new_editor/app/{Host => Rendering/D3D12}/D3D12ShaderResourceDescriptorAllocator.h (100%) create mode 100644 new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp rename new_editor/app/{Host => Rendering/D3D12}/D3D12WindowInteropContext.h (100%) create mode 100644 new_editor/app/Rendering/D3D12/D3D12WindowInteropDevice.cpp create mode 100644 new_editor/app/Rendering/D3D12/D3D12WindowInteropResources.cpp create mode 100644 new_editor/app/Rendering/D3D12/D3D12WindowInteropSourceTextures.cpp create mode 100644 new_editor/app/Rendering/D3D12/D3D12WindowInteropSupport.h rename new_editor/app/{Host => Rendering/D3D12}/D3D12WindowRenderLoop.cpp (100%) rename new_editor/app/{Host => Rendering/D3D12}/D3D12WindowRenderLoop.h (93%) rename new_editor/app/{Host => Rendering/D3D12}/D3D12WindowRenderer.cpp (100%) rename new_editor/app/{Host => Rendering/D3D12}/D3D12WindowRenderer.h (100%) create mode 100644 new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp rename new_editor/app/{Host => Rendering/D3D12}/D3D12WindowSwapChainPresenter.h (100%) create mode 100644 new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterBackBuffers.cpp create mode 100644 new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterLifecycle.cpp create mode 100644 new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterPresentation.cpp create mode 100644 new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterResize.cpp rename new_editor/app/{Host => Rendering/Native}/AutoScreenshot.cpp (80%) rename new_editor/app/{Host => Rendering/Native}/AutoScreenshot.h (100%) create mode 100644 new_editor/app/Rendering/Native/NativeRenderer.cpp rename new_editor/app/{Host => Rendering/Native}/NativeRenderer.h (97%) create mode 100644 new_editor/app/Rendering/Native/NativeRendererCapture.cpp create mode 100644 new_editor/app/Rendering/Native/NativeRendererDraw.cpp create mode 100644 new_editor/app/Rendering/Native/NativeRendererLifecycle.cpp create mode 100644 new_editor/app/Rendering/Native/NativeRendererRendering.cpp create mode 100644 new_editor/app/Rendering/Native/NativeRendererSupport.h create mode 100644 new_editor/app/Rendering/Native/NativeRendererText.cpp create mode 100644 new_editor/app/Rendering/Native/NativeRendererTextures.cpp create mode 100644 new_editor/app/Rendering/Viewport/ViewportHostService.cpp rename new_editor/app/{ => Rendering}/Viewport/ViewportHostService.h (72%) rename new_editor/app/{Viewport/ViewportHostService.cpp => Rendering/Viewport/ViewportHostServiceFrame.cpp} (55%) create mode 100644 new_editor/app/Rendering/Viewport/ViewportHostServiceLifecycle.cpp create mode 100644 new_editor/app/Rendering/Viewport/ViewportRenderTargetManager.cpp rename new_editor/app/{Viewport/ViewportRenderTargetManager.cpp => Rendering/Viewport/ViewportRenderTargetManagerResources.cpp} (61%) create mode 100644 new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerSurfaces.cpp rename new_editor/app/{Viewport/ViewportSurfaceUtils.h => Rendering/Viewport/ViewportRenderTargetSupport.h} (82%) create mode 100644 new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp rename new_editor/app/{ => Rendering}/Viewport/ViewportRenderTargets.h (70%) rename new_editor/app/{ => Rendering}/Viewport/ViewportTypes.h (84%) rename new_editor/app/{Core => State}/EditorContext.cpp (80%) rename new_editor/app/{Core => State}/EditorContext.h (78%) create mode 100644 new_editor/app/State/EditorSession.cpp create mode 100644 new_editor/app/State/EditorSession.h create mode 100644 new_editor/app/Support/EmbeddedPngLoader.h create mode 100644 new_editor/app/Support/EnvironmentFlags.h create mode 100644 new_editor/app/Support/ExecutablePath.h create mode 100644 new_editor/app/Support/StringEncoding.h create mode 100644 new_editor/app/Support/TextFormat.h create mode 100644 new_editor/app/UI/Styles/EditorTreeViewStyle.h delete mode 100644 new_editor/app/Viewport/ViewportRenderTargets.cpp delete mode 100644 new_editor/app/Workspace/EditorWorkspace.cpp delete mode 100644 new_editor/app/Workspace/EditorWorkspaceEventRouter.cpp delete mode 100644 new_editor/app/Workspace/EditorWorkspaceEventRouter.h rename new_editor/include/XCEditor/{Shell => Docking}/UIEditorDockHost.h (88%) rename new_editor/include/XCEditor/{Shell => Docking}/UIEditorDockHostInteraction.h (94%) rename new_editor/include/XCEditor/{Shell => Menu}/UIEditorMenuBar.h (91%) rename new_editor/include/XCEditor/{Shell => Menu}/UIEditorMenuModel.h (100%) rename new_editor/include/XCEditor/{Shell => Menu}/UIEditorMenuPopup.h (88%) rename new_editor/include/XCEditor/{Shell => Menu}/UIEditorMenuSession.h (100%) rename new_editor/include/XCEditor/{Shell => Panels}/UIEditorPanelContentHost.h (96%) rename new_editor/include/XCEditor/{Shell => Panels}/UIEditorPanelFrame.h (84%) rename new_editor/include/XCEditor/{Shell => Panels}/UIEditorPanelHostLifecycle.h (94%) rename new_editor/include/XCEditor/{Shell => Panels}/UIEditorPanelRegistry.h (100%) rename new_editor/include/XCEditor/{Shell => Viewport}/UIEditorViewportInputBridge.h (100%) rename new_editor/include/XCEditor/{Shell => Viewport}/UIEditorViewportShell.h (94%) rename new_editor/include/XCEditor/{Shell => Viewport}/UIEditorViewportSlot.h (85%) rename new_editor/include/XCEditor/{Shell => Workspace}/UIEditorWindowWorkspaceController.h (97%) rename new_editor/include/XCEditor/{Shell => Workspace}/UIEditorWindowWorkspaceModel.h (94%) rename new_editor/include/XCEditor/{Shell => Workspace}/UIEditorWorkspaceCompose.h (93%) rename new_editor/include/XCEditor/{Shell => Workspace}/UIEditorWorkspaceController.h (97%) rename new_editor/include/XCEditor/{Shell => Workspace}/UIEditorWorkspaceInteraction.h (92%) rename new_editor/include/XCEditor/{Shell => Workspace}/UIEditorWorkspaceLayoutPersistence.h (96%) rename new_editor/include/XCEditor/{Shell => Workspace}/UIEditorWorkspaceModel.h (100%) rename new_editor/include/XCEditor/{Shell => Workspace}/UIEditorWorkspaceSession.h (96%) rename new_editor/include/XCEditor/{Shell => Workspace}/UIEditorWorkspaceTransfer.h (95%) rename new_editor/src/{Shell => Docking}/UIEditorDockHost.cpp (98%) rename new_editor/src/{Shell => Docking}/UIEditorDockHostInteraction.cpp (99%) rename new_editor/src/{Shell => Menu}/UIEditorMenuBar.cpp (99%) rename new_editor/src/{Shell => Menu}/UIEditorMenuModel.cpp (99%) rename new_editor/src/{Shell => Menu}/UIEditorMenuPopup.cpp (99%) rename new_editor/src/{Shell => Menu}/UIEditorMenuSession.cpp (99%) rename new_editor/src/{Shell => Panels}/UIEditorPanelContentHost.cpp (99%) rename new_editor/src/{Shell => Panels}/UIEditorPanelFrame.cpp (99%) rename new_editor/src/{Shell => Panels}/UIEditorPanelHostLifecycle.cpp (99%) rename new_editor/src/{Shell => Panels}/UIEditorPanelRegistry.cpp (97%) rename new_editor/src/{Shell => Viewport}/UIEditorViewportInputBridge.cpp (99%) rename new_editor/src/{Shell => Viewport}/UIEditorViewportShell.cpp (98%) rename new_editor/src/{Shell => Viewport}/UIEditorViewportSlot.cpp (99%) rename new_editor/src/{Shell => Workspace}/UIEditorWindowWorkspaceController.cpp (99%) rename new_editor/src/{Shell => Workspace}/UIEditorWindowWorkspaceModel.cpp (98%) rename new_editor/src/{Shell => Workspace}/UIEditorWorkspaceCompose.cpp (99%) rename new_editor/src/{Shell => Workspace}/UIEditorWorkspaceController.cpp (99%) rename new_editor/src/{Shell => Workspace}/UIEditorWorkspaceInteraction.cpp (98%) rename new_editor/src/{Shell => Workspace}/UIEditorWorkspaceLayoutPersistence.cpp (99%) rename new_editor/src/{Shell => Workspace}/UIEditorWorkspaceModel.cpp (99%) rename new_editor/src/{Shell => Workspace}/UIEditorWorkspaceSession.cpp (99%) rename new_editor/src/{Shell => Workspace}/UIEditorWorkspaceTransfer.cpp (99%) diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 1b5a4d78..704473b2 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -147,22 +147,41 @@ target_link_libraries(XCUIEditorLib PUBLIC set(XCUI_EDITOR_HOST_PLATFORM_SOURCES app/Platform/Win32/BorderlessWindowChrome.cpp + app/Platform/Win32/BorderlessWindowChromeRendering.cpp + app/Platform/Win32/BorderlessWindowChromeDwm.cpp app/Platform/Win32/BorderlessWindowFrame.cpp app/Platform/Win32/WindowMessageDispatcher.cpp app/Platform/Win32/WindowMessageDispatcherChrome.cpp app/Platform/Win32/WindowMessageDispatcherInput.cpp + app/Platform/Win32/WindowMessageDispatcherPointer.cpp app/Platform/Win32/WindowMessageDispatcherLifecycle.cpp ) set(XCUI_EDITOR_HOST_RENDERING_SOURCES app/Rendering/Native/AutoScreenshot.cpp app/Rendering/D3D12/D3D12HostDevice.cpp + app/Rendering/D3D12/D3D12HostDeviceFence.cpp + app/Rendering/D3D12/D3D12HostDeviceFrame.cpp + app/Rendering/D3D12/D3D12HostDeviceLifecycle.cpp app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.cpp app/Rendering/D3D12/D3D12WindowInteropContext.cpp + app/Rendering/D3D12/D3D12WindowInteropDevice.cpp + app/Rendering/D3D12/D3D12WindowInteropResources.cpp + app/Rendering/D3D12/D3D12WindowInteropSourceTextures.cpp app/Rendering/D3D12/D3D12WindowRenderer.cpp app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp + app/Rendering/D3D12/D3D12WindowSwapChainPresenterBackBuffers.cpp + app/Rendering/D3D12/D3D12WindowSwapChainPresenterLifecycle.cpp + app/Rendering/D3D12/D3D12WindowSwapChainPresenterPresentation.cpp + app/Rendering/D3D12/D3D12WindowSwapChainPresenterResize.cpp app/Rendering/D3D12/D3D12WindowRenderLoop.cpp app/Rendering/Native/NativeRenderer.cpp + app/Rendering/Native/NativeRendererCapture.cpp + app/Rendering/Native/NativeRendererDraw.cpp + app/Rendering/Native/NativeRendererLifecycle.cpp + app/Rendering/Native/NativeRendererRendering.cpp + app/Rendering/Native/NativeRendererText.cpp + app/Rendering/Native/NativeRendererTextures.cpp ) add_library(XCUIEditorHost STATIC @@ -194,7 +213,10 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) set(XCUI_EDITOR_APP_BOOTSTRAP_SOURCES app/Bootstrap/EditorApp.rc app/Bootstrap/main.cpp - app/Bootstrap/ApplicationCore.cpp + app/Bootstrap/ApplicationBootstrap.cpp + app/Bootstrap/ApplicationLifecycle.cpp + app/Bootstrap/ApplicationRunLoop.cpp + app/Bootstrap/ApplicationWindowClass.cpp ) set(XCUI_EDITOR_APP_STATE_SOURCES @@ -205,35 +227,61 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) set(XCUI_EDITOR_APP_COMPOSITION_SOURCES app/Composition/EditorHostCommandBridge.cpp app/Composition/EditorShellAssetBuilder.cpp - app/Composition/EditorWorkspace.cpp - app/Composition/EditorWorkspaceEventRouter.cpp + app/Composition/EditorShellAssetCommands.cpp + app/Composition/EditorShellAssetDefinition.cpp + app/Composition/EditorShellAssetLayout.cpp + app/Composition/EditorShellAssetMenu.cpp + app/Composition/EditorShellRuntime.cpp + app/Composition/EditorShellRuntimeRendering.cpp + app/Composition/EditorShellRuntimeUpdate.cpp + app/Composition/EditorShellRuntimeViewport.cpp + app/Composition/WorkspaceEventSync.cpp ) set(XCUI_EDITOR_APP_FEATURE_SOURCES app/Features/Console/ConsolePanel.cpp app/Features/Hierarchy/HierarchyModel.cpp app/Features/Hierarchy/HierarchyPanel.cpp + app/Features/Hierarchy/HierarchyPanelInteraction.cpp + app/Features/Hierarchy/HierarchyPanelRendering.cpp app/Features/Inspector/InspectorPanel.cpp app/Features/Project/ProjectPanel.cpp + app/Features/Project/ProjectPanelInteraction.cpp + app/Features/Project/ProjectPanelLayout.cpp + app/Features/Project/ProjectPanelRendering.cpp app/Features/Project/ProjectBrowserModel.cpp + app/Features/Project/ProjectBrowserModelAssets.cpp + app/Features/Project/ProjectBrowserModelFolders.cpp ) set(XCUI_EDITOR_APP_RENDERING_SOURCES app/Rendering/Assets/BuiltInIcons.cpp app/Rendering/Viewport/ViewportHostService.cpp + app/Rendering/Viewport/ViewportHostServiceFrame.cpp + app/Rendering/Viewport/ViewportHostServiceLifecycle.cpp app/Rendering/Viewport/ViewportRenderTargetManager.cpp + app/Rendering/Viewport/ViewportRenderTargetManagerResources.cpp + app/Rendering/Viewport/ViewportRenderTargetManagerSurfaces.cpp app/Rendering/Viewport/ViewportRenderTargets.cpp ) set(XCUI_EDITOR_APP_PLATFORM_SOURCES - app/Platform/Win32/EditorWindowChrome.cpp - app/Platform/Win32/EditorWindowCore.cpp + app/Platform/Win32/EditorWindowBorderlessPlacement.cpp + app/Platform/Win32/EditorWindowBorderlessResize.cpp + app/Platform/Win32/EditorWindowFrame.cpp + app/Platform/Win32/EditorWindowTitleBar.cpp + app/Platform/Win32/EditorWindowTitleBarInteraction.cpp + app/Platform/Win32/EditorWindowTitleBarRendering.cpp + app/Platform/Win32/EditorWindowInitialization.cpp + app/Platform/Win32/EditorWindowLifecycle.cpp + app/Platform/Win32/EditorWindowMetrics.cpp app/Platform/Win32/EditorWindowInput.cpp - app/Platform/Win32/EditorWindowRender.cpp - app/Platform/Win32/ManagedWindowWorkspaceCoordinatorCore.cpp - app/Platform/Win32/ManagedWindowWorkspaceCoordinatorWorkspace.cpp - app/Platform/Win32/ManagedWindowWorkspaceCoordinatorDrag.cpp - app/Platform/Win32/ManagedWindowWorkspaceCoordinatorDrop.cpp + app/Platform/Win32/EditorWindowResizeLifecycle.cpp + app/Platform/Win32/EditorWindowManagerLifecycle.cpp + app/Platform/Win32/EditorWindowManagerWindowSet.cpp + app/Platform/Win32/EditorWindowManagerTabDrag.cpp + app/Platform/Win32/EditorWindowManagerDetach.cpp + app/Platform/Win32/EditorWindowManagerCrossWindowDrop.cpp ) add_executable(XCUIEditorApp WIN32 @@ -270,6 +318,20 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) set_target_properties(XCUIEditorApp PROPERTIES OUTPUT_NAME "XCUIEditor" ) + + add_test( + NAME xcui_editor_app_smoke + COMMAND ${CMAKE_COMMAND} -E env + XCUIEDITOR_SMOKE_TEST=1 + XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4 + XCUI_AUTO_CAPTURE_ON_STARTUP=0 + $ + ) + set_tests_properties(xcui_editor_app_smoke PROPERTIES + TIMEOUT 20 + RUN_SERIAL TRUE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) endif() diff --git a/new_editor/app/Application.cpp b/new_editor/app/Application.cpp deleted file mode 100644 index cabdcfee..00000000 --- a/new_editor/app/Application.cpp +++ /dev/null @@ -1,3026 +0,0 @@ -#include "Application.h" -#include "EditorResources.h" - -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#ifndef XCUIEDITOR_REPO_ROOT -#define XCUIEDITOR_REPO_ROOT "." -#endif - -namespace XCEngine::UI::Editor { - -namespace { - -using ::XCEngine::Input::KeyCode; -using ::XCEngine::UI::UIColor; -using ::XCEngine::UI::UIDrawData; -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIPointerButton; -using ::XCEngine::UI::UIRect; - -constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost"; -constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor"; -constexpr const char* kWindowTitleText = "Main Scene * - Main.xx - XCEngine Editor"; -constexpr UINT kDefaultDpi = 96u; -constexpr float kBaseDpiScale = 96.0f; -constexpr float kBorderlessTitleBarHeightDips = 28.0f; -constexpr float kBorderlessTitleBarFontSize = 12.0f; -constexpr LONG kDetachedWindowDragOffsetXPixels = 40; -constexpr LONG kDetachedWindowDragOffsetYPixels = 12; -const UIColor kShellSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); -const UIColor kShellBorderColor(0.15f, 0.15f, 0.15f, 1.0f); -const UIColor kShellTextColor(0.92f, 0.92f, 0.92f, 1.0f); -const UIColor kShellMutedTextColor(0.70f, 0.70f, 0.70f, 1.0f); -constexpr DWORD kBorderlessWindowStyle = - WS_POPUP | WS_THICKFRAME; - -bool ResolveVerboseRuntimeTraceEnabled() { - wchar_t buffer[8] = {}; - const DWORD length = GetEnvironmentVariableW( - L"XCUIEDITOR_VERBOSE_TRACE", - buffer, - static_cast(std::size(buffer))); - return length > 0u && buffer[0] != L'0'; -} - -bool LoadEmbeddedPngBytes( - UINT resourceId, - const std::uint8_t*& outData, - std::size_t& outSize, - std::string& outError) { - outData = nullptr; - outSize = 0u; - outError.clear(); - - HMODULE module = GetModuleHandleW(nullptr); - if (module == nullptr) { - outError = "GetModuleHandleW(nullptr) returned null."; - return false; - } - - HRSRC resource = FindResourceW(module, MAKEINTRESOURCEW(resourceId), L"PNG"); - if (resource == nullptr) { - outError = "FindResourceW failed."; - return false; - } - - HGLOBAL resourceData = LoadResource(module, resource); - if (resourceData == nullptr) { - outError = "LoadResource failed."; - return false; - } - - const DWORD resourceSize = SizeofResource(module, resource); - if (resourceSize == 0u) { - outError = "SizeofResource returned zero."; - return false; - } - - const void* lockedBytes = LockResource(resourceData); - if (lockedBytes == nullptr) { - outError = "LockResource failed."; - return false; - } - - outData = reinterpret_cast(lockedBytes); - outSize = static_cast(resourceSize); - return true; -} - -bool LoadEmbeddedPngTexture( - Host::NativeRenderer& renderer, - UINT resourceId, - ::XCEngine::UI::UITextureHandle& outTexture, - std::string& outError) { - const std::uint8_t* bytes = nullptr; - std::size_t byteCount = 0u; - if (!LoadEmbeddedPngBytes(resourceId, bytes, byteCount, outError)) { - return false; - } - - return renderer.LoadTextureFromMemory(bytes, byteCount, outTexture, outError); -} - -UINT QuerySystemDpi() { - HDC screenDc = GetDC(nullptr); - if (screenDc == nullptr) { - return kDefaultDpi; - } - - const int dpiX = GetDeviceCaps(screenDc, LOGPIXELSX); - ReleaseDC(nullptr, screenDc); - return dpiX > 0 ? static_cast(dpiX) : kDefaultDpi; -} - -UINT QueryWindowDpi(HWND hwnd) { - if (hwnd != nullptr) { - const HMODULE user32 = GetModuleHandleW(L"user32.dll"); - if (user32 != nullptr) { - using GetDpiForWindowFn = UINT(WINAPI*)(HWND); - const auto getDpiForWindow = - reinterpret_cast(GetProcAddress(user32, "GetDpiForWindow")); - if (getDpiForWindow != nullptr) { - const UINT dpi = getDpiForWindow(hwnd); - if (dpi != 0u) { - return dpi; - } - } - } - } - - return QuerySystemDpi(); -} - -void EnableDpiAwareness() { - const HMODULE user32 = GetModuleHandleW(L"user32.dll"); - if (user32 != nullptr) { - using SetProcessDpiAwarenessContextFn = BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT); - const auto setProcessDpiAwarenessContext = - reinterpret_cast( - GetProcAddress(user32, "SetProcessDpiAwarenessContext")); - if (setProcessDpiAwarenessContext != nullptr) { - if (setProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { - return; - } - if (GetLastError() == ERROR_ACCESS_DENIED) { - return; - } - if (setProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) { - return; - } - if (GetLastError() == ERROR_ACCESS_DENIED) { - return; - } - } - } - - const HMODULE shcore = LoadLibraryW(L"shcore.dll"); - if (shcore != nullptr) { - using SetProcessDpiAwarenessFn = HRESULT(WINAPI*)(PROCESS_DPI_AWARENESS); - const auto setProcessDpiAwareness = - reinterpret_cast( - GetProcAddress(shcore, "SetProcessDpiAwareness")); - if (setProcessDpiAwareness != nullptr) { - const HRESULT hr = setProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); - FreeLibrary(shcore); - if (SUCCEEDED(hr) || hr == E_ACCESSDENIED) { - return; - } - } else { - FreeLibrary(shcore); - } - } - - if (user32 != nullptr) { - using SetProcessDPIAwareFn = BOOL(WINAPI*)(); - const auto setProcessDPIAware = - reinterpret_cast(GetProcAddress(user32, "SetProcessDPIAware")); - if (setProcessDPIAware != nullptr) { - setProcessDPIAware(); - } - } -} - -std::string TruncateText(const std::string& text, std::size_t maxLength) { - if (text.size() <= maxLength) { - return text; - } - - if (maxLength <= 3u) { - return text.substr(0u, maxLength); - } - - return text.substr(0u, maxLength - 3u) + "..."; -} - -bool IsAutoCaptureOnStartupEnabled() { - const char* value = std::getenv("XCUI_AUTO_CAPTURE_ON_STARTUP"); - if (value == nullptr || value[0] == '\0') { - return false; - } - - std::string normalized = value; - std::transform( - normalized.begin(), - normalized.end(), - normalized.begin(), - [](unsigned char character) { - return static_cast(std::tolower(character)); - }); - return normalized != "0" && - normalized != "false" && - normalized != "off" && - normalized != "no"; -} - -std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) { - switch (wParam) { - case 'A': return static_cast(KeyCode::A); - case 'B': return static_cast(KeyCode::B); - case 'C': return static_cast(KeyCode::C); - case 'D': return static_cast(KeyCode::D); - case 'E': return static_cast(KeyCode::E); - case 'F': return static_cast(KeyCode::F); - case 'G': return static_cast(KeyCode::G); - case 'H': return static_cast(KeyCode::H); - case 'I': return static_cast(KeyCode::I); - case 'J': return static_cast(KeyCode::J); - case 'K': return static_cast(KeyCode::K); - case 'L': return static_cast(KeyCode::L); - case 'M': return static_cast(KeyCode::M); - case 'N': return static_cast(KeyCode::N); - case 'O': return static_cast(KeyCode::O); - case 'P': return static_cast(KeyCode::P); - case 'Q': return static_cast(KeyCode::Q); - case 'R': return static_cast(KeyCode::R); - case 'S': return static_cast(KeyCode::S); - case 'T': return static_cast(KeyCode::T); - case 'U': return static_cast(KeyCode::U); - case 'V': return static_cast(KeyCode::V); - case 'W': return static_cast(KeyCode::W); - case 'X': return static_cast(KeyCode::X); - case 'Y': return static_cast(KeyCode::Y); - case 'Z': return static_cast(KeyCode::Z); - case '0': return static_cast(KeyCode::Zero); - case '1': return static_cast(KeyCode::One); - case '2': return static_cast(KeyCode::Two); - case '3': return static_cast(KeyCode::Three); - case '4': return static_cast(KeyCode::Four); - case '5': return static_cast(KeyCode::Five); - case '6': return static_cast(KeyCode::Six); - case '7': return static_cast(KeyCode::Seven); - case '8': return static_cast(KeyCode::Eight); - case '9': return static_cast(KeyCode::Nine); - case VK_SPACE: return static_cast(KeyCode::Space); - case VK_TAB: return static_cast(KeyCode::Tab); - case VK_RETURN: return static_cast(KeyCode::Enter); - case VK_ESCAPE: return static_cast(KeyCode::Escape); - case VK_SHIFT: return static_cast(KeyCode::LeftShift); - case VK_CONTROL: return static_cast(KeyCode::LeftCtrl); - case VK_MENU: return static_cast(KeyCode::LeftAlt); - case VK_UP: return static_cast(KeyCode::Up); - case VK_DOWN: return static_cast(KeyCode::Down); - case VK_LEFT: return static_cast(KeyCode::Left); - case VK_RIGHT: return static_cast(KeyCode::Right); - case VK_HOME: return static_cast(KeyCode::Home); - case VK_END: return static_cast(KeyCode::End); - case VK_PRIOR: return static_cast(KeyCode::PageUp); - case VK_NEXT: return static_cast(KeyCode::PageDown); - case VK_DELETE: return static_cast(KeyCode::Delete); - case VK_BACK: return static_cast(KeyCode::Backspace); - case VK_F1: return static_cast(KeyCode::F1); - case VK_F2: return static_cast(KeyCode::F2); - case VK_F3: return static_cast(KeyCode::F3); - case VK_F4: return static_cast(KeyCode::F4); - case VK_F5: return static_cast(KeyCode::F5); - case VK_F6: return static_cast(KeyCode::F6); - case VK_F7: return static_cast(KeyCode::F7); - case VK_F8: return static_cast(KeyCode::F8); - case VK_F9: return static_cast(KeyCode::F9); - case VK_F10: return static_cast(KeyCode::F10); - case VK_F11: return static_cast(KeyCode::F11); - case VK_F12: return static_cast(KeyCode::F12); - default: return static_cast(KeyCode::None); - } -} - -bool IsRepeatKeyMessage(LPARAM lParam) { - return (static_cast(lParam) & (1ul << 30)) != 0ul; -} - -std::filesystem::path GetExecutableDirectory() { - std::vector buffer(MAX_PATH); - while (true) { - const DWORD copied = ::GetModuleFileNameW( - nullptr, - buffer.data(), - static_cast(buffer.size())); - if (copied == 0u) { - return std::filesystem::current_path().lexically_normal(); - } - - if (copied < buffer.size() - 1u) { - return std::filesystem::path(std::wstring(buffer.data(), copied)) - .parent_path() - .lexically_normal(); - } - - buffer.resize(buffer.size() * 2u); - } -} - -std::string DescribeInputEventType(const UIInputEvent& event) { - switch (event.type) { - case UIInputEventType::PointerMove: return "PointerMove"; - case UIInputEventType::PointerEnter: return "PointerEnter"; - case UIInputEventType::PointerLeave: return "PointerLeave"; - case UIInputEventType::PointerButtonDown: return "PointerDown"; - case UIInputEventType::PointerButtonUp: return "PointerUp"; - case UIInputEventType::PointerWheel: return "PointerWheel"; - case UIInputEventType::KeyDown: return "KeyDown"; - case UIInputEventType::KeyUp: return "KeyUp"; - case UIInputEventType::Character: return "Character"; - case UIInputEventType::FocusGained: return "FocusGained"; - case UIInputEventType::FocusLost: return "FocusLost"; - default: return "Unknown"; - } -} - -std::string DescribeProjectPanelEvent(const App::ProductProjectPanel::Event& event) { - std::ostringstream stream = {}; - switch (event.kind) { - case App::ProductProjectPanel::EventKind::AssetSelected: - stream << "AssetSelected"; - break; - case App::ProductProjectPanel::EventKind::AssetSelectionCleared: - stream << "AssetSelectionCleared"; - break; - case App::ProductProjectPanel::EventKind::FolderNavigated: - stream << "FolderNavigated"; - break; - case App::ProductProjectPanel::EventKind::AssetOpened: - stream << "AssetOpened"; - break; - case App::ProductProjectPanel::EventKind::ContextMenuRequested: - stream << "ContextMenuRequested"; - break; - case App::ProductProjectPanel::EventKind::None: - default: - stream << "None"; - break; - } - - stream << " source="; - switch (event.source) { - case App::ProductProjectPanel::EventSource::Tree: - stream << "Tree"; - break; - case App::ProductProjectPanel::EventSource::Breadcrumb: - stream << "Breadcrumb"; - break; - case App::ProductProjectPanel::EventSource::GridPrimary: - stream << "GridPrimary"; - break; - case App::ProductProjectPanel::EventSource::GridDoubleClick: - stream << "GridDoubleClick"; - break; - case App::ProductProjectPanel::EventSource::GridSecondary: - stream << "GridSecondary"; - break; - case App::ProductProjectPanel::EventSource::Background: - stream << "Background"; - break; - case App::ProductProjectPanel::EventSource::None: - default: - stream << "None"; - break; - } - - if (!event.itemId.empty()) { - stream << " item=" << event.itemId; - } - if (!event.displayName.empty()) { - stream << " label=" << event.displayName; - } - return stream.str(); -} - -std::string DescribeHierarchyPanelEvent(const App::ProductHierarchyPanel::Event& event) { - std::ostringstream stream = {}; - switch (event.kind) { - case App::ProductHierarchyPanel::EventKind::SelectionChanged: - stream << "SelectionChanged"; - break; - case App::ProductHierarchyPanel::EventKind::Reparented: - stream << "Reparented"; - break; - case App::ProductHierarchyPanel::EventKind::MovedToRoot: - stream << "MovedToRoot"; - break; - case App::ProductHierarchyPanel::EventKind::RenameRequested: - stream << "RenameRequested"; - break; - case App::ProductHierarchyPanel::EventKind::None: - default: - stream << "None"; - break; - } - - if (!event.itemId.empty()) { - stream << " item=" << event.itemId; - } - if (!event.targetItemId.empty()) { - stream << " target=" << event.targetItemId; - } - if (!event.label.empty()) { - stream << " label=" << event.label; - } - return stream.str(); -} - -struct CrossWindowDockDropTarget { - bool valid = false; - std::string nodeId = {}; - UIEditorWorkspaceDockPlacement placement = UIEditorWorkspaceDockPlacement::Center; - std::size_t insertionIndex = Widgets::UIEditorTabStripInvalidIndex; -}; - -bool IsPointInsideRect( - const UIRect& rect, - const UIPoint& point) { - return point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; -} - -const Widgets::UIEditorDockHostTabStackLayout* FindDockHostTabStackLayoutByNodeId( - const Widgets::UIEditorDockHostLayout& layout, - std::string_view nodeId) { - for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { - if (tabStack.nodeId == nodeId) { - return &tabStack; - } - } - - return nullptr; -} - -std::size_t ResolveCrossWindowDropInsertionIndex( - const Widgets::UIEditorDockHostTabStackLayout& tabStack, - const UIPoint& point) { - if (!IsPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { - return Widgets::UIEditorTabStripInvalidIndex; - } - - std::size_t insertionIndex = 0u; - for (const UIRect& rect : tabStack.tabStripLayout.tabHeaderRects) { - const float midpoint = rect.x + rect.width * 0.5f; - if (point.x > midpoint) { - ++insertionIndex; - } - } - return insertionIndex; -} - -UIEditorWorkspaceDockPlacement ResolveCrossWindowDockPlacement( - const Widgets::UIEditorDockHostTabStackLayout& tabStack, - const UIPoint& point) { - if (IsPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { - return UIEditorWorkspaceDockPlacement::Center; - } - - const float leftDistance = point.x - tabStack.bounds.x; - const float rightDistance = tabStack.bounds.x + tabStack.bounds.width - point.x; - const float topDistance = point.y - tabStack.bounds.y; - const float bottomDistance = tabStack.bounds.y + tabStack.bounds.height - point.y; - const float minHorizontalThreshold = tabStack.bounds.width * 0.25f; - const float minVerticalThreshold = tabStack.bounds.height * 0.25f; - const float nearestEdge = - (std::min)((std::min)(leftDistance, rightDistance), (std::min)(topDistance, bottomDistance)); - - if (nearestEdge == leftDistance && leftDistance <= minHorizontalThreshold) { - return UIEditorWorkspaceDockPlacement::Left; - } - if (nearestEdge == rightDistance && rightDistance <= minHorizontalThreshold) { - return UIEditorWorkspaceDockPlacement::Right; - } - if (nearestEdge == topDistance && topDistance <= minVerticalThreshold) { - return UIEditorWorkspaceDockPlacement::Top; - } - if (nearestEdge == bottomDistance && bottomDistance <= minVerticalThreshold) { - return UIEditorWorkspaceDockPlacement::Bottom; - } - - return UIEditorWorkspaceDockPlacement::Center; -} - -bool TryResolveCrossWindowDockDropTarget( - const Widgets::UIEditorDockHostLayout& layout, - const UIPoint& point, - CrossWindowDockDropTarget& outTarget) { - outTarget = {}; - if (!IsPointInsideRect(layout.bounds, point)) { - return false; - } - - const Widgets::UIEditorDockHostHitTarget hitTarget = - Widgets::HitTestUIEditorDockHost(layout, point); - for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { - if ((!hitTarget.nodeId.empty() && tabStack.nodeId != hitTarget.nodeId) || - !IsPointInsideRect(tabStack.bounds, point)) { - continue; - } - - outTarget.valid = true; - outTarget.nodeId = tabStack.nodeId; - outTarget.placement = ResolveCrossWindowDockPlacement(tabStack, point); - if (outTarget.placement == UIEditorWorkspaceDockPlacement::Center) { - outTarget.insertionIndex = ResolveCrossWindowDropInsertionIndex(tabStack, point); - if (outTarget.insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { - outTarget.insertionIndex = tabStack.items.size(); - } - } - return true; - } - - for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { - if (!IsPointInsideRect(tabStack.bounds, point)) { - continue; - } - - outTarget.valid = true; - outTarget.nodeId = tabStack.nodeId; - outTarget.placement = ResolveCrossWindowDockPlacement(tabStack, point); - if (outTarget.placement == UIEditorWorkspaceDockPlacement::Center) { - outTarget.insertionIndex = tabStack.items.size(); - } - return true; - } - - return false; -} - -} // namespace - -Application::Application() = default; -Application::~Application() = default; - -Application::ManagedWindowState* Application::FindWindowState(HWND hwnd) { - if (hwnd == nullptr) { - return nullptr; - } - - for (const std::unique_ptr& windowState : m_windows) { - if (windowState != nullptr && windowState->hwnd == hwnd) { - return windowState.get(); - } - } - - return nullptr; -} - -const Application::ManagedWindowState* Application::FindWindowState(HWND hwnd) const { - if (hwnd == nullptr) { - return nullptr; - } - - for (const std::unique_ptr& windowState : m_windows) { - if (windowState != nullptr && windowState->hwnd == hwnd) { - return windowState.get(); - } - } - - return nullptr; -} - -Application::ManagedWindowState* Application::FindWindowState(std::string_view windowId) { - if (windowId.empty()) { - return nullptr; - } - - for (const std::unique_ptr& windowState : m_windows) { - if (windowState != nullptr && windowState->windowId == windowId) { - return windowState.get(); - } - } - - return nullptr; -} - -const Application::ManagedWindowState* Application::FindWindowState(std::string_view windowId) const { - if (windowId.empty()) { - return nullptr; - } - - for (const std::unique_ptr& windowState : m_windows) { - if (windowState != nullptr && windowState->windowId == windowId) { - return windowState.get(); - } - } - - return nullptr; -} - -Application::ManagedWindowState* Application::FindPrimaryWindowState() { - for (const std::unique_ptr& windowState : m_windows) { - if (windowState != nullptr && windowState->primary) { - return windowState.get(); - } - } - - return nullptr; -} - -const Application::ManagedWindowState* Application::FindPrimaryWindowState() const { - for (const std::unique_ptr& windowState : m_windows) { - if (windowState != nullptr && windowState->primary) { - return windowState.get(); - } - } - - return nullptr; -} - -Application::ManagedWindowState& Application::RequireCurrentWindowState() { - assert(m_currentWindowState != nullptr); - return *m_currentWindowState; -} - -const Application::ManagedWindowState& Application::RequireCurrentWindowState() const { - assert(m_currentWindowState != nullptr); - return *m_currentWindowState; -} - -#define m_hwnd RequireCurrentWindowState().hwnd -#define m_renderer RequireCurrentWindowState().renderer -#define m_windowRenderer RequireCurrentWindowState().windowRenderer -#define m_windowRenderLoop RequireCurrentWindowState().windowRenderLoop -#define m_autoScreenshot RequireCurrentWindowState().autoScreenshot -#define m_inputModifierTracker RequireCurrentWindowState().inputModifierTracker -#define m_workspaceController RequireCurrentWindowState().workspaceController -#define m_editorWorkspace RequireCurrentWindowState().editorWorkspace -#define m_pendingInputEvents RequireCurrentWindowState().pendingInputEvents -#define m_trackingMouseLeave RequireCurrentWindowState().trackingMouseLeave -#define m_renderReady RequireCurrentWindowState().renderReady -#define m_titleBarLogoIcon RequireCurrentWindowState().titleBarLogoIcon -#define m_borderlessWindowChromeState RequireCurrentWindowState().borderlessWindowChromeState -#define m_hostRuntime RequireCurrentWindowState().hostRuntime - -int Application::Run(HINSTANCE hInstance, int nCmdShow) { - if (!Initialize(hInstance, nCmdShow)) { - Shutdown(); - return 1; - } - - MSG message = {}; - while (true) { - while (PeekMessageW(&message, nullptr, 0U, 0U, PM_REMOVE)) { - if (message.message == WM_QUIT) { - Shutdown(); - return static_cast(message.wParam); - } - - TranslateMessage(&message); - DispatchMessageW(&message); - } - - DestroyClosedWindows(); - ProcessPendingGlobalTabDragStarts(); - ProcessPendingDetachRequests(); - DestroyClosedWindows(); - if (m_windows.empty()) { - break; - } - - RenderAllWindows(); - } - - Shutdown(); - return 0; -} - -bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { - m_hInstance = hInstance; - m_repoRoot = ResolveRepoRootPath(); - m_shutdownRequested = false; - EnableDpiAwareness(); - const std::filesystem::path logRoot = - GetExecutableDirectory() / "logs"; - InitializeUIEditorRuntimeTrace(logRoot); - SetUnhandledExceptionFilter(&Application::HandleUnhandledException); - LogRuntimeTrace("app", "initialize begin"); - if (!m_editorContext.Initialize(m_repoRoot)) { - LogRuntimeTrace( - "app", - "shell asset validation failed: " + m_editorContext.GetValidationMessage()); - return false; - } - if (!RegisterWindowClass()) { - return false; - } - m_editorContext.SetExitRequestHandler([this]() { - if (ManagedWindowState* primaryWindowState = FindPrimaryWindowState(); - primaryWindowState != nullptr && - primaryWindowState->hwnd != nullptr) { - PostMessageW(primaryWindowState->hwnd, WM_CLOSE, 0, 0); - } - }); - - ManagedWindowCreateParams primaryParams = {}; - primaryParams.windowId = "main-window"; - primaryParams.title = std::wstring(kWindowTitle); - primaryParams.showCommand = nCmdShow; - primaryParams.primary = true; - primaryParams.autoCaptureOnStartup = true; - if (CreateManagedWindow(m_editorContext.BuildWorkspaceController(), primaryParams) == nullptr) { - LogRuntimeTrace("app", "primary window creation failed"); - return false; - } - - LogRuntimeTrace("app", "initialize completed"); - return true; -} - -bool Application::RegisterWindowClass() { - if (m_windowClassAtom != 0) { - return true; - } - - WNDCLASSEXW windowClass = {}; - windowClass.cbSize = sizeof(windowClass); - windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; - windowClass.lpfnWndProc = &Application::WndProc; - windowClass.hInstance = m_hInstance; - windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); - windowClass.hIcon = static_cast( - LoadImageW( - m_hInstance, - MAKEINTRESOURCEW(IDI_APP_ICON), - IMAGE_ICON, - 0, - 0, - LR_DEFAULTSIZE)); - windowClass.hIconSm = static_cast( - LoadImageW( - m_hInstance, - MAKEINTRESOURCEW(IDI_APP_ICON_SMALL), - IMAGE_ICON, - GetSystemMetrics(SM_CXSMICON), - GetSystemMetrics(SM_CYSMICON), - LR_DEFAULTCOLOR)); - windowClass.lpszClassName = kWindowClassName; - m_windowClassAtom = RegisterClassExW(&windowClass); - if (m_windowClassAtom == 0) { - LogRuntimeTrace("app", "window class registration failed"); - return false; - } - - return true; -} - -Application::ManagedWindowState* Application::CreateManagedWindow( - UIEditorWorkspaceController workspaceController, - const ManagedWindowCreateParams& params) { - auto windowState = std::make_unique(); - windowState->windowId = params.windowId; - windowState->title = params.title.empty() ? std::wstring(L"XCEngine Editor") : params.title; - windowState->primary = params.primary; - windowState->workspaceController = std::move(workspaceController); - ManagedWindowState* const rawWindowState = windowState.get(); - m_windows.push_back(std::move(windowState)); - - const auto eraseRawWindowState = [this, rawWindowState]() { - const auto it = std::find_if( - m_windows.begin(), - m_windows.end(), - [rawWindowState](const std::unique_ptr& candidate) { - return candidate.get() == rawWindowState; - }); - if (it != m_windows.end()) { - m_windows.erase(it); - } - }; - - ManagedWindowState* const previousWindowState = m_currentWindowState; - m_currentWindowState = rawWindowState; - m_pendingCreateWindowState = rawWindowState; - rawWindowState->hwnd = CreateWindowExW( - WS_EX_APPWINDOW, - kWindowClassName, - rawWindowState->title.c_str(), - kBorderlessWindowStyle, - params.initialX, - params.initialY, - params.initialWidth, - params.initialHeight, - nullptr, - nullptr, - m_hInstance, - this); - m_pendingCreateWindowState = nullptr; - if (rawWindowState->hwnd == nullptr) { - m_currentWindowState = previousWindowState; - eraseRawWindowState(); - return nullptr; - } - - auto failWindowInitialization = [&](std::string_view message) { - LogRuntimeTrace("app", std::string(message)); - DestroyManagedWindow(*rawWindowState); - m_currentWindowState = previousWindowState; - eraseRawWindowState(); - return static_cast(nullptr); - }; - - const HICON bigIcon = static_cast( - LoadImageW( - m_hInstance, - MAKEINTRESOURCEW(IDI_APP_ICON), - IMAGE_ICON, - 0, - 0, - LR_DEFAULTSIZE)); - const HICON smallIcon = static_cast( - LoadImageW( - m_hInstance, - MAKEINTRESOURCEW(IDI_APP_ICON_SMALL), - IMAGE_ICON, - GetSystemMetrics(SM_CXSMICON), - GetSystemMetrics(SM_CYSMICON), - LR_DEFAULTCOLOR)); - if (bigIcon != nullptr) { - SendMessageW(m_hwnd, WM_SETICON, ICON_BIG, reinterpret_cast(bigIcon)); - } - if (smallIcon != nullptr) { - SendMessageW(m_hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(smallIcon)); - } - - Host::RefreshBorderlessWindowDwmDecorations(m_hwnd); - m_hostRuntime.Reset(); - m_hostRuntime.SetWindowDpi(QueryWindowDpi(m_hwnd)); - m_renderer.SetDpiScale(GetDpiScale()); - - std::ostringstream dpiTrace = {}; - dpiTrace << "initial dpi=" << m_hostRuntime.GetWindowDpi() << " scale=" << GetDpiScale(); - LogRuntimeTrace("window", dpiTrace.str()); - - if (!m_renderer.Initialize(m_hwnd)) { - return failWindowInitialization("renderer initialization failed"); - } - - RECT clientRect = {}; - GetClientRect(m_hwnd, &clientRect); - const int clientWidth = (std::max)(clientRect.right - clientRect.left, 1L); - const int clientHeight = (std::max)(clientRect.bottom - clientRect.top, 1L); - if (!m_windowRenderer.Initialize(m_hwnd, clientWidth, clientHeight)) { - return failWindowInitialization("d3d12 window renderer initialization failed"); - } - - const Host::D3D12WindowRenderLoopAttachResult attachResult = - m_windowRenderLoop.Attach(m_renderer, m_windowRenderer); - if (!attachResult.interopWarning.empty()) { - LogRuntimeTrace("app", attachResult.interopWarning); - } - - m_editorContext.AttachTextMeasurer(m_renderer); - m_editorWorkspace.Initialize(m_repoRoot, m_renderer); - m_editorWorkspace.AttachViewportWindowRenderer(m_windowRenderer); - m_editorWorkspace.SetViewportSurfacePresentationEnabled( - attachResult.hasViewportSurfacePresentation); - - std::string titleBarLogoError = {}; - if (!LoadEmbeddedPngTexture(m_renderer, IDR_PNG_LOGO_ICON, m_titleBarLogoIcon, titleBarLogoError)) { - LogRuntimeTrace("icons", "titlebar logo_icon.png: " + titleBarLogoError); - } - if (!m_editorWorkspace.GetBuiltInIconError().empty()) { - LogRuntimeTrace("icons", m_editorWorkspace.GetBuiltInIconError()); - } - - LogRuntimeTrace( - "app", - "workspace initialized: " + - m_editorContext.DescribeWorkspaceState( - m_workspaceController, - m_editorWorkspace.GetShellInteractionState())); - m_renderReady = true; - - ShowWindow(m_hwnd, params.showCommand); - UpdateWindow(m_hwnd); - - m_autoScreenshot.Initialize(m_editorContext.GetShellAsset().captureRootPath); - if (params.autoCaptureOnStartup && IsAutoCaptureOnStartupEnabled()) { - m_autoScreenshot.RequestCapture("startup"); - m_editorContext.SetStatus("Capture", "Startup capture requested."); - } - - m_currentWindowState = previousWindowState; - return rawWindowState; -} - -void Application::DestroyManagedWindow(ManagedWindowState& windowState) { - if (GetCapture() == windowState.hwnd) { - ReleaseCapture(); - } - - windowState.renderReady = false; - windowState.autoScreenshot.Shutdown(); - windowState.editorWorkspace.Shutdown(); - windowState.renderer.ReleaseTexture(windowState.titleBarLogoIcon); - windowState.windowRenderLoop.Detach(); - windowState.windowRenderer.Shutdown(); - windowState.renderer.Shutdown(); - if (windowState.hwnd != nullptr && IsWindow(windowState.hwnd)) { - DestroyWindow(windowState.hwnd); - } - windowState.hwnd = nullptr; -} - -void Application::DestroyClosedWindows() { - for (auto it = m_windows.begin(); it != m_windows.end();) { - ManagedWindowState* const windowState = it->get(); - if (windowState == nullptr || windowState->hwnd != nullptr) { - ++it; - continue; - } - - if (m_currentWindowState == windowState) { - m_currentWindowState = nullptr; - } - if (m_pendingCreateWindowState == windowState) { - m_pendingCreateWindowState = nullptr; - } - - DestroyManagedWindow(*windowState); - it = m_windows.erase(it); - } -} - -void Application::RenderAllWindows() { - for (const std::unique_ptr& windowState : m_windows) { - if (windowState == nullptr || windowState->hwnd == nullptr) { - continue; - } - - ManagedWindowState* const previousWindowState = m_currentWindowState; - m_currentWindowState = windowState.get(); - RenderFrame(); - m_currentWindowState = previousWindowState; - } -} - -UIEditorWindowWorkspaceSet Application::BuildWindowWorkspaceSet(std::string_view activeWindowId) const { - UIEditorWindowWorkspaceSet windowSet = {}; - if (const ManagedWindowState* primaryWindowState = FindPrimaryWindowState(); - primaryWindowState != nullptr) { - windowSet.primaryWindowId = primaryWindowState->windowId; - } - - for (const std::unique_ptr& windowState : m_windows) { - if (windowState == nullptr || windowState->hwnd == nullptr) { - continue; - } - - UIEditorWindowWorkspaceState entry = {}; - entry.windowId = windowState->windowId; - entry.workspace = windowState->workspaceController.GetWorkspace(); - entry.session = windowState->workspaceController.GetSession(); - windowSet.windows.push_back(std::move(entry)); - } - - if (!activeWindowId.empty() && FindWindowState(activeWindowId) != nullptr) { - windowSet.activeWindowId = std::string(activeWindowId); - } else if (m_currentWindowState != nullptr && m_currentWindowState->hwnd != nullptr) { - windowSet.activeWindowId = m_currentWindowState->windowId; - } else { - windowSet.activeWindowId = windowSet.primaryWindowId; - } - - return windowSet; -} - -UIEditorWorkspaceController Application::BuildWorkspaceControllerForWindow( - const UIEditorWindowWorkspaceState& windowState) const { - return UIEditorWorkspaceController( - m_editorContext.GetShellAsset().panelRegistry, - windowState.workspace, - windowState.session); -} - -std::wstring Application::BuildManagedWindowTitle( - const UIEditorWorkspaceController& workspaceController) const { - const std::string& activePanelId = workspaceController.GetWorkspace().activePanelId; - if (const UIEditorPanelDescriptor* descriptor = - FindUIEditorPanelDescriptor( - workspaceController.GetPanelRegistry(), - activePanelId); - descriptor != nullptr && - !descriptor->defaultTitle.empty()) { - const std::string titleText = descriptor->defaultTitle + " - XCEngine Editor"; - return std::wstring(titleText.begin(), titleText.end()); - } - - return std::wstring(L"XCEngine Editor"); -} - -RECT Application::BuildDetachedWindowRect(const POINT& screenPoint) const { - RECT rect = { - screenPoint.x - kDetachedWindowDragOffsetXPixels, - screenPoint.y - kDetachedWindowDragOffsetYPixels, - screenPoint.x - kDetachedWindowDragOffsetXPixels + 960, - screenPoint.y - kDetachedWindowDragOffsetYPixels + 720 - }; - - const HMONITOR monitor = MonitorFromPoint(screenPoint, MONITOR_DEFAULTTONEAREST); - MONITORINFO monitorInfo = {}; - monitorInfo.cbSize = sizeof(monitorInfo); - if (monitor != nullptr && GetMonitorInfoW(monitor, &monitorInfo)) { - const RECT& workArea = monitorInfo.rcWork; - const LONG width = rect.right - rect.left; - const LONG height = rect.bottom - rect.top; - rect.left = (std::max)(workArea.left, (std::min)(rect.left, workArea.right - width)); - rect.top = (std::max)(workArea.top, (std::min)(rect.top, workArea.bottom - height)); - rect.right = rect.left + width; - rect.bottom = rect.top + height; - } - - return rect; -} - -void Application::ResetManagedWindowInteractionState(ManagedWindowState& windowState) { - if (GetCapture() == windowState.hwnd) { - ReleaseCapture(); - } - - windowState.pendingInputEvents.clear(); - windowState.trackingMouseLeave = false; - windowState.inputModifierTracker.Reset(); - windowState.editorWorkspace.ResetInteractionState(); - windowState.borderlessWindowChromeState = {}; - windowState.hostRuntime.EndBorderlessResize(); - windowState.hostRuntime.EndBorderlessWindowDragRestore(); - windowState.hostRuntime.EndInteractiveResize(); - windowState.hostRuntime.SetHoveredBorderlessResizeEdge( - Host::BorderlessWindowResizeEdge::None); - windowState.hostRuntime.ClearPredictedClientPixelSize(); - windowState.detachRequested = false; - windowState.detachedNodeId.clear(); - windowState.detachedPanelId.clear(); - windowState.detachScreenPoint = {}; - windowState.pendingGlobalTabDragStart = false; - windowState.pendingGlobalTabDragNodeId.clear(); - windowState.pendingGlobalTabDragPanelId.clear(); - windowState.pendingGlobalTabDragScreenPoint = {}; - windowState.pendingGlobalTabDragWindowOffset = {}; -} - -bool Application::SynchronizeManagedWindowsFromWindowSet( - const UIEditorWindowWorkspaceSet& windowSet, - std::string_view preferredNewWindowId, - const POINT& preferredScreenPoint) { - std::vector windowIdsInSet = {}; - windowIdsInSet.reserve(windowSet.windows.size()); - - for (const UIEditorWindowWorkspaceState& entry : windowSet.windows) { - windowIdsInSet.push_back(entry.windowId); - if (ManagedWindowState* existingWindowState = FindWindowState(entry.windowId); - existingWindowState != nullptr) { - existingWindowState->workspaceController = BuildWorkspaceControllerForWindow(entry); - ResetManagedWindowInteractionState(*existingWindowState); - if (!existingWindowState->primary) { - existingWindowState->title = BuildManagedWindowTitle(existingWindowState->workspaceController); - if (existingWindowState->hwnd != nullptr) { - SetWindowTextW(existingWindowState->hwnd, existingWindowState->title.c_str()); - } - } - continue; - } - - ManagedWindowCreateParams createParams = {}; - createParams.windowId = entry.windowId; - createParams.primary = entry.windowId == windowSet.primaryWindowId; - createParams.title = - createParams.primary - ? std::wstring(kWindowTitle) - : BuildManagedWindowTitle(BuildWorkspaceControllerForWindow(entry)); - if (entry.windowId == preferredNewWindowId) { - const RECT detachedRect = BuildDetachedWindowRect(preferredScreenPoint); - createParams.initialX = detachedRect.left; - createParams.initialY = detachedRect.top; - createParams.initialWidth = detachedRect.right - detachedRect.left; - createParams.initialHeight = detachedRect.bottom - detachedRect.top; - } - - if (CreateManagedWindow(BuildWorkspaceControllerForWindow(entry), createParams) == nullptr) { - return false; - } - } - - for (const std::unique_ptr& windowState : m_windows) { - if (windowState == nullptr || - windowState->hwnd == nullptr || - windowState->primary) { - continue; - } - - const bool existsInWindowSet = - std::find(windowIdsInSet.begin(), windowIdsInSet.end(), windowState->windowId) != - windowIdsInSet.end(); - if (!existsInWindowSet) { - PostMessageW(windowState->hwnd, WM_CLOSE, 0, 0); - } - } - - return true; -} - -void Application::BeginGlobalTabDragSession( - std::string_view panelWindowId, - std::string_view sourceNodeId, - std::string_view panelId, - const POINT& screenPoint, - const POINT& windowDragOffset) { - m_globalTabDragSession.active = true; - m_globalTabDragSession.panelWindowId = std::string(panelWindowId); - m_globalTabDragSession.sourceNodeId = std::string(sourceNodeId); - m_globalTabDragSession.panelId = std::string(panelId); - m_globalTabDragSession.screenPoint = screenPoint; - m_globalTabDragSession.windowDragOffset = windowDragOffset; -} - -void Application::EndGlobalTabDragSession() { - if (!m_globalTabDragSession.active) { - return; - } - - if (ManagedWindowState* ownerWindowState = - FindWindowState(m_globalTabDragSession.panelWindowId); - ownerWindowState != nullptr && - ownerWindowState->hwnd != nullptr && - GetCapture() == ownerWindowState->hwnd) { - ReleaseCapture(); - } - - m_globalTabDragSession = {}; -} - -Application::ManagedWindowState* Application::FindTopmostWindowStateAtScreenPoint( - const POINT& screenPoint, - std::string_view excludedWindowId) { - if (const HWND hitWindow = WindowFromPoint(screenPoint); hitWindow != nullptr) { - const HWND rootWindow = GetAncestor(hitWindow, GA_ROOT); - if (ManagedWindowState* windowState = FindWindowState(rootWindow); - windowState != nullptr && - windowState->windowId != excludedWindowId) { - return windowState; - } - } - - for (auto it = m_windows.rbegin(); it != m_windows.rend(); ++it) { - ManagedWindowState* const windowState = it->get(); - if (windowState == nullptr || - windowState->hwnd == nullptr || - windowState->windowId == excludedWindowId) { - continue; - } - - RECT windowRect = {}; - if (GetWindowRect(windowState->hwnd, &windowRect) && - screenPoint.x >= windowRect.left && - screenPoint.x < windowRect.right && - screenPoint.y >= windowRect.top && - screenPoint.y < windowRect.bottom) { - return windowState; - } - } - - return nullptr; -} - -const Application::ManagedWindowState* Application::FindTopmostWindowStateAtScreenPoint( - const POINT& screenPoint, - std::string_view excludedWindowId) const { - return const_cast(this)->FindTopmostWindowStateAtScreenPoint( - screenPoint, - excludedWindowId); -} - -POINT Application::ConvertClientDipsToScreenPixels( - const ManagedWindowState& windowState, - const UIPoint& point) const { - const float dpiScale = windowState.hostRuntime.GetDpiScale(kBaseDpiScale); - POINT clientPoint = { - static_cast(point.x * dpiScale), - static_cast(point.y * dpiScale) - }; - if (windowState.hwnd != nullptr) { - ClientToScreen(windowState.hwnd, &clientPoint); - } - return clientPoint; -} - -bool Application::TryResolveDraggedTabScreenRect( - const ManagedWindowState& windowState, - std::string_view nodeId, - std::string_view panelId, - RECT& outRect) const { - outRect = {}; - const Widgets::UIEditorDockHostLayout& layout = - windowState.editorWorkspace - .GetShellFrame() - .workspaceInteractionFrame - .dockHostFrame - .layout; - const Widgets::UIEditorDockHostTabStackLayout* tabStack = - FindDockHostTabStackLayoutByNodeId(layout, nodeId); - if (tabStack == nullptr) { - return false; - } - - std::size_t tabIndex = Widgets::UIEditorTabStripInvalidIndex; - for (std::size_t index = 0; index < tabStack->items.size(); ++index) { - if (tabStack->items[index].panelId == panelId) { - tabIndex = index; - break; - } - } - - if (tabIndex == Widgets::UIEditorTabStripInvalidIndex || - tabIndex >= tabStack->tabStripLayout.tabHeaderRects.size()) { - return false; - } - - const UIRect& tabRect = tabStack->tabStripLayout.tabHeaderRects[tabIndex]; - const POINT topLeft = ConvertClientDipsToScreenPixels( - windowState, - UIPoint(tabRect.x, tabRect.y)); - const POINT bottomRight = ConvertClientDipsToScreenPixels( - windowState, - UIPoint(tabRect.x + tabRect.width, tabRect.y + tabRect.height)); - outRect.left = topLeft.x; - outRect.top = topLeft.y; - outRect.right = bottomRight.x; - outRect.bottom = bottomRight.y; - return outRect.right > outRect.left && outRect.bottom > outRect.top; -} - -POINT Application::ResolveGlobalTabDragWindowOffset( - const ManagedWindowState& windowState, - std::string_view nodeId, - std::string_view panelId, - const POINT& screenPoint) const { - RECT tabScreenRect = {}; - if (TryResolveDraggedTabScreenRect(windowState, nodeId, panelId, tabScreenRect)) { - const LONG offsetX = - (std::clamp)(screenPoint.x - tabScreenRect.left, 0L, tabScreenRect.right - tabScreenRect.left); - const LONG offsetY = - (std::clamp)(screenPoint.y - tabScreenRect.top, 0L, tabScreenRect.bottom - tabScreenRect.top); - return POINT { offsetX, offsetY }; - } - - const float dpiScale = windowState.hostRuntime.GetDpiScale(kBaseDpiScale); - return POINT { - static_cast(static_cast(kDetachedWindowDragOffsetXPixels) * dpiScale), - static_cast(static_cast(kDetachedWindowDragOffsetYPixels) * dpiScale) - }; -} - -void Application::MoveGlobalTabDragWindow( - ManagedWindowState& windowState, - const POINT& screenPoint) const { - if (windowState.hwnd == nullptr) { - return; - } - - RECT currentRect = {}; - if (!GetWindowRect(windowState.hwnd, ¤tRect)) { - return; - } - - const LONG width = currentRect.right - currentRect.left; - const LONG height = currentRect.bottom - currentRect.top; - const LONG left = screenPoint.x - m_globalTabDragSession.windowDragOffset.x; - const LONG top = screenPoint.y - m_globalTabDragSession.windowDragOffset.y; - SetWindowPos( - windowState.hwnd, - nullptr, - left, - top, - width, - height, - SWP_NOZORDER | SWP_NOACTIVATE); -} - -bool Application::TryStartGlobalTabDrag(ManagedWindowState& sourceWindowState) { - if (!sourceWindowState.pendingGlobalTabDragStart || - sourceWindowState.pendingGlobalTabDragNodeId.empty() || - sourceWindowState.pendingGlobalTabDragPanelId.empty()) { - return false; - } - - const std::string sourceNodeId = sourceWindowState.pendingGlobalTabDragNodeId; - const std::string panelId = sourceWindowState.pendingGlobalTabDragPanelId; - const POINT screenPoint = sourceWindowState.pendingGlobalTabDragScreenPoint; - const POINT windowDragOffset = sourceWindowState.pendingGlobalTabDragWindowOffset; - sourceWindowState.pendingGlobalTabDragStart = false; - sourceWindowState.pendingGlobalTabDragNodeId.clear(); - sourceWindowState.pendingGlobalTabDragPanelId.clear(); - sourceWindowState.pendingGlobalTabDragScreenPoint = {}; - sourceWindowState.pendingGlobalTabDragWindowOffset = {}; - - if (sourceWindowState.primary) { - UIEditorWindowWorkspaceController windowWorkspaceController( - m_editorContext.GetShellAsset().panelRegistry, - BuildWindowWorkspaceSet(sourceWindowState.windowId)); - const UIEditorWindowWorkspaceOperationResult result = - windowWorkspaceController.DetachPanelToNewWindow( - sourceWindowState.windowId, - sourceNodeId, - panelId); - if (result.status != UIEditorWindowWorkspaceOperationStatus::Changed) { - LogRuntimeTrace( - "drag", - "failed to start global tab drag from primary window: " + result.message); - return false; - } - - if (!SynchronizeManagedWindowsFromWindowSet( - windowWorkspaceController.GetWindowSet(), - result.targetWindowId, - screenPoint)) { - LogRuntimeTrace("drag", "failed to synchronize detached drag window state"); - return false; - } - - ManagedWindowState* detachedWindowState = FindWindowState(result.targetWindowId); - if (detachedWindowState == nullptr || detachedWindowState->hwnd == nullptr) { - LogRuntimeTrace("drag", "detached drag window was not created."); - return false; - } - - BeginGlobalTabDragSession( - detachedWindowState->windowId, - detachedWindowState->workspaceController.GetWorkspace().root.nodeId, - panelId, - screenPoint, - windowDragOffset); - MoveGlobalTabDragWindow(*detachedWindowState, screenPoint); - SetCapture(detachedWindowState->hwnd); - SetForegroundWindow(detachedWindowState->hwnd); - LogRuntimeTrace( - "drag", - "started global tab drag by detaching panel '" + panelId + - "' into window '" + detachedWindowState->windowId + "'"); - return true; - } - - ResetManagedWindowInteractionState(sourceWindowState); - BeginGlobalTabDragSession( - sourceWindowState.windowId, - sourceNodeId, - panelId, - screenPoint, - windowDragOffset); - MoveGlobalTabDragWindow(sourceWindowState, screenPoint); - if (sourceWindowState.hwnd != nullptr) { - SetCapture(sourceWindowState.hwnd); - } - LogRuntimeTrace( - "drag", - "started global tab drag from detached window '" + sourceWindowState.windowId + - "' panel '" + panelId + "'"); - return true; -} - -void Application::ProcessPendingGlobalTabDragStarts() { - if (m_globalTabDragSession.active) { - return; - } - - for (const std::unique_ptr& windowState : m_windows) { - if (windowState == nullptr || windowState->hwnd == nullptr) { - continue; - } - - if (TryStartGlobalTabDrag(*windowState)) { - return; - } - } -} - -bool Application::TryProcessDetachRequest(ManagedWindowState& sourceWindowState) { - if (!sourceWindowState.detachRequested || - sourceWindowState.detachedNodeId.empty() || - sourceWindowState.detachedPanelId.empty()) { - return false; - } - - const std::string sourceWindowId = sourceWindowState.windowId; - const std::string sourceNodeId = sourceWindowState.detachedNodeId; - const std::string panelId = sourceWindowState.detachedPanelId; - const POINT screenPoint = sourceWindowState.detachScreenPoint; - sourceWindowState.detachRequested = false; - sourceWindowState.detachedNodeId.clear(); - sourceWindowState.detachedPanelId.clear(); - sourceWindowState.detachScreenPoint = {}; - - UIEditorWindowWorkspaceController windowWorkspaceController( - m_editorContext.GetShellAsset().panelRegistry, - BuildWindowWorkspaceSet(sourceWindowId)); - const UIEditorWindowWorkspaceOperationResult result = - windowWorkspaceController.DetachPanelToNewWindow( - sourceWindowId, - sourceNodeId, - panelId); - if (result.status != UIEditorWindowWorkspaceOperationStatus::Changed) { - LogRuntimeTrace("detach", "detach request rejected: " + result.message); - return false; - } - - if (!SynchronizeManagedWindowsFromWindowSet( - windowWorkspaceController.GetWindowSet(), - result.targetWindowId, - screenPoint)) { - LogRuntimeTrace("detach", "failed to synchronize detached window state"); - return false; - } - - if (ManagedWindowState* detachedWindowState = FindWindowState(result.targetWindowId); - detachedWindowState != nullptr && - detachedWindowState->hwnd != nullptr) { - SetForegroundWindow(detachedWindowState->hwnd); - } - - LogRuntimeTrace( - "detach", - "detached panel '" + panelId + "' from window '" + sourceWindowId + - "' to window '" + result.targetWindowId + "'"); - return true; -} - -void Application::ProcessPendingDetachRequests() { - if (m_globalTabDragSession.active) { - return; - } - - std::vector windowsToProcess = {}; - for (const std::unique_ptr& windowState : m_windows) { - if (windowState != nullptr && windowState->hwnd != nullptr && windowState->detachRequested) { - windowsToProcess.push_back(windowState.get()); - } - } - - for (ManagedWindowState* windowState : windowsToProcess) { - if (windowState != nullptr) { - TryProcessDetachRequest(*windowState); - } - } -} - -bool Application::HandleGlobalTabDragPointerMove(HWND hwnd) { - if (!m_globalTabDragSession.active) { - return false; - } - - ManagedWindowState* ownerWindowState = FindWindowState(m_globalTabDragSession.panelWindowId); - if (ownerWindowState == nullptr || ownerWindowState->hwnd != hwnd) { - return false; - } - - POINT screenPoint = {}; - if (GetCursorPos(&screenPoint)) { - m_globalTabDragSession.screenPoint = screenPoint; - MoveGlobalTabDragWindow(*ownerWindowState, screenPoint); - } - return true; -} - -bool Application::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { - if (!m_globalTabDragSession.active) { - return false; - } - - const ManagedWindowState* ownerWindowState = FindWindowState(m_globalTabDragSession.panelWindowId); - if (ownerWindowState == nullptr || ownerWindowState->hwnd != hwnd) { - return false; - } - - POINT screenPoint = m_globalTabDragSession.screenPoint; - GetCursorPos(&screenPoint); - - const std::string panelWindowId = m_globalTabDragSession.panelWindowId; - const std::string sourceNodeId = m_globalTabDragSession.sourceNodeId; - const std::string panelId = m_globalTabDragSession.panelId; - EndGlobalTabDragSession(); - - ManagedWindowState* targetWindowState = - FindTopmostWindowStateAtScreenPoint(screenPoint, panelWindowId); - if (targetWindowState == nullptr || targetWindowState->hwnd == nullptr) { - return true; - } - - const UIPoint targetPoint = - ConvertScreenPixelsToClientDips(*targetWindowState, screenPoint); - const Widgets::UIEditorDockHostLayout& targetLayout = - targetWindowState->editorWorkspace - .GetShellFrame() - .workspaceInteractionFrame - .dockHostFrame - .layout; - CrossWindowDockDropTarget dropTarget = {}; - if (!TryResolveCrossWindowDockDropTarget(targetLayout, targetPoint, dropTarget)) { - return true; - } - - UIEditorWindowWorkspaceController windowWorkspaceController( - m_editorContext.GetShellAsset().panelRegistry, - BuildWindowWorkspaceSet(targetWindowState->windowId)); - const UIEditorWindowWorkspaceOperationResult result = - dropTarget.placement == UIEditorWorkspaceDockPlacement::Center - ? windowWorkspaceController.MovePanelToStack( - panelWindowId, - sourceNodeId, - panelId, - targetWindowState->windowId, - dropTarget.nodeId, - dropTarget.insertionIndex) - : windowWorkspaceController.DockPanelRelative( - panelWindowId, - sourceNodeId, - panelId, - targetWindowState->windowId, - dropTarget.nodeId, - dropTarget.placement); - if (result.status != UIEditorWindowWorkspaceOperationStatus::Changed) { - LogRuntimeTrace("drag", "cross-window drop rejected: " + result.message); - return true; - } - - if (!SynchronizeManagedWindowsFromWindowSet( - windowWorkspaceController.GetWindowSet(), - {}, - screenPoint)) { - LogRuntimeTrace("drag", "failed to synchronize windows after cross-window drop"); - return true; - } - - if (targetWindowState->hwnd != nullptr) { - SetForegroundWindow(targetWindowState->hwnd); - } - LogRuntimeTrace( - "drag", - "committed cross-window drop panel '" + panelId + - "' into window '" + targetWindowState->windowId + "'"); - return true; -} - -void Application::AppendGlobalTabDragDropPreview(UIDrawList& drawList) const { - if (!m_globalTabDragSession.active || m_currentWindowState == nullptr) { - return; - } - - if (m_currentWindowState->windowId == m_globalTabDragSession.panelWindowId) { - return; - } - - const ManagedWindowState* targetWindowState = FindTopmostWindowStateAtScreenPoint( - m_globalTabDragSession.screenPoint, - m_globalTabDragSession.panelWindowId); - if (targetWindowState == nullptr || targetWindowState != m_currentWindowState) { - return; - } - - const UIPoint targetPoint = - ConvertScreenPixelsToClientDips(*targetWindowState, m_globalTabDragSession.screenPoint); - const Widgets::UIEditorDockHostLayout& targetLayout = - targetWindowState->editorWorkspace - .GetShellFrame() - .workspaceInteractionFrame - .dockHostFrame - .layout; - CrossWindowDockDropTarget dropTarget = {}; - if (!TryResolveCrossWindowDockDropTarget(targetLayout, targetPoint, dropTarget)) { - return; - } - - Widgets::UIEditorDockHostDropPreviewState previewState = {}; - previewState.visible = true; - previewState.sourceNodeId = m_globalTabDragSession.sourceNodeId; - previewState.sourcePanelId = m_globalTabDragSession.panelId; - previewState.targetNodeId = dropTarget.nodeId; - previewState.placement = dropTarget.placement; - previewState.insertionIndex = dropTarget.insertionIndex; - const Widgets::UIEditorDockHostDropPreviewLayout previewLayout = - Widgets::ResolveUIEditorDockHostDropPreviewLayout(targetLayout, previewState); - if (!previewLayout.visible) { - return; - } - - const Widgets::UIEditorDockHostPalette& dockPalette = - ResolveUIEditorShellInteractionPalette().shellPalette.dockHostPalette; - drawList.AddFilledRect( - previewLayout.previewRect, - dockPalette.dropPreviewFillColor); - drawList.AddRectOutline( - previewLayout.previewRect, - dockPalette.dropPreviewBorderColor, - 1.0f); -} - -void Application::HandleDestroyedWindow(HWND hwnd) { - if (ManagedWindowState* windowState = FindWindowState(hwnd); windowState != nullptr) { - windowState->hwnd = nullptr; - if (windowState->primary) { - m_shutdownRequested = true; - for (const std::unique_ptr& otherWindowState : m_windows) { - if (otherWindowState != nullptr && - otherWindowState.get() != windowState && - otherWindowState->hwnd != nullptr) { - PostMessageW(otherWindowState->hwnd, WM_CLOSE, 0, 0); - } - } - } - } -} - -void Application::Shutdown() { - LogRuntimeTrace("app", "shutdown begin"); - m_shutdownRequested = true; - for (const std::unique_ptr& windowState : m_windows) { - if (windowState == nullptr) { - continue; - } - - ManagedWindowState* const previousWindowState = m_currentWindowState; - m_currentWindowState = windowState.get(); - DestroyManagedWindow(*windowState); - m_currentWindowState = previousWindowState; - } - m_windows.clear(); - m_currentWindowState = nullptr; - m_pendingCreateWindowState = nullptr; - - if (m_windowClassAtom != 0 && m_hInstance != nullptr) { - UnregisterClassW(kWindowClassName, m_hInstance); - m_windowClassAtom = 0; - } - - LogRuntimeTrace("app", "shutdown end"); - ShutdownUIEditorRuntimeTrace(); -} - -void Application::RenderFrame() { - if (!m_renderReady || m_hwnd == nullptr) { - return; - } - - UINT pixelWidth = 0u; - UINT pixelHeight = 0u; - if (!ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) { - return; - } - const float width = PixelsToDips(static_cast(pixelWidth)); - const float height = PixelsToDips(static_cast(pixelHeight)); - const UIRect workspaceBounds = ResolveWorkspaceBounds(width, height); - - UIDrawData drawData = {}; - UIDrawList& drawList = drawData.EmplaceDrawList("XCEditorShell"); - drawList.AddFilledRect( - UIRect(0.0f, 0.0f, width, height), - kShellSurfaceColor); - - if (m_editorContext.IsValid()) { - std::vector frameEvents = std::move(m_pendingInputEvents); - m_pendingInputEvents.clear(); - if (!frameEvents.empty() && IsVerboseRuntimeTraceEnabled()) { - LogRuntimeTrace( - "input", - DescribeInputEvents(frameEvents) + " | " + - m_editorContext.DescribeWorkspaceState( - m_workspaceController, - m_editorWorkspace.GetShellInteractionState())); - } - - const Host::D3D12WindowRenderLoopFrameContext frameContext = - m_windowRenderLoop.BeginFrame(); - if (!frameContext.warning.empty()) { - LogRuntimeTrace("viewport", frameContext.warning); - } - - m_editorContext.AttachTextMeasurer(m_renderer); - m_editorWorkspace.Update( - m_editorContext, - m_workspaceController, - workspaceBounds, - frameEvents, - BuildCaptureStatusText(), - RequireCurrentWindowState().primary - ? App::ProductEditorShellVariant::Primary - : App::ProductEditorShellVariant::DetachedWindow); - const UIEditorShellInteractionFrame& shellFrame = - m_editorWorkspace.GetShellFrame(); - const UIEditorDockHostInteractionState& dockHostInteractionState = - m_editorWorkspace - .GetShellInteractionState() - .workspaceInteractionState - .dockHostInteractionState; - if (IsVerboseRuntimeTraceEnabled() && - (!frameEvents.empty() || - shellFrame.result.workspaceResult.dockHostResult.layoutChanged || - shellFrame.result.workspaceResult.dockHostResult.commandExecuted)) { - std::ostringstream frameTrace = {}; - frameTrace << "result consumed=" - << (shellFrame.result.consumed ? "true" : "false") - << " layoutChanged=" - << (shellFrame.result.workspaceResult.dockHostResult.layoutChanged ? "true" : "false") - << " commandExecuted=" - << (shellFrame.result.workspaceResult.dockHostResult.commandExecuted ? "true" : "false") - << " active=" - << m_workspaceController.GetWorkspace().activePanelId - << " message=" - << shellFrame.result.workspaceResult.dockHostResult.layoutResult.message; - LogRuntimeTrace( - "frame", - frameTrace.str()); - } - if (!m_globalTabDragSession.active && - !dockHostInteractionState.activeTabDragNodeId.empty() && - !dockHostInteractionState.activeTabDragPanelId.empty()) { - POINT screenPoint = {}; - GetCursorPos(&screenPoint); - RequireCurrentWindowState().pendingGlobalTabDragStart = true; - RequireCurrentWindowState().pendingGlobalTabDragNodeId = - dockHostInteractionState.activeTabDragNodeId; - RequireCurrentWindowState().pendingGlobalTabDragPanelId = - dockHostInteractionState.activeTabDragPanelId; - RequireCurrentWindowState().pendingGlobalTabDragScreenPoint = screenPoint; - RequireCurrentWindowState().pendingGlobalTabDragWindowOffset = - ResolveGlobalTabDragWindowOffset( - RequireCurrentWindowState(), - dockHostInteractionState.activeTabDragNodeId, - dockHostInteractionState.activeTabDragPanelId, - screenPoint); - } - if (shellFrame.result.workspaceResult.dockHostResult.detachRequested) { - POINT screenPoint = {}; - GetCursorPos(&screenPoint); - RequireCurrentWindowState().detachRequested = true; - RequireCurrentWindowState().detachedNodeId = - shellFrame.result.workspaceResult.dockHostResult.detachedNodeId; - RequireCurrentWindowState().detachedPanelId = - shellFrame.result.workspaceResult.dockHostResult.detachedPanelId; - RequireCurrentWindowState().detachScreenPoint = screenPoint; - } - ApplyHostCaptureRequests(shellFrame.result); - for (const App::ProductEditorWorkspaceTraceEntry& entry : m_editorWorkspace.GetTraceEntries()) { - LogRuntimeTrace(entry.channel, entry.message); - } - ApplyHostedContentCaptureRequests(); - ApplyCurrentCursor(); - m_editorWorkspace.Append(drawList); - AppendGlobalTabDragDropPreview(drawList); - if (frameContext.canRenderViewports) { - m_editorWorkspace.RenderRequestedViewports(frameContext.renderContext); - } - } else { - drawList.AddText( - UIPoint(28.0f, 28.0f), - "Editor shell asset invalid.", - kShellTextColor, - 16.0f); - drawList.AddText( - UIPoint(28.0f, 54.0f), - m_editorContext.GetValidationMessage().empty() - ? std::string("Unknown validation error.") - : m_editorContext.GetValidationMessage(), - kShellMutedTextColor, - 12.0f); - } - - AppendBorderlessWindowChrome(drawList, width); - - const Host::D3D12WindowRenderLoopPresentResult presentResult = - m_windowRenderLoop.Present(drawData); - if (!presentResult.warning.empty()) { - LogRuntimeTrace("present", presentResult.warning); - } - m_autoScreenshot.CaptureIfRequested( - m_renderer, - drawData, - pixelWidth, - pixelHeight, - presentResult.framePresented); -} - -void Application::OnPaintMessage() { - if (!m_renderReady || m_hwnd == nullptr) { - return; - } - - PAINTSTRUCT paintStruct = {}; - BeginPaint(m_hwnd, &paintStruct); - RenderFrame(); - EndPaint(m_hwnd, &paintStruct); -} - -bool Application::IsBorderlessWindowEnabled() const { - return true; -} - -bool Application::HasBorderlessWindowChrome() const { - return m_currentWindowState != nullptr && m_currentWindowState->primary; -} - -bool Application::IsBorderlessWindowMaximized() const { - return m_hostRuntime.IsBorderlessWindowMaximized(); -} - -bool Application::HandleBorderlessWindowSystemCommand(WPARAM wParam) { - if (!IsBorderlessWindowEnabled()) { - return false; - } - - switch (wParam & 0xFFF0u) { - case SC_MAXIMIZE: - ToggleBorderlessWindowMaximizeRestore(); - return true; - case SC_RESTORE: - if (!IsIconic(m_hwnd)) { - ToggleBorderlessWindowMaximizeRestore(); - return true; - } - return false; - default: - return false; - } -} - -bool Application::HandleBorderlessWindowGetMinMaxInfo(LPARAM lParam) const { - return Host::HandleBorderlessWindowGetMinMaxInfo(m_hwnd, lParam); -} - -LRESULT Application::HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const { - return Host::HandleBorderlessWindowNcCalcSize( - m_hwnd, - wParam, - lParam, - m_hostRuntime.GetWindowDpi()); -} - -float Application::GetDpiScale() const { - return m_hostRuntime.GetDpiScale(kBaseDpiScale); -} - -float Application::PixelsToDips(float pixels) const { - const float dpiScale = GetDpiScale(); - return dpiScale > 0.0f ? pixels / dpiScale : pixels; -} - -bool Application::IsPointerInsideClientArea() const { - if (m_hwnd == nullptr || !IsWindow(m_hwnd)) { - return false; - } - - POINT screenPoint = {}; - if (!GetCursorPos(&screenPoint)) { - return false; - } - - const LPARAM pointParam = MAKELPARAM( - static_cast(screenPoint.x), - static_cast(screenPoint.y)); - return SendMessageW(m_hwnd, WM_NCHITTEST, 0, pointParam) == HTCLIENT; -} - -LPCWSTR Application::ResolveCurrentCursorResource() const { - const Host::BorderlessWindowResizeEdge borderlessResizeEdge = - m_hostRuntime.IsBorderlessResizeActive() - ? m_hostRuntime.GetBorderlessResizeEdge() - : m_hostRuntime.GetHoveredBorderlessResizeEdge(); - if (borderlessResizeEdge != Host::BorderlessWindowResizeEdge::None) { - return Host::ResolveBorderlessWindowResizeCursor(borderlessResizeEdge); - } - - switch (m_editorWorkspace.GetHostedContentCursorKind()) { - case App::ProductProjectPanel::CursorKind::ResizeEW: - return IDC_SIZEWE; - case App::ProductProjectPanel::CursorKind::Arrow: - default: - break; - } - - switch (m_editorWorkspace.GetDockCursorKind()) { - case Widgets::UIEditorDockHostCursorKind::ResizeEW: - return IDC_SIZEWE; - case Widgets::UIEditorDockHostCursorKind::ResizeNS: - return IDC_SIZENS; - case Widgets::UIEditorDockHostCursorKind::Arrow: - default: - return IDC_ARROW; - } -} - -bool Application::ApplyCurrentCursor() const { - if (!HasInteractiveCaptureState() && !IsPointerInsideClientArea()) { - return false; - } - - const HCURSOR cursor = LoadCursorW(nullptr, ResolveCurrentCursorResource()); - if (cursor == nullptr) { - return false; - } - - SetCursor(cursor); - return true; -} - -Host::BorderlessWindowResizeEdge Application::HitTestBorderlessWindowResizeEdge(LPARAM lParam) const { - if (!IsBorderlessWindowEnabled() || m_hwnd == nullptr || IsBorderlessWindowMaximized()) { - return Host::BorderlessWindowResizeEdge::None; - } - - RECT clientRect = {}; - if (!GetClientRect(m_hwnd, &clientRect)) { - return Host::BorderlessWindowResizeEdge::None; - } - - const float clientWidthDips = - PixelsToDips(static_cast((std::max)(clientRect.right - clientRect.left, 1L))); - const float clientHeightDips = - PixelsToDips(static_cast((std::max)(clientRect.bottom - clientRect.top, 1L))); - return Host::HitTestBorderlessWindowResizeEdge( - ::XCEngine::UI::UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips), - ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); -} - -bool Application::UpdateBorderlessWindowResizeHover(LPARAM lParam) { - const Host::BorderlessWindowResizeEdge hoveredEdge = HitTestBorderlessWindowResizeEdge(lParam); - if (m_hostRuntime.GetHoveredBorderlessResizeEdge() == hoveredEdge) { - return false; - } - - m_hostRuntime.SetHoveredBorderlessResizeEdge(hoveredEdge); - ApplyBorderlessWindowResizeCursorHoverPriority(); - return true; -} - -bool Application::HandleBorderlessWindowResizeButtonDown(LPARAM lParam) { - const Host::BorderlessWindowResizeEdge edge = HitTestBorderlessWindowResizeEdge(lParam); - if (edge == Host::BorderlessWindowResizeEdge::None || m_hwnd == nullptr) { - return false; - } - - POINT screenPoint = {}; - if (!GetCursorPos(&screenPoint)) { - return false; - } - - RECT windowRect = {}; - if (!GetWindowRect(m_hwnd, &windowRect)) { - return false; - } - - m_hostRuntime.BeginBorderlessResize(edge, screenPoint, windowRect); - SetCapture(m_hwnd); - InvalidateHostWindow(); - return true; -} - -bool Application::HandleBorderlessWindowResizeButtonUp() { - if (!m_hostRuntime.IsBorderlessResizeActive()) { - return false; - } - - m_hostRuntime.EndBorderlessResize(); - if (GetCapture() == m_hwnd) { - ReleaseCapture(); - } - InvalidateHostWindow(); - return true; -} - -bool Application::HandleBorderlessWindowResizePointerMove() { - if (!m_hostRuntime.IsBorderlessResizeActive() || m_hwnd == nullptr) { - return false; - } - - POINT currentScreenPoint = {}; - if (!GetCursorPos(¤tScreenPoint)) { - return false; - } - - RECT targetRect = Host::ComputeBorderlessWindowResizeRect( - m_hostRuntime.GetBorderlessResizeInitialWindowRect(), - m_hostRuntime.GetBorderlessResizeInitialScreenPoint(), - currentScreenPoint, - m_hostRuntime.GetBorderlessResizeEdge(), - 640, - 360); - const int width = targetRect.right - targetRect.left; - const int height = targetRect.bottom - targetRect.top; - if (width <= 0 || height <= 0) { - return true; - } - - m_hostRuntime.SetPredictedClientPixelSize( - static_cast(width), - static_cast(height)); - ApplyWindowResize( - static_cast(width), - static_cast(height)); - RenderFrame(); - - SetWindowPos( - m_hwnd, - nullptr, - targetRect.left, - targetRect.top, - width, - height, - SWP_NOZORDER | SWP_NOACTIVATE); - return true; -} - -void Application::ClearBorderlessWindowResizeState() { - if (m_hostRuntime.IsBorderlessResizeActive()) { - return; - } - - if (m_hostRuntime.GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None) { - return; - } - - m_hostRuntime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); - InvalidateHostWindow(); -} - -void Application::ForceClearBorderlessWindowResizeState() { - if (m_hostRuntime.GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None && - !m_hostRuntime.IsBorderlessResizeActive()) { - return; - } - - m_hostRuntime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); - m_hostRuntime.EndBorderlessResize(); - if (GetCapture() == m_hwnd) { - ReleaseCapture(); - } - InvalidateHostWindow(); -} - -void Application::ApplyBorderlessWindowResizeCursorHoverPriority() { - if (m_hostRuntime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None || - m_hostRuntime.IsBorderlessResizeActive()) { - m_borderlessWindowChromeState.hoveredTarget = Host::BorderlessWindowChromeHitTarget::None; - } -} - -Host::BorderlessWindowChromeLayout Application::ResolveBorderlessWindowChromeLayout( - float clientWidthDips) const { - return Host::BuildBorderlessWindowChromeLayout( - ::XCEngine::UI::UIRect(0.0f, 0.0f, clientWidthDips, kBorderlessTitleBarHeightDips), - 0.0f); -} - -Host::BorderlessWindowChromeHitTarget Application::HitTestBorderlessWindowChrome(LPARAM lParam) const { - if (!HasBorderlessWindowChrome() || m_hwnd == nullptr) { - return Host::BorderlessWindowChromeHitTarget::None; - } - - RECT clientRect = {}; - if (!GetClientRect(m_hwnd, &clientRect)) { - return Host::BorderlessWindowChromeHitTarget::None; - } - - const float clientWidthDips = PixelsToDips( - static_cast((std::max)(clientRect.right - clientRect.left, 1L))); - const Host::BorderlessWindowChromeLayout layout = - ResolveBorderlessWindowChromeLayout(clientWidthDips); - return Host::HitTestBorderlessWindowChrome( - layout, - ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); -} - -bool Application::UpdateBorderlessWindowChromeHover(LPARAM lParam) { - if (m_hostRuntime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None || - m_hostRuntime.IsBorderlessResizeActive()) { - const bool changed = - m_borderlessWindowChromeState.hoveredTarget != Host::BorderlessWindowChromeHitTarget::None; - m_borderlessWindowChromeState.hoveredTarget = Host::BorderlessWindowChromeHitTarget::None; - return changed; - } - - const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam); - const Host::BorderlessWindowChromeHitTarget buttonTarget = - hitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || - hitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || - hitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton - ? hitTarget - : Host::BorderlessWindowChromeHitTarget::None; - if (m_borderlessWindowChromeState.hoveredTarget == buttonTarget) { - return false; - } - - m_borderlessWindowChromeState.hoveredTarget = buttonTarget; - return true; -} - -bool Application::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) { - if (!HasBorderlessWindowChrome()) { - return false; - } - - if (m_hostRuntime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None || - m_hostRuntime.IsBorderlessResizeActive()) { - return false; - } - - const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestBorderlessWindowChrome(lParam); - switch (hitTarget) { - case Host::BorderlessWindowChromeHitTarget::MinimizeButton: - case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: - case Host::BorderlessWindowChromeHitTarget::CloseButton: - m_borderlessWindowChromeState.pressedTarget = hitTarget; - if (m_hwnd != nullptr) { - SetCapture(m_hwnd); - } - InvalidateHostWindow(); - return true; - case Host::BorderlessWindowChromeHitTarget::DragRegion: - if (m_hwnd != nullptr) { - if (IsBorderlessWindowMaximized()) { - POINT screenPoint = {}; - if (GetCursorPos(&screenPoint)) { - m_hostRuntime.BeginBorderlessWindowDragRestore(screenPoint); - SetCapture(m_hwnd); - return true; - } - } - - ReleaseCapture(); - SendMessageW(m_hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); - } - return true; - case Host::BorderlessWindowChromeHitTarget::None: - default: - return false; - } -} - -bool Application::HandleBorderlessWindowChromeButtonUp(LPARAM lParam) { - if (!HasBorderlessWindowChrome()) { - return false; - } - - if (m_hostRuntime.IsBorderlessWindowDragRestoreArmed()) { - ClearBorderlessWindowChromeDragRestoreState(); - return true; - } - - const Host::BorderlessWindowChromeHitTarget pressedTarget = - m_borderlessWindowChromeState.pressedTarget; - if (pressedTarget != Host::BorderlessWindowChromeHitTarget::MinimizeButton && - pressedTarget != Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton && - pressedTarget != Host::BorderlessWindowChromeHitTarget::CloseButton) { - return false; - } - - const Host::BorderlessWindowChromeHitTarget releasedTarget = - HitTestBorderlessWindowChrome(lParam); - m_borderlessWindowChromeState.pressedTarget = Host::BorderlessWindowChromeHitTarget::None; - if (GetCapture() == m_hwnd) { - ReleaseCapture(); - } - InvalidateHostWindow(); - - if (pressedTarget == releasedTarget) { - ExecuteBorderlessWindowChromeAction(pressedTarget); - } - return true; -} - -bool Application::HandleBorderlessWindowChromeDoubleClick(LPARAM lParam) { - if (!HasBorderlessWindowChrome()) { - return false; - } - - if (m_hostRuntime.IsBorderlessWindowDragRestoreArmed()) { - ClearBorderlessWindowChromeDragRestoreState(); - } - - if (HitTestBorderlessWindowChrome(lParam) != Host::BorderlessWindowChromeHitTarget::DragRegion) { - return false; - } - - ExecuteBorderlessWindowChromeAction(Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton); - return true; -} - -bool Application::HandleBorderlessWindowChromeDragRestorePointerMove() { - if (!HasBorderlessWindowChrome()) { - return false; - } - - if (!m_hostRuntime.IsBorderlessWindowDragRestoreArmed() || m_hwnd == nullptr) { - return false; - } - - POINT currentScreenPoint = {}; - if (!GetCursorPos(¤tScreenPoint)) { - return true; - } - - const POINT initialScreenPoint = m_hostRuntime.GetBorderlessWindowDragRestoreInitialScreenPoint(); - const int dragThresholdX = (std::max)(GetSystemMetrics(SM_CXDRAG), 1); - const int dragThresholdY = (std::max)(GetSystemMetrics(SM_CYDRAG), 1); - const LONG deltaX = currentScreenPoint.x - initialScreenPoint.x; - const LONG deltaY = currentScreenPoint.y - initialScreenPoint.y; - if (std::abs(deltaX) < dragThresholdX && std::abs(deltaY) < dragThresholdY) { - return true; - } - - RECT restoreRect = {}; - RECT currentRect = {}; - RECT workAreaRect = {}; - if (!m_hostRuntime.TryGetBorderlessWindowRestoreRect(restoreRect) || - !QueryCurrentWindowRect(currentRect) || - !QueryBorderlessWindowWorkAreaRect(workAreaRect)) { - ClearBorderlessWindowChromeDragRestoreState(); - return true; - } - - const int restoreWidth = restoreRect.right - restoreRect.left; - const int restoreHeight = restoreRect.bottom - restoreRect.top; - const int currentWidth = currentRect.right - currentRect.left; - if (restoreWidth <= 0 || restoreHeight <= 0 || currentWidth <= 0) { - ClearBorderlessWindowChromeDragRestoreState(); - return true; - } - - const float pointerRatio = - static_cast(currentScreenPoint.x - currentRect.left) / - static_cast(currentWidth); - const float clampedPointerRatio = (std::clamp)(pointerRatio, 0.0f, 1.0f); - const int newLeft = - (std::clamp)( - currentScreenPoint.x - static_cast(clampedPointerRatio * static_cast(restoreWidth)), - workAreaRect.left, - workAreaRect.right - restoreWidth); - const int titleBarHeightPixels = - static_cast(kBorderlessTitleBarHeightDips * GetDpiScale()); - const int newTop = - (std::clamp)( - currentScreenPoint.y - (std::max)(titleBarHeightPixels / 2, 1), - workAreaRect.top, - workAreaRect.bottom - restoreHeight); - const RECT targetRect = { - newLeft, - newTop, - newLeft + restoreWidth, - newTop + restoreHeight - }; - - m_hostRuntime.SetBorderlessWindowMaximized(false); - ApplyPredictedWindowRectTransition(targetRect); - ClearBorderlessWindowChromeDragRestoreState(); - ReleaseCapture(); - SendMessageW(m_hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); - return true; -} - -void Application::ClearBorderlessWindowChromeDragRestoreState() { - if (!m_hostRuntime.IsBorderlessWindowDragRestoreArmed()) { - return; - } - - m_hostRuntime.EndBorderlessWindowDragRestore(); - if (GetCapture() == m_hwnd) { - ReleaseCapture(); - } -} - -void Application::ClearBorderlessWindowChromeState() { - if (m_borderlessWindowChromeState.hoveredTarget == Host::BorderlessWindowChromeHitTarget::None && - m_borderlessWindowChromeState.pressedTarget == Host::BorderlessWindowChromeHitTarget::None) { - return; - } - - m_borderlessWindowChromeState = {}; - InvalidateHostWindow(); -} - -void Application::AppendBorderlessWindowChrome( - ::XCEngine::UI::UIDrawList& drawList, - float clientWidthDips) const { - if (!HasBorderlessWindowChrome()) { - return; - } - - const Host::BorderlessWindowChromeLayout layout = - ResolveBorderlessWindowChromeLayout(clientWidthDips); - const float iconExtent = 16.0f; - const float iconInsetLeft = 8.0f; - const float iconTextGap = 8.0f; - const float iconX = layout.titleBarRect.x + iconInsetLeft; - const float iconY = - layout.titleBarRect.y + - (std::max)(0.0f, (layout.titleBarRect.height - iconExtent) * 0.5f); - drawList.AddFilledRect( - layout.titleBarRect, - kShellSurfaceColor); - drawList.AddLine( - UIPoint(layout.titleBarRect.x, layout.titleBarRect.y + layout.titleBarRect.height), - UIPoint( - layout.titleBarRect.x + layout.titleBarRect.width, - layout.titleBarRect.y + layout.titleBarRect.height), - kShellBorderColor, - 1.0f); - if (m_titleBarLogoIcon.IsValid()) { - drawList.AddImage( - UIRect(iconX, iconY, iconExtent, iconExtent), - m_titleBarLogoIcon, - UIColor(1.0f, 1.0f, 1.0f, 1.0f)); - } - drawList.AddText( - UIPoint( - iconX + (m_titleBarLogoIcon.IsValid() ? (iconExtent + iconTextGap) : 4.0f), - layout.titleBarRect.y + - (std::max)(0.0f, (layout.titleBarRect.height - kBorderlessTitleBarFontSize) * 0.5f - 1.0f)), - kWindowTitleText, - kShellTextColor, - kBorderlessTitleBarFontSize); - Host::AppendBorderlessWindowChrome( - drawList, - layout, - m_borderlessWindowChromeState, - IsBorderlessWindowMaximized()); -} - -void Application::ExecuteBorderlessWindowChromeAction( - Host::BorderlessWindowChromeHitTarget target) { - if (m_hwnd == nullptr) { - return; - } - - switch (target) { - case Host::BorderlessWindowChromeHitTarget::MinimizeButton: - ShowWindow(m_hwnd, SW_MINIMIZE); - break; - case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: - ToggleBorderlessWindowMaximizeRestore(); - break; - case Host::BorderlessWindowChromeHitTarget::CloseButton: - PostMessageW(m_hwnd, WM_CLOSE, 0, 0); - break; - case Host::BorderlessWindowChromeHitTarget::DragRegion: - case Host::BorderlessWindowChromeHitTarget::None: - default: - break; - } - - InvalidateHostWindow(); -} - -bool Application::QueryCurrentWindowRect(RECT& outRect) const { - outRect = {}; - return m_hwnd != nullptr && GetWindowRect(m_hwnd, &outRect) != FALSE; -} - -bool Application::QueryBorderlessWindowWorkAreaRect(RECT& outRect) const { - outRect = {}; - if (m_hwnd == nullptr) { - return false; - } - - const HMONITOR monitor = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST); - if (monitor == nullptr) { - return false; - } - - MONITORINFO monitorInfo = {}; - monitorInfo.cbSize = sizeof(monitorInfo); - if (!GetMonitorInfoW(monitor, &monitorInfo)) { - return false; - } - - outRect = monitorInfo.rcWork; - return true; -} - -bool Application::ApplyPredictedWindowRectTransition(const RECT& targetRect) { - if (m_hwnd == nullptr) { - return false; - } - - const int width = targetRect.right - targetRect.left; - const int height = targetRect.bottom - targetRect.top; - if (width <= 0 || height <= 0) { - return false; - } - - m_hostRuntime.SetPredictedClientPixelSize( - static_cast(width), - static_cast(height)); - ApplyWindowResize( - static_cast(width), - static_cast(height)); - RenderFrame(); - SetWindowPos( - m_hwnd, - nullptr, - targetRect.left, - targetRect.top, - width, - height, - SWP_NOZORDER | SWP_NOACTIVATE); - InvalidateHostWindow(); - return true; -} - -void Application::ToggleBorderlessWindowMaximizeRestore() { - if (m_hwnd == nullptr) { - return; - } - - if (!IsBorderlessWindowMaximized()) { - RECT currentRect = {}; - RECT workAreaRect = {}; - if (!QueryCurrentWindowRect(currentRect) || !QueryBorderlessWindowWorkAreaRect(workAreaRect)) { - return; - } - - m_hostRuntime.SetBorderlessWindowRestoreRect(currentRect); - m_hostRuntime.SetBorderlessWindowMaximized(true); - ApplyPredictedWindowRectTransition(workAreaRect); - return; - } - - RECT restoreRect = {}; - if (!m_hostRuntime.TryGetBorderlessWindowRestoreRect(restoreRect)) { - return; - } - - m_hostRuntime.SetBorderlessWindowMaximized(false); - ApplyPredictedWindowRectTransition(restoreRect); -} - -void Application::InvalidateHostWindow() const { - if (m_hwnd != nullptr && IsWindow(m_hwnd)) { - InvalidateRect(m_hwnd, nullptr, FALSE); - } -} - -UIPoint Application::ConvertClientPixelsToDips(LONG x, LONG y) const { - return UIPoint( - PixelsToDips(static_cast(x)), - PixelsToDips(static_cast(y))); -} - -UIPoint Application::ConvertScreenPixelsToClientDips( - const ManagedWindowState& windowState, - const POINT& screenPoint) const { - POINT clientPoint = screenPoint; - if (windowState.hwnd != nullptr) { - ScreenToClient(windowState.hwnd, &clientPoint); - } - - const float dpiScale = windowState.hostRuntime.GetDpiScale(kBaseDpiScale); - return UIPoint( - dpiScale > 0.0f ? static_cast(clientPoint.x) / dpiScale : static_cast(clientPoint.x), - dpiScale > 0.0f ? static_cast(clientPoint.y) / dpiScale : static_cast(clientPoint.y)); -} - -std::string Application::BuildCaptureStatusText() const { - if (m_autoScreenshot.HasPendingCapture()) { - return "Shot pending..."; - } - - if (!m_autoScreenshot.GetLastCaptureError().empty()) { - return TruncateText(m_autoScreenshot.GetLastCaptureError(), 38u); - } - - if (!m_autoScreenshot.GetLastCaptureSummary().empty()) { - return TruncateText(m_autoScreenshot.GetLastCaptureSummary(), 38u); - } - - return {}; -} - -void Application::LogRuntimeTrace( - std::string_view channel, - std::string_view message) const { - AppendUIEditorRuntimeTrace(channel, message); -} - -bool Application::IsVerboseRuntimeTraceEnabled() { - static const bool s_enabled = ResolveVerboseRuntimeTraceEnabled(); - return s_enabled; -} - -std::string Application::DescribeInputEvents( - const std::vector& events) const { - std::ostringstream stream = {}; - stream << "events=["; - for (std::size_t index = 0; index < events.size(); ++index) { - if (index > 0u) { - stream << " | "; - } - const UIInputEvent& event = events[index]; - stream << DescribeInputEventType(event) - << '@' - << static_cast(event.position.x) - << ',' - << static_cast(event.position.y); - } - stream << ']'; - return stream.str(); -} - -void Application::OnResize(UINT width, UINT height) { - bool matchesPredictedClientSize = false; - UINT predictedWidth = 0u; - UINT predictedHeight = 0u; - if (m_hostRuntime.TryGetPredictedClientPixelSize(predictedWidth, predictedHeight)) { - matchesPredictedClientSize = - predictedWidth == width && - predictedHeight == height; - } - - m_hostRuntime.ClearPredictedClientPixelSize(); - if (IsBorderlessWindowEnabled() && m_hwnd != nullptr) { - Host::RefreshBorderlessWindowDwmDecorations(m_hwnd); - } - - if (!matchesPredictedClientSize) { - ApplyWindowResize(width, height); - } -} - -void Application::OnEnterSizeMove() { - m_hostRuntime.BeginInteractiveResize(); -} - -void Application::OnExitSizeMove() { - m_hostRuntime.EndInteractiveResize(); - m_hostRuntime.ClearPredictedClientPixelSize(); - UINT width = 0u; - UINT height = 0u; - if (QueryCurrentClientPixelSize(width, height)) { - ApplyWindowResize(width, height); - } -} - -bool Application::ApplyWindowResize(UINT width, UINT height) { - if (!m_renderReady || width == 0u || height == 0u) { - return false; - } - - const Host::D3D12WindowRenderLoopResizeResult resizeResult = - m_windowRenderLoop.ApplyResize(width, height); - m_editorWorkspace.SetViewportSurfacePresentationEnabled( - resizeResult.hasViewportSurfacePresentation); - - if (!resizeResult.windowRendererWarning.empty()) { - LogRuntimeTrace("present", resizeResult.windowRendererWarning); - } - - if (!resizeResult.interopWarning.empty()) { - LogRuntimeTrace("present", resizeResult.interopWarning); - } - - return resizeResult.hasViewportSurfacePresentation; -} - -bool Application::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const { - outWidth = 0u; - outHeight = 0u; - if (m_hwnd == nullptr || !IsWindow(m_hwnd)) { - return false; - } - - RECT clientRect = {}; - if (!GetClientRect(m_hwnd, &clientRect)) { - return false; - } - - const LONG width = clientRect.right - clientRect.left; - const LONG height = clientRect.bottom - clientRect.top; - if (width <= 0 || height <= 0) { - return false; - } - - outWidth = static_cast(width); - outHeight = static_cast(height); - return true; -} - -bool Application::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const { - if (m_hostRuntime.TryGetPredictedClientPixelSize(outWidth, outHeight)) { - return true; - } - - return QueryCurrentClientPixelSize(outWidth, outHeight); -} - -UIRect Application::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const { - if (!HasBorderlessWindowChrome()) { - return UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips); - } - - const float titleBarHeight = (std::min)(kBorderlessTitleBarHeightDips, clientHeightDips); - return UIRect( - 0.0f, - titleBarHeight, - clientWidthDips, - (std::max)(0.0f, clientHeightDips - titleBarHeight)); -} - -void Application::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { - m_hostRuntime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); - m_renderer.SetDpiScale(GetDpiScale()); - if (m_hwnd != nullptr) { - const LONG windowWidth = suggestedRect.right - suggestedRect.left; - const LONG windowHeight = suggestedRect.bottom - suggestedRect.top; - SetWindowPos( - m_hwnd, - nullptr, - suggestedRect.left, - suggestedRect.top, - windowWidth, - windowHeight, - SWP_NOZORDER | SWP_NOACTIVATE); - UINT clientWidth = 0u; - UINT clientHeight = 0u; - if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) { - ApplyWindowResize(clientWidth, clientHeight); - } - Host::RefreshBorderlessWindowDwmDecorations(m_hwnd); - } - - std::ostringstream trace = {}; - trace << "dpi changed to " << m_hostRuntime.GetWindowDpi() << " scale=" << GetDpiScale(); - LogRuntimeTrace("window", trace.str()); -} - -void Application::ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result) { - if (result.requestPointerCapture && GetCapture() != m_hwnd) { - SetCapture(m_hwnd); - } - if (result.releasePointerCapture && GetCapture() == m_hwnd) { - ReleaseCapture(); - } -} - -void Application::ApplyHostedContentCaptureRequests() { - if (m_editorWorkspace.WantsHostPointerCapture() && GetCapture() != m_hwnd) { - SetCapture(m_hwnd); - } - - if (m_editorWorkspace.WantsHostPointerRelease() && - GetCapture() == m_hwnd && - !m_editorWorkspace.HasShellInteractiveCapture()) { - ReleaseCapture(); - } -} - -bool Application::HasInteractiveCaptureState() const { - return m_editorWorkspace.HasInteractiveCapture() || - m_hostRuntime.IsBorderlessWindowDragRestoreArmed(); -} - -void Application::QueuePointerEvent( - UIInputEventType type, - UIPointerButton button, - WPARAM wParam, - LPARAM lParam) { - UIInputEvent event = {}; - event.type = type; - event.pointerButton = button; - event.position = ConvertClientPixelsToDips( - GET_X_LPARAM(lParam), - GET_Y_LPARAM(lParam)); - event.modifiers = m_inputModifierTracker.BuildPointerModifiers(static_cast(wParam)); - m_pendingInputEvents.push_back(event); -} - -void Application::QueuePointerLeaveEvent() { - UIInputEvent event = {}; - event.type = UIInputEventType::PointerLeave; - if (m_hwnd != nullptr) { - POINT clientPoint = {}; - GetCursorPos(&clientPoint); - ScreenToClient(m_hwnd, &clientPoint); - event.position = ConvertClientPixelsToDips(clientPoint.x, clientPoint.y); - } - m_pendingInputEvents.push_back(event); -} - -void Application::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) { - if (m_hwnd == nullptr) { - return; - } - - POINT screenPoint = { - GET_X_LPARAM(lParam), - GET_Y_LPARAM(lParam) - }; - ScreenToClient(m_hwnd, &screenPoint); - - UIInputEvent event = {}; - event.type = UIInputEventType::PointerWheel; - event.position = ConvertClientPixelsToDips(screenPoint.x, screenPoint.y); - event.wheelDelta = static_cast(wheelDelta); - event.modifiers = m_inputModifierTracker.BuildPointerModifiers(static_cast(wParam)); - m_pendingInputEvents.push_back(event); -} - -void Application::QueueKeyEvent(UIInputEventType type, WPARAM wParam, LPARAM lParam) { - UIInputEvent event = {}; - event.type = type; - event.keyCode = MapVirtualKeyToUIKeyCode(wParam); - event.modifiers = m_inputModifierTracker.ApplyKeyMessage(type, wParam, lParam); - event.repeat = IsRepeatKeyMessage(lParam); - m_pendingInputEvents.push_back(event); -} - -void Application::QueueCharacterEvent(WPARAM wParam, LPARAM) { - UIInputEvent event = {}; - event.type = UIInputEventType::Character; - event.character = static_cast(wParam); - event.modifiers = m_inputModifierTracker.GetCurrentModifiers(); - m_pendingInputEvents.push_back(event); -} - -void Application::QueueWindowFocusEvent(UIInputEventType type) { - UIInputEvent event = {}; - event.type = type; - m_pendingInputEvents.push_back(event); -} - -std::filesystem::path Application::ResolveRepoRootPath() { - std::string root = XCUIEDITOR_REPO_ROOT; - if (root.size() >= 2u && root.front() == '"' && root.back() == '"') { - root = root.substr(1u, root.size() - 2u); - } - return std::filesystem::path(root).lexically_normal(); -} - -LONG WINAPI Application::HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo) { - if (exceptionInfo != nullptr && - exceptionInfo->ExceptionRecord != nullptr) { - AppendUIEditorCrashTrace( - exceptionInfo->ExceptionRecord->ExceptionCode, - exceptionInfo->ExceptionRecord->ExceptionAddress); - } else { - AppendUIEditorCrashTrace(0u, nullptr); - } - - return EXCEPTION_EXECUTE_HANDLER; -} - -LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { - LRESULT dispatcherResult = 0; - if (Host::WindowMessageDispatcher::TryHandleNonClientCreate( - hwnd, - message, - lParam, - dispatcherResult)) { - const auto* createStruct = reinterpret_cast(lParam); - if (createStruct != nullptr) { - if (Application* application = - reinterpret_cast(createStruct->lpCreateParams); - application != nullptr && - application->m_pendingCreateWindowState != nullptr && - application->m_pendingCreateWindowState->hwnd == nullptr) { - application->m_pendingCreateWindowState->hwnd = hwnd; - } - } - return dispatcherResult; - } - - Application* application = Host::WindowMessageDispatcher::GetApplicationFromWindow(hwnd); - if (application != nullptr) { - application->m_currentWindowState = application->FindWindowState(hwnd); - } - if (application != nullptr && - Host::WindowMessageDispatcher::TryDispatch( - hwnd, - *application, - message, - wParam, - lParam, - dispatcherResult)) { - return dispatcherResult; - } - - switch (message) { - case WM_MOUSEMOVE: - if (application != nullptr) { - if (application->HandleGlobalTabDragPointerMove(hwnd)) { - return 0; - } - if (application->HandleBorderlessWindowResizePointerMove()) { - return 0; - } - if (application->HandleBorderlessWindowChromeDragRestorePointerMove()) { - return 0; - } - const bool resizeHoverChanged = - application->UpdateBorderlessWindowResizeHover(lParam); - if (application->UpdateBorderlessWindowChromeHover(lParam)) { - application->InvalidateHostWindow(); - } - if (resizeHoverChanged) { - application->InvalidateHostWindow(); - } - if (!application->m_trackingMouseLeave) { - TRACKMOUSEEVENT trackMouseEvent = {}; - trackMouseEvent.cbSize = sizeof(trackMouseEvent); - trackMouseEvent.dwFlags = TME_LEAVE; - trackMouseEvent.hwndTrack = hwnd; - if (TrackMouseEvent(&trackMouseEvent)) { - application->m_trackingMouseLeave = true; - } - } - if (application->m_hostRuntime.GetHoveredBorderlessResizeEdge() != - Host::BorderlessWindowResizeEdge::None) { - return 0; - } - const Host::BorderlessWindowChromeHitTarget chromeHitTarget = - application->HitTestBorderlessWindowChrome(lParam); - if (chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || - chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || - chromeHitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton) { - return 0; - } - application->QueuePointerEvent( - UIInputEventType::PointerMove, - UIPointerButton::None, - wParam, - lParam); - return 0; - } - break; - case WM_MOUSELEAVE: - if (application != nullptr) { - application->m_trackingMouseLeave = false; - application->ClearBorderlessWindowResizeState(); - application->ClearBorderlessWindowChromeDragRestoreState(); - application->ClearBorderlessWindowChromeState(); - application->QueuePointerLeaveEvent(); - return 0; - } - break; - case WM_LBUTTONDOWN: - if (application != nullptr) { - if (application->HandleBorderlessWindowResizeButtonDown(lParam)) { - return 0; - } - if (application->HandleBorderlessWindowChromeButtonDown(lParam)) { - return 0; - } - SetFocus(hwnd); - application->QueuePointerEvent( - UIInputEventType::PointerButtonDown, - UIPointerButton::Left, - wParam, - lParam); - return 0; - } - break; - case WM_LBUTTONUP: - if (application != nullptr) { - if (application->HandleGlobalTabDragPointerButtonUp(hwnd)) { - return 0; - } - if (application->HandleBorderlessWindowResizeButtonUp()) { - return 0; - } - if (application->HandleBorderlessWindowChromeButtonUp(lParam)) { - return 0; - } - application->QueuePointerEvent( - UIInputEventType::PointerButtonUp, - UIPointerButton::Left, - wParam, - lParam); - return 0; - } - break; - case WM_LBUTTONDBLCLK: - if (application != nullptr && - application->HandleBorderlessWindowChromeDoubleClick(lParam)) { - return 0; - } - break; - case WM_MOUSEWHEEL: - if (application != nullptr) { - application->QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam); - return 0; - } - break; - case WM_SETFOCUS: - if (application != nullptr) { - application->m_inputModifierTracker.SyncFromSystemState(); - application->QueueWindowFocusEvent(UIInputEventType::FocusGained); - return 0; - } - break; - case WM_KILLFOCUS: - if (application != nullptr) { - application->m_inputModifierTracker.Reset(); - application->QueueWindowFocusEvent(UIInputEventType::FocusLost); - return 0; - } - break; - case WM_CAPTURECHANGED: - if (application != nullptr && - application->m_globalTabDragSession.active && - application->m_globalTabDragSession.panelWindowId == - (application->m_currentWindowState != nullptr - ? application->m_currentWindowState->windowId - : std::string()) && - reinterpret_cast(lParam) != hwnd) { - application->EndGlobalTabDragSession(); - return 0; - } - if (application != nullptr && - reinterpret_cast(lParam) != hwnd && - application->HasInteractiveCaptureState()) { - application->QueueWindowFocusEvent(UIInputEventType::FocusLost); - application->ForceClearBorderlessWindowResizeState(); - application->ClearBorderlessWindowChromeDragRestoreState(); - application->ClearBorderlessWindowChromeState(); - return 0; - } - if (application != nullptr && - reinterpret_cast(lParam) != hwnd) { - application->ForceClearBorderlessWindowResizeState(); - application->ClearBorderlessWindowChromeDragRestoreState(); - application->ClearBorderlessWindowChromeState(); - } - break; - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - if (application != nullptr) { - if (wParam == VK_F12) { - application->m_autoScreenshot.RequestCapture("manual_f12"); - } - application->QueueKeyEvent(UIInputEventType::KeyDown, wParam, lParam); - return 0; - } - break; - case WM_KEYUP: - case WM_SYSKEYUP: - if (application != nullptr) { - application->QueueKeyEvent(UIInputEventType::KeyUp, wParam, lParam); - return 0; - } - break; - case WM_CHAR: - if (application != nullptr) { - application->QueueCharacterEvent(wParam, lParam); - return 0; - } - break; - case WM_ERASEBKGND: - return 1; - case WM_DESTROY: - if (application != nullptr) { - if (application->m_globalTabDragSession.active && - application->m_globalTabDragSession.panelWindowId == - (application->m_currentWindowState != nullptr - ? application->m_currentWindowState->windowId - : std::string())) { - application->EndGlobalTabDragSession(); - } - application->HandleDestroyedWindow(hwnd); - } - return 0; - default: - break; - } - - return DefWindowProcW(hwnd, message, wParam, lParam); -} - -#undef m_hwnd -#undef m_renderer -#undef m_windowRenderer -#undef m_windowRenderLoop -#undef m_autoScreenshot -#undef m_inputModifierTracker -#undef m_workspaceController -#undef m_editorWorkspace -#undef m_pendingInputEvents -#undef m_trackingMouseLeave -#undef m_renderReady -#undef m_titleBarLogoIcon -#undef m_borderlessWindowChromeState -#undef m_hostRuntime - -int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow) { - Application application; - return application.Run(hInstance, nCmdShow); -} - -} // namespace XCEngine::UI::Editor diff --git a/new_editor/app/Application.h b/new_editor/app/Application.h deleted file mode 100644 index dfb7fa25..00000000 --- a/new_editor/app/Application.h +++ /dev/null @@ -1,255 +0,0 @@ -#pragma once - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Core/ProductEditorContext.h" -#include "Workspace/ProductEditorWorkspace.h" - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace XCEngine::UI::Editor::Host { -class WindowMessageDispatcher; -} - -namespace XCEngine::UI::Editor { - -class Application { -public: - Application(); - ~Application(); - - Application(const Application&) = delete; - Application& operator=(const Application&) = delete; - Application(Application&&) = delete; - Application& operator=(Application&&) = delete; - - int Run(HINSTANCE hInstance, int nCmdShow); - -private: - struct ManagedWindowState { - HWND hwnd = nullptr; - std::string windowId = {}; - std::wstring title = {}; - bool primary = false; - ::XCEngine::UI::Editor::Host::NativeRenderer renderer = {}; - ::XCEngine::UI::Editor::Host::D3D12WindowRenderer windowRenderer = {}; - ::XCEngine::UI::Editor::Host::D3D12WindowRenderLoop windowRenderLoop = {}; - ::XCEngine::UI::Editor::Host::AutoScreenshotController autoScreenshot = {}; - ::XCEngine::UI::Editor::Host::InputModifierTracker inputModifierTracker = {}; - UIEditorWorkspaceController workspaceController = {}; - App::ProductEditorWorkspace editorWorkspace = {}; - std::vector<::XCEngine::UI::UIInputEvent> pendingInputEvents = {}; - bool trackingMouseLeave = false; - bool renderReady = false; - ::XCEngine::UI::UITextureHandle titleBarLogoIcon = {}; - ::XCEngine::UI::Editor::Host::BorderlessWindowChromeState borderlessWindowChromeState = {}; - ::XCEngine::UI::Editor::Host::HostRuntimeState hostRuntime = {}; - bool detachRequested = false; - std::string detachedNodeId = {}; - std::string detachedPanelId = {}; - POINT detachScreenPoint = {}; - bool pendingGlobalTabDragStart = false; - std::string pendingGlobalTabDragNodeId = {}; - std::string pendingGlobalTabDragPanelId = {}; - POINT pendingGlobalTabDragScreenPoint = {}; - POINT pendingGlobalTabDragWindowOffset = {}; - }; - struct GlobalTabDragSession { - bool active = false; - std::string panelWindowId = {}; - std::string sourceNodeId = {}; - std::string panelId = {}; - POINT screenPoint = {}; - POINT windowDragOffset = {}; - }; - struct ManagedWindowCreateParams { - std::string windowId = {}; - std::wstring title = {}; - int initialX = CW_USEDEFAULT; - int initialY = CW_USEDEFAULT; - int initialWidth = 1540; - int initialHeight = 940; - int showCommand = SW_SHOW; - bool primary = false; - bool autoCaptureOnStartup = false; - }; - - friend class ::XCEngine::UI::Editor::Host::WindowMessageDispatcher; - - static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); - - bool Initialize(HINSTANCE hInstance, int nCmdShow); - void Shutdown(); - bool RegisterWindowClass(); - ManagedWindowState* CreateManagedWindow( - UIEditorWorkspaceController workspaceController, - const ManagedWindowCreateParams& params); - void DestroyManagedWindow(ManagedWindowState& windowState); - void DestroyClosedWindows(); - void RenderAllWindows(); - void ProcessPendingGlobalTabDragStarts(); - void ProcessPendingDetachRequests(); - bool TryStartGlobalTabDrag(ManagedWindowState& sourceWindowState); - bool TryProcessDetachRequest(ManagedWindowState& sourceWindowState); - UIEditorWindowWorkspaceSet BuildWindowWorkspaceSet(std::string_view activeWindowId = {}) const; - bool SynchronizeManagedWindowsFromWindowSet( - const UIEditorWindowWorkspaceSet& windowSet, - std::string_view preferredNewWindowId, - const POINT& preferredScreenPoint); - UIEditorWorkspaceController BuildWorkspaceControllerForWindow( - const UIEditorWindowWorkspaceState& windowState) const; - std::wstring BuildManagedWindowTitle(const UIEditorWorkspaceController& workspaceController) const; - RECT BuildDetachedWindowRect(const POINT& screenPoint) const; - void ResetManagedWindowInteractionState(ManagedWindowState& windowState); - void HandleDestroyedWindow(HWND hwnd); - void BeginGlobalTabDragSession( - std::string_view panelWindowId, - std::string_view sourceNodeId, - std::string_view panelId, - const POINT& screenPoint, - const POINT& windowDragOffset); - void EndGlobalTabDragSession(); - bool HandleGlobalTabDragPointerMove(HWND hwnd); - bool HandleGlobalTabDragPointerButtonUp(HWND hwnd); - ManagedWindowState* FindTopmostWindowStateAtScreenPoint( - const POINT& screenPoint, - std::string_view excludedWindowId = {}); - const ManagedWindowState* FindTopmostWindowStateAtScreenPoint( - const POINT& screenPoint, - std::string_view excludedWindowId = {}) const; - ManagedWindowState* FindWindowState(HWND hwnd); - const ManagedWindowState* FindWindowState(HWND hwnd) const; - ManagedWindowState* FindWindowState(std::string_view windowId); - const ManagedWindowState* FindWindowState(std::string_view windowId) const; - ManagedWindowState* FindPrimaryWindowState(); - const ManagedWindowState* FindPrimaryWindowState() const; - ManagedWindowState& RequireCurrentWindowState(); - const ManagedWindowState& RequireCurrentWindowState() const; - - void RenderFrame(); - void OnPaintMessage(); - void OnResize(UINT width, UINT height); - void OnEnterSizeMove(); - void OnExitSizeMove(); - void OnDpiChanged(UINT dpi, const RECT& suggestedRect); - bool ApplyWindowResize(UINT width, UINT height); - bool QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const; - bool ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const; - ::XCEngine::UI::UIRect ResolveWorkspaceBounds( - float clientWidthDips, - float clientHeightDips) const; - bool IsPointerInsideClientArea() const; - bool ApplyCurrentCursor() const; - LPCWSTR ResolveCurrentCursorResource() const; - float GetDpiScale() const; - float PixelsToDips(float pixels) const; - bool HasBorderlessWindowChrome() const; - ::XCEngine::UI::UIPoint ConvertClientPixelsToDips(LONG x, LONG y) const; - ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips( - const ManagedWindowState& windowState, - const POINT& screenPoint) const; - POINT ConvertClientDipsToScreenPixels( - const ManagedWindowState& windowState, - const ::XCEngine::UI::UIPoint& point) const; - bool TryResolveDraggedTabScreenRect( - const ManagedWindowState& windowState, - std::string_view nodeId, - std::string_view panelId, - RECT& outRect) const; - POINT ResolveGlobalTabDragWindowOffset( - const ManagedWindowState& windowState, - std::string_view nodeId, - std::string_view panelId, - const POINT& screenPoint) const; - void MoveGlobalTabDragWindow(ManagedWindowState& windowState, const POINT& screenPoint) const; - void AppendGlobalTabDragDropPreview(::XCEngine::UI::UIDrawList& drawList) const; - std::string BuildCaptureStatusText() const; - void LogRuntimeTrace(std::string_view channel, std::string_view message) const; - void ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result); - void ApplyHostedContentCaptureRequests(); - bool HasInteractiveCaptureState() const; - std::string DescribeInputEvents( - const std::vector<::XCEngine::UI::UIInputEvent>& events) const; - void QueuePointerEvent( - ::XCEngine::UI::UIInputEventType type, - ::XCEngine::UI::UIPointerButton button, - WPARAM wParam, - LPARAM lParam); - void QueuePointerLeaveEvent(); - void QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam); - void QueueKeyEvent(::XCEngine::UI::UIInputEventType type, WPARAM wParam, LPARAM lParam); - void QueueCharacterEvent(WPARAM wParam, LPARAM lParam); - void QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType type); - bool IsBorderlessWindowEnabled() const; - bool IsBorderlessWindowMaximized() const; - bool HandleBorderlessWindowSystemCommand(WPARAM wParam); - bool HandleBorderlessWindowGetMinMaxInfo(LPARAM lParam) const; - LRESULT HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const; - Host::BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome(LPARAM lParam) const; - bool UpdateBorderlessWindowChromeHover(LPARAM lParam); - bool HandleBorderlessWindowChromeButtonDown(LPARAM lParam); - bool HandleBorderlessWindowChromeButtonUp(LPARAM lParam); - bool HandleBorderlessWindowChromeDoubleClick(LPARAM lParam); - bool HandleBorderlessWindowChromeDragRestorePointerMove(); - void ClearBorderlessWindowChromeDragRestoreState(); - void ClearBorderlessWindowChromeState(); - Host::BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(LPARAM lParam) const; - bool UpdateBorderlessWindowResizeHover(LPARAM lParam); - bool HandleBorderlessWindowResizeButtonDown(LPARAM lParam); - bool HandleBorderlessWindowResizeButtonUp(); - bool HandleBorderlessWindowResizePointerMove(); - void ClearBorderlessWindowResizeState(); - void ForceClearBorderlessWindowResizeState(); - void ApplyBorderlessWindowResizeCursorHoverPriority(); - bool QueryCurrentWindowRect(RECT& outRect) const; - bool QueryBorderlessWindowWorkAreaRect(RECT& outRect) const; - bool ApplyPredictedWindowRectTransition(const RECT& targetRect); - void ToggleBorderlessWindowMaximizeRestore(); - void AppendBorderlessWindowChrome( - ::XCEngine::UI::UIDrawList& drawList, - float clientWidthDips) const; - void ExecuteBorderlessWindowChromeAction(Host::BorderlessWindowChromeHitTarget target); - Host::BorderlessWindowChromeLayout ResolveBorderlessWindowChromeLayout( - float clientWidthDips) const; - void InvalidateHostWindow() const; - static std::filesystem::path ResolveRepoRootPath(); - static LONG WINAPI HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo); - static bool IsVerboseRuntimeTraceEnabled(); - - HINSTANCE m_hInstance = nullptr; - ATOM m_windowClassAtom = 0; - std::filesystem::path m_repoRoot = {}; - App::ProductEditorContext m_editorContext = {}; - std::vector> m_windows = {}; - GlobalTabDragSession m_globalTabDragSession = {}; - ManagedWindowState* m_currentWindowState = nullptr; - ManagedWindowState* m_pendingCreateWindowState = nullptr; - bool m_shutdownRequested = false; -}; - -int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow); - -} // namespace XCEngine::UI::Editor diff --git a/new_editor/app/Bootstrap/Application.h b/new_editor/app/Bootstrap/Application.h new file mode 100644 index 00000000..b0eb9fa2 --- /dev/null +++ b/new_editor/app/Bootstrap/Application.h @@ -0,0 +1,70 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "Platform/Win32/WindowMessageHost.h" + +#include + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor { +namespace App { +class EditorContext; +class EditorWindowManager; +} +} + +namespace XCEngine::UI::Editor { + +class Application final : public Host::WindowMessageHost { +public: + Application(); + ~Application(); + + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + Application(Application&&) = delete; + Application& operator=(Application&&) = delete; + + int Run(HINSTANCE hInstance, int nCmdShow); + App::EditorContext& GetEditorContext() override; + const App::EditorContext& GetEditorContext() const override; + bool IsGlobalTabDragActive() const override; + bool OwnsActiveGlobalTabDrag(std::string_view windowId) const override; + void EndGlobalTabDragSession() override; + void HandleDestroyedWindow(HWND hwnd) override; + bool HandleGlobalTabDragPointerMove(HWND hwnd) override; + bool HandleGlobalTabDragPointerButtonUp(HWND hwnd) override; + +private: + static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + + bool Initialize(HINSTANCE hInstance, int nCmdShow); + void Shutdown(); + bool RegisterWindowClass(); + static std::filesystem::path ResolveRepoRootPath(); + static LONG WINAPI HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo); + void InitializeSmokeTestConfig(); + void TickSmokeTest(); + + HINSTANCE m_hInstance = nullptr; + ATOM m_windowClassAtom = 0; + std::filesystem::path m_repoRoot = {}; + std::unique_ptr m_editorContext = {}; + std::unique_ptr m_windowManager = {}; + bool m_smokeTestEnabled = false; + int m_smokeTestFrameLimit = 0; + int m_smokeTestRenderedFrames = 0; + bool m_smokeTestCloseRequested = false; +}; + +int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow); + +} // namespace XCEngine::UI::Editor + diff --git a/new_editor/app/Bootstrap/ApplicationBootstrap.cpp b/new_editor/app/Bootstrap/ApplicationBootstrap.cpp new file mode 100644 index 00000000..3e42256e --- /dev/null +++ b/new_editor/app/Bootstrap/ApplicationBootstrap.cpp @@ -0,0 +1,139 @@ +#include "Bootstrap/Application.h" +#include "Bootstrap/ApplicationBootstrapSupport.h" + +#include "State/EditorContext.h" +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowManager.h" +#include "Support/EnvironmentFlags.h" + +#ifndef XCUIEDITOR_REPO_ROOT +#define XCUIEDITOR_REPO_ROOT "." +#endif + +namespace XCEngine::UI::Editor { + +using namespace BootstrapSupport; + +bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { + m_hInstance = hInstance; + m_repoRoot = ResolveRepoRootPath(); + EnableDpiAwareness(); + InitializeSmokeTestConfig(); + + const std::filesystem::path logRoot = GetExecutableDirectory() / "logs"; + InitializeUIEditorRuntimeTrace(logRoot); + SetUnhandledExceptionFilter(&Application::HandleUnhandledException); + AppendUIEditorRuntimeTrace("app", "initialize begin"); + + if (!m_editorContext->Initialize(m_repoRoot)) { + AppendUIEditorRuntimeTrace( + "app", + "shell asset validation failed: " + m_editorContext->GetValidationMessage()); + return false; + } + if (!RegisterWindowClass()) { + return false; + } + + App::EditorWindowHostConfig hostConfig = {}; + hostConfig.hInstance = m_hInstance; + hostConfig.windowClassName = kWindowClassName; + hostConfig.windowStyle = kBorderlessWindowStyle; + hostConfig.primaryWindowTitle = kWindowTitle; + hostConfig.windowUserData = this; + m_windowManager = std::make_unique( + hostConfig, + m_repoRoot, + *m_editorContext); + + m_editorContext->SetExitRequestHandler([this]() { + if (m_windowManager == nullptr) { + return; + } + + if (App::EditorWindow* primaryWindow = m_windowManager->FindPrimaryWindow(); + primaryWindow != nullptr && + primaryWindow->GetHwnd() != nullptr) { + PostMessageW(primaryWindow->GetHwnd(), WM_CLOSE, 0, 0); + } + }); + m_editorContext->SetReadyStatus(); + + App::EditorWindowManager::CreateParams createParams = {}; + createParams.windowId = "main"; + createParams.title = kWindowTitle; + createParams.showCommand = nCmdShow; + createParams.primary = true; + createParams.autoCaptureOnStartup = !m_smokeTestEnabled; + if (m_windowManager->CreateEditorWindow( + m_editorContext->BuildWorkspaceController(), + createParams) == nullptr) { + AppendUIEditorRuntimeTrace("app", "primary window creation failed"); + return false; + } + + AppendUIEditorRuntimeTrace("app", "initialize end"); + return true; +} + +void Application::InitializeSmokeTestConfig() { + m_smokeTestEnabled = Support::IsEnvironmentFlagEnabled("XCUIEDITOR_SMOKE_TEST"); + m_smokeTestFrameLimit = 0; + m_smokeTestRenderedFrames = 0; + m_smokeTestCloseRequested = false; + if (!m_smokeTestEnabled) { + return; + } + + constexpr int kDefaultSmokeFrameLimit = 4; + const std::optional frameLimit = + Support::TryGetEnvironmentInt("XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT"); + m_smokeTestFrameLimit = + frameLimit.has_value() && frameLimit.value() > 0 + ? frameLimit.value() + : kDefaultSmokeFrameLimit; + + AppendUIEditorRuntimeTrace( + "smoke", + "enabled with frame limit=" + std::to_string(m_smokeTestFrameLimit)); +} + +void Application::Shutdown() { + AppendUIEditorRuntimeTrace("app", "shutdown begin"); + + if (m_windowManager != nullptr) { + m_windowManager->Shutdown(); + m_windowManager.reset(); + } + + if (m_windowClassAtom != 0 && m_hInstance != nullptr) { + UnregisterClassW(kWindowClassName, m_hInstance); + m_windowClassAtom = 0; + } + + AppendUIEditorRuntimeTrace("app", "shutdown end"); + ShutdownUIEditorRuntimeTrace(); +} + +std::filesystem::path Application::ResolveRepoRootPath() { + std::string root = XCUIEDITOR_REPO_ROOT; + if (root.size() >= 2u && root.front() == '"' && root.back() == '"') { + root = root.substr(1u, root.size() - 2u); + } + return std::filesystem::path(root).lexically_normal(); +} + +LONG WINAPI Application::HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo) { + if (exceptionInfo != nullptr && + exceptionInfo->ExceptionRecord != nullptr) { + AppendUIEditorCrashTrace( + exceptionInfo->ExceptionRecord->ExceptionCode, + exceptionInfo->ExceptionRecord->ExceptionAddress); + } else { + AppendUIEditorCrashTrace(0u, nullptr); + } + + return EXCEPTION_EXECUTE_HANDLER; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/app/Bootstrap/ApplicationBootstrapSupport.h b/new_editor/app/Bootstrap/ApplicationBootstrapSupport.h new file mode 100644 index 00000000..a59ac87d --- /dev/null +++ b/new_editor/app/Bootstrap/ApplicationBootstrapSupport.h @@ -0,0 +1,71 @@ +#pragma once + +#include "Bootstrap/EditorResources.h" +#include "Support/ExecutablePath.h" + +#include + +#include + +#include +#include + +namespace XCEngine::UI::Editor::BootstrapSupport { + +inline constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost"; +inline constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor"; +inline constexpr DWORD kBorderlessWindowStyle = WS_POPUP | WS_THICKFRAME; + +inline void EnableDpiAwareness() { + const HMODULE user32 = GetModuleHandleW(L"user32.dll"); + if (user32 != nullptr) { + using SetProcessDpiAwarenessContextFn = BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT); + const auto setProcessDpiAwarenessContext = + reinterpret_cast( + GetProcAddress(user32, "SetProcessDpiAwarenessContext")); + if (setProcessDpiAwarenessContext != nullptr) { + if (setProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { + return; + } + if (GetLastError() == ERROR_ACCESS_DENIED) { + return; + } + if (setProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) { + return; + } + if (GetLastError() == ERROR_ACCESS_DENIED) { + return; + } + } + } + + const HMODULE shcore = LoadLibraryW(L"shcore.dll"); + if (shcore != nullptr) { + using SetProcessDpiAwarenessFn = HRESULT(WINAPI*)(PROCESS_DPI_AWARENESS); + const auto setProcessDpiAwareness = + reinterpret_cast( + GetProcAddress(shcore, "SetProcessDpiAwareness")); + if (setProcessDpiAwareness != nullptr) { + const HRESULT hr = setProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); + FreeLibrary(shcore); + if (SUCCEEDED(hr) || hr == E_ACCESSDENIED) { + return; + } + } else { + FreeLibrary(shcore); + } + } + + if (user32 != nullptr) { + using SetProcessDPIAwareFn = BOOL(WINAPI*)(); + const auto setProcessDPIAware = + reinterpret_cast(GetProcAddress(user32, "SetProcessDPIAware")); + if (setProcessDPIAware != nullptr) { + setProcessDPIAware(); + } + } +} + +using Support::GetExecutableDirectory; + +} // namespace XCEngine::UI::Editor::BootstrapSupport diff --git a/new_editor/app/Bootstrap/ApplicationLifecycle.cpp b/new_editor/app/Bootstrap/ApplicationLifecycle.cpp new file mode 100644 index 00000000..77dfef33 --- /dev/null +++ b/new_editor/app/Bootstrap/ApplicationLifecycle.cpp @@ -0,0 +1,58 @@ +#include "Bootstrap/Application.h" + +#include "State/EditorContext.h" +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowManager.h" + +namespace XCEngine::UI::Editor { + +Application::Application() + : m_editorContext(std::make_unique()) {} + +Application::~Application() = default; + +App::EditorContext& Application::GetEditorContext() { + return *m_editorContext; +} + +const App::EditorContext& Application::GetEditorContext() const { + return *m_editorContext; +} + +bool Application::IsGlobalTabDragActive() const { + return m_windowManager != nullptr && m_windowManager->IsGlobalTabDragActive(); +} + +bool Application::OwnsActiveGlobalTabDrag(std::string_view windowId) const { + return m_windowManager != nullptr && + m_windowManager->OwnsActiveGlobalTabDrag(windowId); +} + +void Application::EndGlobalTabDragSession() { + if (m_windowManager != nullptr) { + m_windowManager->EndGlobalTabDragSession(); + } +} + +void Application::HandleDestroyedWindow(HWND hwnd) { + if (m_windowManager != nullptr) { + m_windowManager->HandleDestroyedWindow(hwnd); + } +} + +bool Application::HandleGlobalTabDragPointerMove(HWND hwnd) { + return m_windowManager != nullptr && + m_windowManager->HandleGlobalTabDragPointerMove(hwnd); +} + +bool Application::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { + return m_windowManager != nullptr && + m_windowManager->HandleGlobalTabDragPointerButtonUp(hwnd); +} + +int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow) { + Application application; + return application.Run(hInstance, nCmdShow); +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/app/Bootstrap/ApplicationRunLoop.cpp b/new_editor/app/Bootstrap/ApplicationRunLoop.cpp new file mode 100644 index 00000000..05fd5002 --- /dev/null +++ b/new_editor/app/Bootstrap/ApplicationRunLoop.cpp @@ -0,0 +1,67 @@ +#include "Bootstrap/Application.h" + +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowManager.h" + +#include + +namespace XCEngine::UI::Editor { + +void Application::TickSmokeTest() { + if (!m_smokeTestEnabled || m_smokeTestCloseRequested || m_windowManager == nullptr) { + return; + } + + ++m_smokeTestRenderedFrames; + if (m_smokeTestRenderedFrames < m_smokeTestFrameLimit) { + return; + } + + if (App::EditorWindow* primaryWindow = m_windowManager->FindPrimaryWindow(); + primaryWindow != nullptr && + primaryWindow->GetHwnd() != nullptr) { + PostMessageW(primaryWindow->GetHwnd(), WM_CLOSE, 0, 0); + m_smokeTestCloseRequested = true; + AppendUIEditorRuntimeTrace("smoke", "frame budget reached, requested WM_CLOSE"); + } +} + +int Application::Run(HINSTANCE hInstance, int nCmdShow) { + if (!Initialize(hInstance, nCmdShow)) { + Shutdown(); + return 1; + } + + MSG message = {}; + while (true) { + while (PeekMessageW(&message, nullptr, 0U, 0U, PM_REMOVE)) { + if (message.message == WM_QUIT) { + Shutdown(); + return static_cast(message.wParam); + } + + TranslateMessage(&message); + DispatchMessageW(&message); + } + + if (m_windowManager != nullptr) { + m_windowManager->DestroyClosedWindows(); + m_windowManager->ProcessPendingGlobalTabDragStarts(); + m_windowManager->ProcessPendingDetachRequests(); + m_windowManager->DestroyClosedWindows(); + if (!m_windowManager->HasWindows()) { + break; + } + + m_windowManager->RenderAllWindows(); + TickSmokeTest(); + } else { + break; + } + } + + Shutdown(); + return 0; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/app/Bootstrap/ApplicationWindowClass.cpp b/new_editor/app/Bootstrap/ApplicationWindowClass.cpp new file mode 100644 index 00000000..4f123b55 --- /dev/null +++ b/new_editor/app/Bootstrap/ApplicationWindowClass.cpp @@ -0,0 +1,114 @@ +#include "Bootstrap/Application.h" +#include "Bootstrap/ApplicationBootstrapSupport.h" + +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowManager.h" + +#include + +namespace XCEngine::UI::Editor { + +using namespace BootstrapSupport; + +namespace { + +void TryEnableNonClientDpiScaling(HWND hwnd) { + if (hwnd == nullptr) { + return; + } + + const HMODULE user32 = GetModuleHandleW(L"user32.dll"); + if (user32 == nullptr) { + return; + } + + using EnableNonClientDpiScalingFn = BOOL(WINAPI*)(HWND); + const auto enableNonClientDpiScaling = + reinterpret_cast( + GetProcAddress(user32, "EnableNonClientDpiScaling")); + if (enableNonClientDpiScaling != nullptr) { + enableNonClientDpiScaling(hwnd); + } +} + +Application* GetApplicationFromWindowUserData(HWND hwnd) { + return reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); +} + +} // namespace + +bool Application::RegisterWindowClass() { + WNDCLASSEXW windowClass = {}; + windowClass.cbSize = sizeof(windowClass); + windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; + windowClass.lpfnWndProc = &Application::WndProc; + windowClass.cbClsExtra = 0; + windowClass.cbWndExtra = 0; + windowClass.hInstance = m_hInstance; + windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); + windowClass.hbrBackground = nullptr; + windowClass.lpszMenuName = nullptr; + windowClass.hIcon = static_cast( + LoadImageW( + m_hInstance, + MAKEINTRESOURCEW(IDI_APP_ICON), + IMAGE_ICON, + 0, + 0, + LR_DEFAULTSIZE)); + windowClass.hIconSm = static_cast( + LoadImageW( + m_hInstance, + MAKEINTRESOURCEW(IDI_APP_ICON_SMALL), + IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + LR_DEFAULTCOLOR)); + windowClass.lpszClassName = kWindowClassName; + m_windowClassAtom = RegisterClassExW(&windowClass); + if (m_windowClassAtom == 0) { + AppendUIEditorRuntimeTrace("app", "window class registration failed"); + return false; + } + + return true; +} + +LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { + if (message == WM_NCCREATE) { + TryEnableNonClientDpiScaling(hwnd); + const auto* createStruct = reinterpret_cast(lParam); + Application* application = + createStruct != nullptr + ? reinterpret_cast(createStruct->lpCreateParams) + : nullptr; + SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(application)); + if (application != nullptr && application->m_windowManager != nullptr) { + application->m_windowManager->HandlePendingNativeWindowCreated(hwnd); + } + return TRUE; + } + + Application* application = GetApplicationFromWindowUserData(hwnd); + App::EditorWindow* window = + application != nullptr && application->m_windowManager != nullptr + ? application->m_windowManager->FindWindow(hwnd) + : nullptr; + LRESULT dispatcherResult = 0; + if (application != nullptr && + window != nullptr && + Host::WindowMessageDispatcher::TryDispatch( + hwnd, + *application, + *window, + message, + wParam, + lParam, + dispatcherResult)) { + return dispatcherResult; + } + + return DefWindowProcW(hwnd, message, wParam, lParam); +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/app/EditorApp.rc b/new_editor/app/Bootstrap/EditorApp.rc similarity index 91% rename from new_editor/app/EditorApp.rc rename to new_editor/app/Bootstrap/EditorApp.rc index 4fda63c9..6525da89 100644 --- a/new_editor/app/EditorApp.rc +++ b/new_editor/app/Bootstrap/EditorApp.rc @@ -1,4 +1,4 @@ -#include "EditorResources.h" +#include "Bootstrap/EditorResources.h" IDI_APP_ICON ICON "../resources/Icons/logo.ico" IDI_APP_ICON_SMALL ICON "../resources/Icons/logo_icon.ico" diff --git a/new_editor/app/EditorResources.h b/new_editor/app/Bootstrap/EditorResources.h similarity index 100% rename from new_editor/app/EditorResources.h rename to new_editor/app/Bootstrap/EditorResources.h diff --git a/new_editor/app/main.cpp b/new_editor/app/Bootstrap/main.cpp similarity index 81% rename from new_editor/app/main.cpp rename to new_editor/app/Bootstrap/main.cpp index 99224c12..0ff38cec 100644 --- a/new_editor/app/main.cpp +++ b/new_editor/app/Bootstrap/main.cpp @@ -1,4 +1,4 @@ -#include "Application.h" +#include "Bootstrap/Application.h" int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) { return XCEngine::UI::Editor::RunXCUIEditorApp(hInstance, nCmdShow); diff --git a/new_editor/app/Composition/EditorHostCommandBridge.cpp b/new_editor/app/Composition/EditorHostCommandBridge.cpp index 362b9e03..5d334130 100644 --- a/new_editor/app/Composition/EditorHostCommandBridge.cpp +++ b/new_editor/app/Composition/EditorHostCommandBridge.cpp @@ -1,18 +1,18 @@ -#include "ProductEditorHostCommandBridge.h" +#include "EditorHostCommandBridge.h" #include namespace XCEngine::UI::Editor::App { -void ProductEditorHostCommandBridge::BindSession(ProductEditorSession& session) { +void EditorHostCommandBridge::BindSession(EditorSession& session) { m_session = &session; } -void ProductEditorHostCommandBridge::SetExitRequestHandler(std::function handler) { +void EditorHostCommandBridge::SetExitRequestHandler(std::function handler) { m_requestExit = std::move(handler); } -UIEditorHostCommandEvaluationResult ProductEditorHostCommandBridge::EvaluateHostCommand( +UIEditorHostCommandEvaluationResult EditorHostCommandBridge::EvaluateHostCommand( std::string_view commandId) const { UIEditorHostCommandEvaluationResult result = {}; @@ -48,7 +48,7 @@ UIEditorHostCommandEvaluationResult ProductEditorHostCommandBridge::EvaluateHost return BuildDisabledResult("Host command is not attached yet."); } -UIEditorHostCommandDispatchResult ProductEditorHostCommandBridge::DispatchHostCommand( +UIEditorHostCommandDispatchResult EditorHostCommandBridge::DispatchHostCommand( std::string_view commandId) { UIEditorHostCommandDispatchResult result = {}; @@ -75,7 +75,7 @@ UIEditorHostCommandDispatchResult ProductEditorHostCommandBridge::DispatchHostCo return result; } -UIEditorHostCommandEvaluationResult ProductEditorHostCommandBridge::BuildDisabledResult( +UIEditorHostCommandEvaluationResult EditorHostCommandBridge::BuildDisabledResult( std::string_view message) const { UIEditorHostCommandEvaluationResult result = {}; result.executable = false; @@ -83,14 +83,14 @@ UIEditorHostCommandEvaluationResult ProductEditorHostCommandBridge::BuildDisable return result; } -UIEditorHostCommandEvaluationResult ProductEditorHostCommandBridge::EvaluateEditCommand( +UIEditorHostCommandEvaluationResult EditorHostCommandBridge::EvaluateEditCommand( std::string_view commandId) const { if (m_session == nullptr) { return BuildDisabledResult("Editor session is not attached yet."); } switch (m_session->activeRoute) { - case ProductEditorActionRoute::Hierarchy: + case EditorActionRoute::Hierarchy: if (SupportsHierarchyEditCommands(commandId)) { return UIEditorHostCommandEvaluationResult{ true, @@ -98,7 +98,7 @@ UIEditorHostCommandEvaluationResult ProductEditorHostCommandBridge::EvaluateEdit }; } return BuildDisabledResult("Current hierarchy route does not expose this command yet."); - case ProductEditorActionRoute::Project: + case EditorActionRoute::Project: if (SupportsProjectEditCommands(commandId)) { return UIEditorHostCommandEvaluationResult{ true, @@ -106,14 +106,14 @@ UIEditorHostCommandEvaluationResult ProductEditorHostCommandBridge::EvaluateEdit }; } return BuildDisabledResult("Current project route does not expose this command yet."); - case ProductEditorActionRoute::None: + case EditorActionRoute::None: return BuildDisabledResult("No active edit route."); default: return BuildDisabledResult("Current panel does not expose edit commands yet."); } } -UIEditorHostCommandDispatchResult ProductEditorHostCommandBridge::DispatchEditCommand( +UIEditorHostCommandDispatchResult EditorHostCommandBridge::DispatchEditCommand( std::string_view commandId) { UIEditorHostCommandDispatchResult result = {}; const UIEditorHostCommandEvaluationResult evaluation = EvaluateEditCommand(commandId); @@ -123,11 +123,11 @@ UIEditorHostCommandDispatchResult ProductEditorHostCommandBridge::DispatchEditCo } result.commandExecuted = true; - switch (m_session != nullptr ? m_session->activeRoute : ProductEditorActionRoute::None) { - case ProductEditorActionRoute::Hierarchy: + switch (m_session != nullptr ? m_session->activeRoute : EditorActionRoute::None) { + case EditorActionRoute::Hierarchy: result.message = "Hierarchy edit command route reached."; break; - case ProductEditorActionRoute::Project: + case EditorActionRoute::Project: result.message = "Project edit command route reached."; break; default: @@ -137,7 +137,7 @@ UIEditorHostCommandDispatchResult ProductEditorHostCommandBridge::DispatchEditCo return result; } -bool ProductEditorHostCommandBridge::SupportsHierarchyEditCommands( +bool EditorHostCommandBridge::SupportsHierarchyEditCommands( std::string_view commandId) const { return commandId == "edit.cut" || commandId == "edit.copy" || @@ -147,10 +147,11 @@ bool ProductEditorHostCommandBridge::SupportsHierarchyEditCommands( commandId == "edit.rename"; } -bool ProductEditorHostCommandBridge::SupportsProjectEditCommands( +bool EditorHostCommandBridge::SupportsProjectEditCommands( std::string_view commandId) const { return commandId == "edit.delete" || commandId == "edit.rename"; } } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Composition/EditorHostCommandBridge.h b/new_editor/app/Composition/EditorHostCommandBridge.h index 7271fec4..8c8a6180 100644 --- a/new_editor/app/Composition/EditorHostCommandBridge.h +++ b/new_editor/app/Composition/EditorHostCommandBridge.h @@ -1,6 +1,6 @@ #pragma once -#include "Core/ProductEditorSession.h" +#include "State/EditorSession.h" #include @@ -9,9 +9,9 @@ namespace XCEngine::UI::Editor::App { -class ProductEditorHostCommandBridge : public UIEditorHostCommandHandler { +class EditorHostCommandBridge : public UIEditorHostCommandHandler { public: - void BindSession(ProductEditorSession& session); + void BindSession(EditorSession& session); void SetExitRequestHandler(std::function handler); UIEditorHostCommandEvaluationResult EvaluateHostCommand( @@ -29,8 +29,9 @@ private: bool SupportsHierarchyEditCommands(std::string_view commandId) const; bool SupportsProjectEditCommands(std::string_view commandId) const; - ProductEditorSession* m_session = nullptr; + EditorSession* m_session = nullptr; std::function m_requestExit = {}; }; } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Composition/EditorShellAssetBuilder.cpp b/new_editor/app/Composition/EditorShellAssetBuilder.cpp index e4c1b887..06534b41 100644 --- a/new_editor/app/Composition/EditorShellAssetBuilder.cpp +++ b/new_editor/app/Composition/EditorShellAssetBuilder.cpp @@ -1,510 +1,46 @@ -#include "ProductShellAsset.h" +#include "EditorShellAssetBuilder.h" -#include -#include - -#include +#include "Composition/EditorShellAssetBuilderSupport.h" namespace XCEngine::UI::Editor::App { -namespace { +using namespace CompositionSupport; -using XCEngine::Input::KeyCode; -using XCEngine::UI::UIInputEventType; -using XCEngine::UI::UIShortcutBinding; -using XCEngine::UI::UIShortcutScope; -using Widgets::UIEditorStatusBarSegment; -using Widgets::UIEditorStatusBarSlot; -using Widgets::UIEditorStatusBarTextTone; - -UIEditorPanelRegistry BuildPanelRegistry() { - UIEditorPanelRegistry registry = {}; - registry.panels = { - { "hierarchy", "Hierarchy", UIEditorPanelPresentationKind::HostedContent, true, false, false }, - { "scene", "Scene", UIEditorPanelPresentationKind::ViewportShell, false, false, false }, - { "game", "Game", UIEditorPanelPresentationKind::ViewportShell, false, false, false }, - { "inspector", "Inspector", UIEditorPanelPresentationKind::HostedContent, true, false, false }, - { "console", "Console", UIEditorPanelPresentationKind::HostedContent, true, false, false }, - { "project", "Project", UIEditorPanelPresentationKind::HostedContent, false, false, false } - }; - return registry; -} - -UIEditorWorkspaceModel BuildWorkspace() { - UIEditorWorkspaceModel workspace = {}; - workspace.root = BuildUIEditorWorkspaceSplit( - "workspace-root", - UIEditorWorkspaceSplitAxis::Vertical, - 0.75f, - BuildUIEditorWorkspaceSplit( - "workspace-top", - UIEditorWorkspaceSplitAxis::Horizontal, - 0.7875f, - BuildUIEditorWorkspaceSplit( - "workspace-main", - UIEditorWorkspaceSplitAxis::Horizontal, - 0.19047619f, - BuildUIEditorWorkspaceSingleTabStack( - "hierarchy-panel", - "hierarchy", - "Hierarchy", - true), - BuildUIEditorWorkspaceTabStack( - "center-tabs", - { - BuildUIEditorWorkspacePanel( - "scene-panel", - "scene", - "Scene", - false), - BuildUIEditorWorkspacePanel( - "game-panel", - "game", - "Game", - false) - }, - 0u)), - BuildUIEditorWorkspaceSingleTabStack( - "inspector-panel", - "inspector", - "Inspector", - true)), - BuildUIEditorWorkspaceTabStack( - "bottom-tabs", - { - BuildUIEditorWorkspacePanel( - "console-panel", - "console", - "Console", - true), - BuildUIEditorWorkspacePanel( - "project-panel", - "project", - "Project", - false) - }, - 1u)); - workspace.activePanelId = "scene"; - return workspace; -} - -UIEditorCommandDescriptor BuildHostCommand( - std::string commandId, - std::string displayName) { - UIEditorCommandDescriptor command = {}; - command.commandId = std::move(commandId); - command.displayName = std::move(displayName); - command.kind = UIEditorCommandKind::Host; - command.workspaceCommand.panelSource = UIEditorCommandPanelSource::None; - command.workspaceCommand.panelId.clear(); - return command; -} - -UIEditorCommandDescriptor BuildWorkspaceCommand( - std::string commandId, - std::string displayName, - UIEditorWorkspaceCommandKind kind, - std::string panelId = {}) { - UIEditorCommandDescriptor command = {}; - command.commandId = std::move(commandId); - command.displayName = std::move(displayName); - command.workspaceCommand.kind = kind; - if (kind == UIEditorWorkspaceCommandKind::ResetWorkspace) { - command.workspaceCommand.panelSource = UIEditorCommandPanelSource::None; - } else { - command.workspaceCommand.panelSource = UIEditorCommandPanelSource::FixedPanelId; - command.workspaceCommand.panelId = std::move(panelId); - } - return command; -} - -UIEditorCommandRegistry BuildCommandRegistry() { - UIEditorCommandRegistry registry = {}; - registry.commands = { - BuildHostCommand("file.new_project", "New Project..."), - BuildHostCommand("file.open_project", "Open Project..."), - BuildHostCommand("file.save_project", "Save Project"), - BuildHostCommand("file.new_scene", "New Scene"), - BuildHostCommand("file.open_scene", "Open Scene"), - BuildHostCommand("file.save_scene", "Save Scene"), - BuildHostCommand("file.save_scene_as", "Save Scene As..."), - BuildHostCommand("file.exit", "Exit"), - BuildHostCommand("edit.undo", "Undo"), - BuildHostCommand("edit.redo", "Redo"), - BuildHostCommand("edit.cut", "Cut"), - BuildHostCommand("edit.copy", "Copy"), - BuildHostCommand("edit.paste", "Paste"), - BuildHostCommand("edit.duplicate", "Duplicate"), - BuildHostCommand("edit.delete", "Delete"), - BuildHostCommand("edit.rename", "Rename"), - BuildHostCommand("assets.reimport_selected", "Reimport Selected Asset"), - BuildHostCommand("assets.reimport_all", "Reimport All Assets"), - BuildHostCommand("assets.clear_library", "Clear Library"), - BuildHostCommand("run.play", "Play"), - BuildHostCommand("run.pause", "Pause"), - BuildHostCommand("run.step", "Step"), - BuildHostCommand("scripts.rebuild", "Rebuild Script Assemblies"), - BuildHostCommand("help.about", "About"), - BuildWorkspaceCommand( - "view.reset_layout", - "Reset Layout", - UIEditorWorkspaceCommandKind::ResetWorkspace), - BuildWorkspaceCommand( - "view.activate_hierarchy", - "Hierarchy", - UIEditorWorkspaceCommandKind::ActivatePanel, - "hierarchy"), - BuildWorkspaceCommand( - "view.activate_scene", - "Scene", - UIEditorWorkspaceCommandKind::ActivatePanel, - "scene"), - BuildWorkspaceCommand( - "view.activate_game", - "Game", - UIEditorWorkspaceCommandKind::ActivatePanel, - "game"), - BuildWorkspaceCommand( - "view.activate_inspector", - "Inspector", - UIEditorWorkspaceCommandKind::ActivatePanel, - "inspector"), - BuildWorkspaceCommand( - "view.activate_console", - "Console", - UIEditorWorkspaceCommandKind::ActivatePanel, - "console"), - BuildWorkspaceCommand( - "view.activate_project", - "Project", - UIEditorWorkspaceCommandKind::ActivatePanel, - "project") - }; - return registry; -} - -UIEditorMenuItemDescriptor BuildCommandItem( - std::string itemId, - std::string label, - std::string commandId, - UIEditorMenuCheckedStateBinding checkedState = {}) { - UIEditorMenuItemDescriptor item = {}; - item.kind = UIEditorMenuItemKind::Command; - item.itemId = std::move(itemId); - item.label = std::move(label); - item.commandId = std::move(commandId); - item.checkedState = std::move(checkedState); - return item; -} - -UIEditorMenuItemDescriptor BuildSeparatorItem(std::string itemId) { - UIEditorMenuItemDescriptor item = {}; - item.kind = UIEditorMenuItemKind::Separator; - item.itemId = std::move(itemId); - return item; -} - -UIEditorMenuItemDescriptor BuildSubmenuItem( - std::string itemId, - std::string label, - std::vector children) { - UIEditorMenuItemDescriptor item = {}; - item.kind = UIEditorMenuItemKind::Submenu; - item.itemId = std::move(itemId); - item.label = std::move(label); - item.children = std::move(children); - return item; -} - -UIEditorMenuModel BuildMenuModel() { - UIEditorMenuModel model = {}; - - UIEditorMenuDescriptor fileMenu = {}; - fileMenu.menuId = "file"; - fileMenu.label = "File"; - fileMenu.items = { - BuildCommandItem("file-new-project", "New Project...", "file.new_project"), - BuildCommandItem("file-open-project", "Open Project...", "file.open_project"), - BuildCommandItem("file-save-project", "Save Project", "file.save_project"), - BuildSeparatorItem("file-separator-project"), - BuildCommandItem("file-new-scene", "New Scene", "file.new_scene"), - BuildCommandItem("file-open-scene", "Open Scene", "file.open_scene"), - BuildCommandItem("file-save-scene", "Save Scene", "file.save_scene"), - BuildCommandItem("file-save-scene-as", "Save Scene As...", "file.save_scene_as"), - BuildSeparatorItem("file-separator-exit"), - BuildCommandItem("file-exit", "Exit", "file.exit") - }; - - UIEditorMenuDescriptor editMenu = {}; - editMenu.menuId = "edit"; - editMenu.label = "Edit"; - editMenu.items = { - BuildCommandItem("edit-undo", "Undo", "edit.undo"), - BuildCommandItem("edit-redo", "Redo", "edit.redo"), - BuildSeparatorItem("edit-separator-history"), - BuildCommandItem("edit-cut", "Cut", "edit.cut"), - BuildCommandItem("edit-copy", "Copy", "edit.copy"), - BuildCommandItem("edit-paste", "Paste", "edit.paste"), - BuildCommandItem("edit-duplicate", "Duplicate", "edit.duplicate"), - BuildCommandItem("edit-delete", "Delete", "edit.delete"), - BuildCommandItem("edit-rename", "Rename", "edit.rename") - }; - - UIEditorMenuDescriptor assetsMenu = {}; - assetsMenu.menuId = "assets"; - assetsMenu.label = "Assets"; - assetsMenu.items = { - BuildCommandItem("assets-reimport-selected", "Reimport Selected Asset", "assets.reimport_selected"), - BuildCommandItem("assets-reimport-all", "Reimport All Assets", "assets.reimport_all"), - BuildSeparatorItem("assets-separator-clear"), - BuildCommandItem("assets-clear-library", "Clear Library", "assets.clear_library") - }; - - UIEditorMenuDescriptor runMenu = {}; - runMenu.menuId = "run"; - runMenu.label = "Run"; - runMenu.items = { - BuildCommandItem("run-play", "Play", "run.play"), - BuildCommandItem("run-pause", "Pause", "run.pause"), - BuildCommandItem("run-step", "Step", "run.step") - }; - - UIEditorMenuDescriptor scriptsMenu = {}; - scriptsMenu.menuId = "scripts"; - scriptsMenu.label = "Scripts"; - scriptsMenu.items = { - BuildCommandItem("scripts-rebuild", "Rebuild Script Assemblies", "scripts.rebuild") - }; - - UIEditorMenuCheckedStateBinding hierarchyActive = { - UIEditorMenuCheckedStateSource::PanelActive, - "hierarchy" - }; - UIEditorMenuCheckedStateBinding sceneActive = { - UIEditorMenuCheckedStateSource::PanelActive, - "scene" - }; - UIEditorMenuCheckedStateBinding gameActive = { - UIEditorMenuCheckedStateSource::PanelActive, - "game" - }; - UIEditorMenuCheckedStateBinding inspectorActive = { - UIEditorMenuCheckedStateSource::PanelActive, - "inspector" - }; - UIEditorMenuCheckedStateBinding consoleActive = { - UIEditorMenuCheckedStateSource::PanelActive, - "console" - }; - UIEditorMenuCheckedStateBinding projectActive = { - UIEditorMenuCheckedStateSource::PanelActive, - "project" - }; - - UIEditorMenuDescriptor viewMenu = {}; - viewMenu.menuId = "view"; - viewMenu.label = "View"; - viewMenu.items = { - BuildCommandItem("view-reset-layout", "Reset Layout", "view.reset_layout"), - BuildSeparatorItem("view-separator-panels"), - BuildSubmenuItem( - "view-panels", - "Panels", - { - BuildCommandItem("view-panel-hierarchy", "Hierarchy", "view.activate_hierarchy", hierarchyActive), - BuildCommandItem("view-panel-scene", "Scene", "view.activate_scene", sceneActive), - BuildCommandItem("view-panel-game", "Game", "view.activate_game", gameActive), - BuildCommandItem("view-panel-inspector", "Inspector", "view.activate_inspector", inspectorActive), - BuildCommandItem("view-panel-console", "Console", "view.activate_console", consoleActive), - BuildCommandItem("view-panel-project", "Project", "view.activate_project", projectActive) - }) - }; - - UIEditorMenuDescriptor helpMenu = {}; - helpMenu.menuId = "help"; - helpMenu.label = "Help"; - helpMenu.items = { - BuildCommandItem("help-about", "About", "help.about") - }; - - model.menus = { - std::move(fileMenu), - std::move(editMenu), - std::move(assetsMenu), - std::move(runMenu), - std::move(scriptsMenu), - std::move(viewMenu), - std::move(helpMenu) - }; - return model; -} - -UIShortcutBinding BuildBinding( - std::string commandId, - std::int32_t keyCode, - bool control = false, - bool shift = false, - bool alt = false) { - UIShortcutBinding binding = {}; - binding.scope = UIShortcutScope::Global; - binding.triggerEventType = UIInputEventType::KeyDown; - binding.commandId = std::move(commandId); - binding.chord.keyCode = keyCode; - binding.chord.modifiers.control = control; - binding.chord.modifiers.shift = shift; - binding.chord.modifiers.alt = alt; - return binding; -} - -std::vector BuildShortcutBindings() { - return { - BuildBinding("file.new_scene", static_cast(KeyCode::N), true), - BuildBinding("file.open_scene", static_cast(KeyCode::O), true), - BuildBinding("file.save_scene", static_cast(KeyCode::S), true), - BuildBinding("file.save_scene_as", static_cast(KeyCode::S), true, true), - BuildBinding("edit.undo", static_cast(KeyCode::Z), true), - BuildBinding("edit.redo", static_cast(KeyCode::Y), true), - BuildBinding("edit.cut", static_cast(KeyCode::X), true), - BuildBinding("edit.copy", static_cast(KeyCode::C), true), - BuildBinding("edit.paste", static_cast(KeyCode::V), true), - BuildBinding("edit.duplicate", static_cast(KeyCode::D), true), - BuildBinding("edit.delete", static_cast(KeyCode::Delete)), - BuildBinding("edit.rename", static_cast(KeyCode::F2)), - BuildBinding("run.play", static_cast(KeyCode::F5)), - BuildBinding("run.pause", static_cast(KeyCode::F6)), - BuildBinding("run.step", static_cast(KeyCode::F7)), - BuildBinding("file.exit", static_cast(KeyCode::F4), false, false, true) - }; -} - -UIEditorStatusBarSegment BuildStatusSegment( - std::string segmentId, - std::string label, - UIEditorStatusBarSlot slot, - UIEditorStatusBarTextTone tone, - float desiredWidth, - bool showSeparator) { - UIEditorStatusBarSegment segment = {}; - segment.segmentId = std::move(segmentId); - segment.label = std::move(label); - segment.slot = slot; - segment.tone = tone; - segment.interactive = false; - segment.desiredWidth = desiredWidth; - segment.showSeparator = showSeparator; - return segment; -} - -UIEditorShellToolbarButton BuildToolbarButton( - std::string buttonId, - UIEditorShellToolbarGlyph glyph) { - UIEditorShellToolbarButton button = {}; - button.buttonId = std::move(buttonId); - button.glyph = glyph; - button.enabled = true; - return button; -} - -UIEditorWorkspacePanelPresentationModel BuildPlaceholderPresentation( - std::string panelId) { - UIEditorWorkspacePanelPresentationModel presentation = {}; - presentation.panelId = std::move(panelId); - presentation.kind = UIEditorPanelPresentationKind::Placeholder; - return presentation; -} - -UIEditorWorkspacePanelPresentationModel BuildHostedContentPresentation( - std::string panelId) { - UIEditorWorkspacePanelPresentationModel presentation = {}; - presentation.panelId = std::move(panelId); - presentation.kind = UIEditorPanelPresentationKind::HostedContent; - return presentation; -} - -UIEditorWorkspacePanelPresentationModel BuildViewportPresentation( - std::string panelId, - std::string title, - std::string subtitle) { - UIEditorWorkspacePanelPresentationModel presentation = {}; - presentation.panelId = std::move(panelId); - presentation.kind = UIEditorPanelPresentationKind::ViewportShell; - presentation.viewportShellModel.spec.chrome.title = std::move(title); - presentation.viewportShellModel.spec.chrome.subtitle = {}; - presentation.viewportShellModel.spec.chrome.showTopBar = false; - presentation.viewportShellModel.spec.chrome.showBottomBar = false; - presentation.viewportShellModel.frame.statusText.clear(); - return presentation; -} - -UIEditorShellInteractionDefinition BuildBaseShellDefinition() { - UIEditorShellInteractionDefinition definition = {}; - definition.menuModel = BuildMenuModel(); - definition.toolbarButtons = { - BuildToolbarButton("run.play", UIEditorShellToolbarGlyph::Play), - BuildToolbarButton("run.pause", UIEditorShellToolbarGlyph::Pause), - BuildToolbarButton("run.step", UIEditorShellToolbarGlyph::Step) - }; - definition.statusSegments = {}; - definition.workspacePresentations = { - BuildHostedContentPresentation("hierarchy"), - BuildViewportPresentation("scene", "Scene", {}), - BuildViewportPresentation("game", "Game", {}), - BuildHostedContentPresentation("inspector"), - BuildHostedContentPresentation("console"), - BuildHostedContentPresentation("project") - }; - return definition; -} - -std::string ResolvePanelTitle( - const UIEditorPanelRegistry& registry, - std::string_view panelId) { - if (const UIEditorPanelDescriptor* descriptor = - FindUIEditorPanelDescriptor(registry, panelId); - descriptor != nullptr) { - return descriptor->defaultTitle; - } - - return panelId.empty() ? std::string("(none)") : std::string(panelId); -} - -} // namespace - -EditorShellAsset BuildProductShellAsset(const std::filesystem::path& repoRoot) { +EditorShellAsset BuildEditorShellAsset(const std::filesystem::path& repoRoot) { EditorShellAsset asset = {}; - asset.screenId = "editor.product.shell"; + asset.screenId = "editor.shell"; asset.captureRootPath = (repoRoot / "new_editor/captures").lexically_normal(); - asset.panelRegistry = BuildPanelRegistry(); - asset.workspace = BuildWorkspace(); + asset.panelRegistry = BuildEditorPanelRegistry(); + asset.workspace = BuildEditorWorkspaceModel(); asset.workspaceSession = BuildDefaultUIEditorWorkspaceSession(asset.panelRegistry, asset.workspace); - asset.shellDefinition = BuildBaseShellDefinition(); - asset.shortcutAsset.commandRegistry = BuildCommandRegistry(); - asset.shortcutAsset.bindings = BuildShortcutBindings(); + asset.shellDefinition = BuildBaseEditorShellDefinition(); + asset.shortcutAsset.commandRegistry = BuildEditorCommandRegistry(); + asset.shortcutAsset.bindings = BuildEditorShortcutBindings(); return asset; } -UIEditorShellInteractionDefinition BuildProductShellInteractionDefinition( +UIEditorShellInteractionDefinition BuildEditorShellInteractionDefinition( const EditorShellAsset& asset, const UIEditorWorkspaceController& controller, std::string_view statusText, std::string_view captureText, - ProductEditorShellVariant variant) { + EditorShellVariant variant) { UIEditorShellInteractionDefinition definition = asset.shellDefinition; - if (variant == ProductEditorShellVariant::DetachedWindow) { + if (variant == EditorShellVariant::DetachedWindow) { definition.menuModel = {}; definition.toolbarButtons.clear(); definition.statusSegments.clear(); } const std::string activeTitle = - ResolvePanelTitle(asset.panelRegistry, controller.GetWorkspace().activePanelId); + ResolveEditorPanelTitle(asset.panelRegistry, controller.GetWorkspace().activePanelId); const std::string resolvedStatus = statusText.empty() ? std::string("Shell ready") : std::string(statusText); const std::string resolvedCapture = captureText.empty() ? std::string("F12 -> Screenshot") : std::string(captureText); - for (UIEditorStatusBarSegment& segment : definition.statusSegments) { + for (Widgets::UIEditorStatusBarSegment& segment : definition.statusSegments) { if (segment.segmentId == "editor-status") { segment.label = resolvedStatus; } else if (segment.segmentId == "capture") { diff --git a/new_editor/app/Composition/EditorShellAssetBuilder.h b/new_editor/app/Composition/EditorShellAssetBuilder.h index f40fe207..9829d9f6 100644 --- a/new_editor/app/Composition/EditorShellAssetBuilder.h +++ b/new_editor/app/Composition/EditorShellAssetBuilder.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -9,18 +9,19 @@ namespace XCEngine::UI::Editor::App { -enum class ProductEditorShellVariant : std::uint8_t { +enum class EditorShellVariant : std::uint8_t { Primary = 0, DetachedWindow }; -EditorShellAsset BuildProductShellAsset(const std::filesystem::path& repoRoot); +EditorShellAsset BuildEditorShellAsset(const std::filesystem::path& repoRoot); -UIEditorShellInteractionDefinition BuildProductShellInteractionDefinition( +UIEditorShellInteractionDefinition BuildEditorShellInteractionDefinition( const EditorShellAsset& asset, const UIEditorWorkspaceController& controller, std::string_view statusText, std::string_view captureText, - ProductEditorShellVariant variant = ProductEditorShellVariant::Primary); + EditorShellVariant variant = EditorShellVariant::Primary); } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Composition/EditorShellAssetBuilderSupport.h b/new_editor/app/Composition/EditorShellAssetBuilderSupport.h new file mode 100644 index 00000000..e97ba626 --- /dev/null +++ b/new_editor/app/Composition/EditorShellAssetBuilderSupport.h @@ -0,0 +1,23 @@ +#pragma once + +#include "EditorShellAssetBuilder.h" + +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::App::CompositionSupport { + +UIEditorPanelRegistry BuildEditorPanelRegistry(); +UIEditorWorkspaceModel BuildEditorWorkspaceModel(); +UIEditorCommandRegistry BuildEditorCommandRegistry(); +UIEditorMenuModel BuildEditorMenuModel(); +std::vector<::XCEngine::UI::UIShortcutBinding> BuildEditorShortcutBindings(); +UIEditorShellInteractionDefinition BuildBaseEditorShellDefinition(); +std::string ResolveEditorPanelTitle( + const UIEditorPanelRegistry& registry, + std::string_view panelId); + +} // namespace XCEngine::UI::Editor::App::CompositionSupport diff --git a/new_editor/app/Composition/EditorShellAssetCommands.cpp b/new_editor/app/Composition/EditorShellAssetCommands.cpp new file mode 100644 index 00000000..f8751b69 --- /dev/null +++ b/new_editor/app/Composition/EditorShellAssetCommands.cpp @@ -0,0 +1,151 @@ +#include "EditorShellAssetBuilderSupport.h" + +#include + +#include + +namespace XCEngine::UI::Editor::App::CompositionSupport { + +namespace { + +using XCEngine::Input::KeyCode; +using XCEngine::UI::UIInputEventType; +using XCEngine::UI::UIShortcutBinding; +using XCEngine::UI::UIShortcutScope; + +UIEditorCommandDescriptor BuildHostCommand( + std::string commandId, + std::string displayName) { + UIEditorCommandDescriptor command = {}; + command.commandId = std::move(commandId); + command.displayName = std::move(displayName); + command.kind = UIEditorCommandKind::Host; + command.workspaceCommand.panelSource = UIEditorCommandPanelSource::None; + command.workspaceCommand.panelId.clear(); + return command; +} + +UIEditorCommandDescriptor BuildWorkspaceCommand( + std::string commandId, + std::string displayName, + UIEditorWorkspaceCommandKind kind, + std::string panelId = {}) { + UIEditorCommandDescriptor command = {}; + command.commandId = std::move(commandId); + command.displayName = std::move(displayName); + command.workspaceCommand.kind = kind; + if (kind == UIEditorWorkspaceCommandKind::ResetWorkspace) { + command.workspaceCommand.panelSource = UIEditorCommandPanelSource::None; + } else { + command.workspaceCommand.panelSource = UIEditorCommandPanelSource::FixedPanelId; + command.workspaceCommand.panelId = std::move(panelId); + } + return command; +} + +UIShortcutBinding BuildBinding( + std::string commandId, + std::int32_t keyCode, + bool control = false, + bool shift = false, + bool alt = false) { + UIShortcutBinding binding = {}; + binding.scope = UIShortcutScope::Global; + binding.triggerEventType = UIInputEventType::KeyDown; + binding.commandId = std::move(commandId); + binding.chord.keyCode = keyCode; + binding.chord.modifiers.control = control; + binding.chord.modifiers.shift = shift; + binding.chord.modifiers.alt = alt; + return binding; +} + +} // namespace + +UIEditorCommandRegistry BuildEditorCommandRegistry() { + UIEditorCommandRegistry registry = {}; + registry.commands = { + BuildHostCommand("file.new_project", "New Project..."), + BuildHostCommand("file.open_project", "Open Project..."), + BuildHostCommand("file.save_project", "Save Project"), + BuildHostCommand("file.new_scene", "New Scene"), + BuildHostCommand("file.open_scene", "Open Scene"), + BuildHostCommand("file.save_scene", "Save Scene"), + BuildHostCommand("file.save_scene_as", "Save Scene As..."), + BuildHostCommand("file.exit", "Exit"), + BuildHostCommand("edit.undo", "Undo"), + BuildHostCommand("edit.redo", "Redo"), + BuildHostCommand("edit.cut", "Cut"), + BuildHostCommand("edit.copy", "Copy"), + BuildHostCommand("edit.paste", "Paste"), + BuildHostCommand("edit.duplicate", "Duplicate"), + BuildHostCommand("edit.delete", "Delete"), + BuildHostCommand("edit.rename", "Rename"), + BuildHostCommand("assets.reimport_selected", "Reimport Selected Asset"), + BuildHostCommand("assets.reimport_all", "Reimport All Assets"), + BuildHostCommand("assets.clear_library", "Clear Library"), + BuildHostCommand("run.play", "Play"), + BuildHostCommand("run.pause", "Pause"), + BuildHostCommand("run.step", "Step"), + BuildHostCommand("scripts.rebuild", "Rebuild Script Assemblies"), + BuildHostCommand("help.about", "About"), + BuildWorkspaceCommand( + "view.reset_layout", + "Reset Layout", + UIEditorWorkspaceCommandKind::ResetWorkspace), + BuildWorkspaceCommand( + "view.activate_hierarchy", + "Hierarchy", + UIEditorWorkspaceCommandKind::ActivatePanel, + "hierarchy"), + BuildWorkspaceCommand( + "view.activate_scene", + "Scene", + UIEditorWorkspaceCommandKind::ActivatePanel, + "scene"), + BuildWorkspaceCommand( + "view.activate_game", + "Game", + UIEditorWorkspaceCommandKind::ActivatePanel, + "game"), + BuildWorkspaceCommand( + "view.activate_inspector", + "Inspector", + UIEditorWorkspaceCommandKind::ActivatePanel, + "inspector"), + BuildWorkspaceCommand( + "view.activate_console", + "Console", + UIEditorWorkspaceCommandKind::ActivatePanel, + "console"), + BuildWorkspaceCommand( + "view.activate_project", + "Project", + UIEditorWorkspaceCommandKind::ActivatePanel, + "project") + }; + return registry; +} + +std::vector BuildEditorShortcutBindings() { + return { + BuildBinding("file.new_scene", static_cast(KeyCode::N), true), + BuildBinding("file.open_scene", static_cast(KeyCode::O), true), + BuildBinding("file.save_scene", static_cast(KeyCode::S), true), + BuildBinding("file.save_scene_as", static_cast(KeyCode::S), true, true), + BuildBinding("edit.undo", static_cast(KeyCode::Z), true), + BuildBinding("edit.redo", static_cast(KeyCode::Y), true), + BuildBinding("edit.cut", static_cast(KeyCode::X), true), + BuildBinding("edit.copy", static_cast(KeyCode::C), true), + BuildBinding("edit.paste", static_cast(KeyCode::V), true), + BuildBinding("edit.duplicate", static_cast(KeyCode::D), true), + BuildBinding("edit.delete", static_cast(KeyCode::Delete)), + BuildBinding("edit.rename", static_cast(KeyCode::F2)), + BuildBinding("run.play", static_cast(KeyCode::F5)), + BuildBinding("run.pause", static_cast(KeyCode::F6)), + BuildBinding("run.step", static_cast(KeyCode::F7)), + BuildBinding("file.exit", static_cast(KeyCode::F4), false, false, true) + }; +} + +} // namespace XCEngine::UI::Editor::App::CompositionSupport diff --git a/new_editor/app/Composition/EditorShellAssetDefinition.cpp b/new_editor/app/Composition/EditorShellAssetDefinition.cpp new file mode 100644 index 00000000..1015a294 --- /dev/null +++ b/new_editor/app/Composition/EditorShellAssetDefinition.cpp @@ -0,0 +1,75 @@ +#include "EditorShellAssetBuilderSupport.h" + +#include + +namespace XCEngine::UI::Editor::App::CompositionSupport { + +namespace { + +UIEditorShellToolbarButton BuildToolbarButton( + std::string buttonId, + UIEditorShellToolbarGlyph glyph) { + UIEditorShellToolbarButton button = {}; + button.buttonId = std::move(buttonId); + button.glyph = glyph; + button.enabled = true; + return button; +} + +UIEditorWorkspacePanelPresentationModel BuildHostedContentPresentation( + std::string panelId) { + UIEditorWorkspacePanelPresentationModel presentation = {}; + presentation.panelId = std::move(panelId); + presentation.kind = UIEditorPanelPresentationKind::HostedContent; + return presentation; +} + +UIEditorWorkspacePanelPresentationModel BuildViewportPresentation( + std::string panelId, + std::string title) { + UIEditorWorkspacePanelPresentationModel presentation = {}; + presentation.panelId = std::move(panelId); + presentation.kind = UIEditorPanelPresentationKind::ViewportShell; + presentation.viewportShellModel.spec.chrome.title = std::move(title); + presentation.viewportShellModel.spec.chrome.subtitle = {}; + presentation.viewportShellModel.spec.chrome.showTopBar = false; + presentation.viewportShellModel.spec.chrome.showBottomBar = false; + presentation.viewportShellModel.frame.statusText.clear(); + return presentation; +} + +} // namespace + +UIEditorShellInteractionDefinition BuildBaseEditorShellDefinition() { + UIEditorShellInteractionDefinition definition = {}; + definition.menuModel = BuildEditorMenuModel(); + definition.toolbarButtons = { + BuildToolbarButton("run.play", UIEditorShellToolbarGlyph::Play), + BuildToolbarButton("run.pause", UIEditorShellToolbarGlyph::Pause), + BuildToolbarButton("run.step", UIEditorShellToolbarGlyph::Step) + }; + definition.statusSegments = {}; + definition.workspacePresentations = { + BuildHostedContentPresentation("hierarchy"), + BuildViewportPresentation("scene", "Scene"), + BuildViewportPresentation("game", "Game"), + BuildHostedContentPresentation("inspector"), + BuildHostedContentPresentation("console"), + BuildHostedContentPresentation("project") + }; + return definition; +} + +std::string ResolveEditorPanelTitle( + const UIEditorPanelRegistry& registry, + std::string_view panelId) { + if (const UIEditorPanelDescriptor* descriptor = + FindUIEditorPanelDescriptor(registry, panelId); + descriptor != nullptr) { + return descriptor->defaultTitle; + } + + return panelId.empty() ? std::string("(none)") : std::string(panelId); +} + +} // namespace XCEngine::UI::Editor::App::CompositionSupport diff --git a/new_editor/app/Composition/EditorShellAssetLayout.cpp b/new_editor/app/Composition/EditorShellAssetLayout.cpp new file mode 100644 index 00000000..2bfc6e3f --- /dev/null +++ b/new_editor/app/Composition/EditorShellAssetLayout.cpp @@ -0,0 +1,76 @@ +#include "EditorShellAssetBuilderSupport.h" + +namespace XCEngine::UI::Editor::App::CompositionSupport { + +UIEditorPanelRegistry BuildEditorPanelRegistry() { + UIEditorPanelRegistry registry = {}; + registry.panels = { + { "hierarchy", "Hierarchy", UIEditorPanelPresentationKind::HostedContent, true, false, false }, + { "scene", "Scene", UIEditorPanelPresentationKind::ViewportShell, false, false, false }, + { "game", "Game", UIEditorPanelPresentationKind::ViewportShell, false, false, false }, + { "inspector", "Inspector", UIEditorPanelPresentationKind::HostedContent, true, false, false }, + { "console", "Console", UIEditorPanelPresentationKind::HostedContent, true, false, false }, + { "project", "Project", UIEditorPanelPresentationKind::HostedContent, false, false, false } + }; + return registry; +} + +UIEditorWorkspaceModel BuildEditorWorkspaceModel() { + UIEditorWorkspaceModel workspace = {}; + workspace.root = BuildUIEditorWorkspaceSplit( + "workspace-root", + UIEditorWorkspaceSplitAxis::Vertical, + 0.75f, + BuildUIEditorWorkspaceSplit( + "workspace-top", + UIEditorWorkspaceSplitAxis::Horizontal, + 0.7875f, + BuildUIEditorWorkspaceSplit( + "workspace-main", + UIEditorWorkspaceSplitAxis::Horizontal, + 0.19047619f, + BuildUIEditorWorkspaceSingleTabStack( + "hierarchy-panel", + "hierarchy", + "Hierarchy", + true), + BuildUIEditorWorkspaceTabStack( + "center-tabs", + { + BuildUIEditorWorkspacePanel( + "scene-panel", + "scene", + "Scene", + false), + BuildUIEditorWorkspacePanel( + "game-panel", + "game", + "Game", + false) + }, + 0u)), + BuildUIEditorWorkspaceSingleTabStack( + "inspector-panel", + "inspector", + "Inspector", + true)), + BuildUIEditorWorkspaceTabStack( + "bottom-tabs", + { + BuildUIEditorWorkspacePanel( + "console-panel", + "console", + "Console", + true), + BuildUIEditorWorkspacePanel( + "project-panel", + "project", + "Project", + false) + }, + 1u)); + workspace.activePanelId = "scene"; + return workspace; +} + +} // namespace XCEngine::UI::Editor::App::CompositionSupport diff --git a/new_editor/app/Composition/EditorShellAssetMenu.cpp b/new_editor/app/Composition/EditorShellAssetMenu.cpp new file mode 100644 index 00000000..2cec6df1 --- /dev/null +++ b/new_editor/app/Composition/EditorShellAssetMenu.cpp @@ -0,0 +1,167 @@ +#include "EditorShellAssetBuilderSupport.h" + +#include + +namespace XCEngine::UI::Editor::App::CompositionSupport { + +namespace { + +UIEditorMenuItemDescriptor BuildCommandItem( + std::string itemId, + std::string label, + std::string commandId, + UIEditorMenuCheckedStateBinding checkedState = {}) { + UIEditorMenuItemDescriptor item = {}; + item.kind = UIEditorMenuItemKind::Command; + item.itemId = std::move(itemId); + item.label = std::move(label); + item.commandId = std::move(commandId); + item.checkedState = std::move(checkedState); + return item; +} + +UIEditorMenuItemDescriptor BuildSeparatorItem(std::string itemId) { + UIEditorMenuItemDescriptor item = {}; + item.kind = UIEditorMenuItemKind::Separator; + item.itemId = std::move(itemId); + return item; +} + +UIEditorMenuItemDescriptor BuildSubmenuItem( + std::string itemId, + std::string label, + std::vector children) { + UIEditorMenuItemDescriptor item = {}; + item.kind = UIEditorMenuItemKind::Submenu; + item.itemId = std::move(itemId); + item.label = std::move(label); + item.children = std::move(children); + return item; +} + +} // namespace + +UIEditorMenuModel BuildEditorMenuModel() { + UIEditorMenuModel model = {}; + + UIEditorMenuDescriptor fileMenu = {}; + fileMenu.menuId = "file"; + fileMenu.label = "File"; + fileMenu.items = { + BuildCommandItem("file-new-project", "New Project...", "file.new_project"), + BuildCommandItem("file-open-project", "Open Project...", "file.open_project"), + BuildCommandItem("file-save-project", "Save Project", "file.save_project"), + BuildSeparatorItem("file-separator-project"), + BuildCommandItem("file-new-scene", "New Scene", "file.new_scene"), + BuildCommandItem("file-open-scene", "Open Scene", "file.open_scene"), + BuildCommandItem("file-save-scene", "Save Scene", "file.save_scene"), + BuildCommandItem("file-save-scene-as", "Save Scene As...", "file.save_scene_as"), + BuildSeparatorItem("file-separator-exit"), + BuildCommandItem("file-exit", "Exit", "file.exit") + }; + + UIEditorMenuDescriptor editMenu = {}; + editMenu.menuId = "edit"; + editMenu.label = "Edit"; + editMenu.items = { + BuildCommandItem("edit-undo", "Undo", "edit.undo"), + BuildCommandItem("edit-redo", "Redo", "edit.redo"), + BuildSeparatorItem("edit-separator-history"), + BuildCommandItem("edit-cut", "Cut", "edit.cut"), + BuildCommandItem("edit-copy", "Copy", "edit.copy"), + BuildCommandItem("edit-paste", "Paste", "edit.paste"), + BuildCommandItem("edit-duplicate", "Duplicate", "edit.duplicate"), + BuildCommandItem("edit-delete", "Delete", "edit.delete"), + BuildCommandItem("edit-rename", "Rename", "edit.rename") + }; + + UIEditorMenuDescriptor assetsMenu = {}; + assetsMenu.menuId = "assets"; + assetsMenu.label = "Assets"; + assetsMenu.items = { + BuildCommandItem("assets-reimport-selected", "Reimport Selected Asset", "assets.reimport_selected"), + BuildCommandItem("assets-reimport-all", "Reimport All Assets", "assets.reimport_all"), + BuildSeparatorItem("assets-separator-clear"), + BuildCommandItem("assets-clear-library", "Clear Library", "assets.clear_library") + }; + + UIEditorMenuDescriptor runMenu = {}; + runMenu.menuId = "run"; + runMenu.label = "Run"; + runMenu.items = { + BuildCommandItem("run-play", "Play", "run.play"), + BuildCommandItem("run-pause", "Pause", "run.pause"), + BuildCommandItem("run-step", "Step", "run.step") + }; + + UIEditorMenuDescriptor scriptsMenu = {}; + scriptsMenu.menuId = "scripts"; + scriptsMenu.label = "Scripts"; + scriptsMenu.items = { + BuildCommandItem("scripts-rebuild", "Rebuild Script Assemblies", "scripts.rebuild") + }; + + UIEditorMenuCheckedStateBinding hierarchyActive = { + UIEditorMenuCheckedStateSource::PanelActive, + "hierarchy" + }; + UIEditorMenuCheckedStateBinding sceneActive = { + UIEditorMenuCheckedStateSource::PanelActive, + "scene" + }; + UIEditorMenuCheckedStateBinding gameActive = { + UIEditorMenuCheckedStateSource::PanelActive, + "game" + }; + UIEditorMenuCheckedStateBinding inspectorActive = { + UIEditorMenuCheckedStateSource::PanelActive, + "inspector" + }; + UIEditorMenuCheckedStateBinding consoleActive = { + UIEditorMenuCheckedStateSource::PanelActive, + "console" + }; + UIEditorMenuCheckedStateBinding projectActive = { + UIEditorMenuCheckedStateSource::PanelActive, + "project" + }; + + UIEditorMenuDescriptor viewMenu = {}; + viewMenu.menuId = "view"; + viewMenu.label = "View"; + viewMenu.items = { + BuildCommandItem("view-reset-layout", "Reset Layout", "view.reset_layout"), + BuildSeparatorItem("view-separator-panels"), + BuildSubmenuItem( + "view-panels", + "Panels", + { + BuildCommandItem("view-panel-hierarchy", "Hierarchy", "view.activate_hierarchy", hierarchyActive), + BuildCommandItem("view-panel-scene", "Scene", "view.activate_scene", sceneActive), + BuildCommandItem("view-panel-game", "Game", "view.activate_game", gameActive), + BuildCommandItem("view-panel-inspector", "Inspector", "view.activate_inspector", inspectorActive), + BuildCommandItem("view-panel-console", "Console", "view.activate_console", consoleActive), + BuildCommandItem("view-panel-project", "Project", "view.activate_project", projectActive) + }) + }; + + UIEditorMenuDescriptor helpMenu = {}; + helpMenu.menuId = "help"; + helpMenu.label = "Help"; + helpMenu.items = { + BuildCommandItem("help-about", "About", "help.about") + }; + + model.menus = { + std::move(fileMenu), + std::move(editMenu), + std::move(assetsMenu), + std::move(runMenu), + std::move(scriptsMenu), + std::move(viewMenu), + std::move(helpMenu) + }; + return model; +} + +} // namespace XCEngine::UI::Editor::App::CompositionSupport diff --git a/new_editor/app/Composition/EditorShellRuntime.cpp b/new_editor/app/Composition/EditorShellRuntime.cpp new file mode 100644 index 00000000..8b79df9f --- /dev/null +++ b/new_editor/app/Composition/EditorShellRuntime.cpp @@ -0,0 +1,115 @@ +#include "Composition/EditorShellRuntime.h" + +namespace XCEngine::UI::Editor::App { + +void EditorShellRuntime::Initialize( + const std::filesystem::path& repoRoot, + Host::NativeRenderer& renderer) { + m_builtInIcons.Initialize(renderer); + m_hierarchyPanel.SetBuiltInIcons(&m_builtInIcons); + m_projectPanel.SetBuiltInIcons(&m_builtInIcons); + m_projectPanel.SetTextMeasurer(&renderer); + m_hierarchyPanel.Initialize(); + m_projectPanel.Initialize(repoRoot); +} + +void EditorShellRuntime::AttachViewportWindowRenderer(Host::D3D12WindowRenderer& renderer) { + m_viewportHostService.AttachWindowRenderer(renderer); +} + +void EditorShellRuntime::DetachViewportWindowRenderer() { + m_viewportHostService.DetachWindowRenderer(); +} + +void EditorShellRuntime::SetViewportSurfacePresentationEnabled(bool enabled) { + m_viewportHostService.SetSurfacePresentationEnabled(enabled); +} + +void EditorShellRuntime::Shutdown() { + m_shellFrame = {}; + m_shellInteractionState = {}; + m_traceEntries.clear(); + m_viewportHostService.Shutdown(); + m_builtInIcons.Shutdown(); +} + +void EditorShellRuntime::ResetInteractionState() { + m_shellFrame = {}; + m_shellInteractionState = {}; + m_traceEntries.clear(); + m_hierarchyPanel.ResetInteractionState(); + m_projectPanel.ResetInteractionState(); +} + +const UIEditorShellInteractionFrame& EditorShellRuntime::GetShellFrame() const { + return m_shellFrame; +} + +const UIEditorShellInteractionState& EditorShellRuntime::GetShellInteractionState() const { + return m_shellInteractionState; +} + +const std::vector& EditorShellRuntime::GetTraceEntries() const { + return m_traceEntries; +} + +const std::vector& EditorShellRuntime::GetHierarchyPanelEvents() const { + return m_hierarchyPanel.GetFrameEvents(); +} + +const std::vector& EditorShellRuntime::GetProjectPanelEvents() const { + return m_projectPanel.GetFrameEvents(); +} + +const std::string& EditorShellRuntime::GetBuiltInIconError() const { + return m_builtInIcons.GetLastError(); +} + +ProjectPanel::CursorKind EditorShellRuntime::GetHostedContentCursorKind() const { + return m_projectPanel.GetCursorKind(); +} + +Widgets::UIEditorDockHostCursorKind EditorShellRuntime::GetDockCursorKind() const { + return Widgets::ResolveUIEditorDockHostCursorKind( + m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout); +} + +bool EditorShellRuntime::WantsHostPointerCapture() const { + return m_hierarchyPanel.WantsHostPointerCapture() || + m_projectPanel.WantsHostPointerCapture(); +} + +bool EditorShellRuntime::WantsHostPointerRelease() const { + return (m_hierarchyPanel.WantsHostPointerRelease() || + m_projectPanel.WantsHostPointerRelease()) && + !HasHostedContentCapture(); +} + +bool EditorShellRuntime::HasHostedContentCapture() const { + return m_hierarchyPanel.HasActivePointerCapture() || + m_projectPanel.HasActivePointerCapture(); +} + +bool EditorShellRuntime::HasShellInteractiveCapture() const { + if (m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) { + return true; + } + + if (!m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.activeTabDragNodeId.empty()) { + return true; + } + + for (const auto& panelState : m_shellInteractionState.workspaceInteractionState.composeState.panelStates) { + if (panelState.viewportShellState.inputBridgeState.captured) { + return true; + } + } + + return false; +} + +bool EditorShellRuntime::HasInteractiveCapture() const { + return HasShellInteractiveCapture() || HasHostedContentCapture(); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Workspace/EditorWorkspace.h b/new_editor/app/Composition/EditorShellRuntime.h similarity index 56% rename from new_editor/app/Workspace/EditorWorkspace.h rename to new_editor/app/Composition/EditorShellRuntime.h index 12f6e0b6..7b9aa7c1 100644 --- a/new_editor/app/Workspace/EditorWorkspace.h +++ b/new_editor/app/Composition/EditorShellRuntime.h @@ -1,16 +1,16 @@ #pragma once -#include "Core/ProductEditorContext.h" -#include "Panels/ProductConsolePanel.h" -#include "Icons/ProductBuiltInIcons.h" -#include "Panels/ProductHierarchyPanel.h" -#include "Panels/ProductInspectorPanel.h" -#include "Panels/ProductProjectPanel.h" -#include "Viewport/ProductViewportHostService.h" -#include "Workspace/ProductEditorWorkspaceEventRouter.h" +#include "State/EditorContext.h" +#include "Features/Console/ConsolePanel.h" +#include "Rendering/Assets/BuiltInIcons.h" +#include "Features/Hierarchy/HierarchyPanel.h" +#include "Features/Inspector/InspectorPanel.h" +#include "Features/Project/ProjectPanel.h" +#include "Rendering/Viewport/ViewportHostService.h" +#include "Composition/WorkspaceEventSync.h" -#include -#include +#include +#include #include @@ -23,7 +23,7 @@ namespace XCEngine::UI::Editor::App { -class ProductEditorWorkspace { +class EditorShellRuntime { public: void Initialize( const std::filesystem::path& repoRoot, @@ -35,24 +35,24 @@ public: void SetViewportSurfacePresentationEnabled(bool enabled); void Update( - ProductEditorContext& context, + EditorContext& context, UIEditorWorkspaceController& workspaceController, const ::XCEngine::UI::UIRect& bounds, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, std::string_view captureText, - ProductEditorShellVariant shellVariant = ProductEditorShellVariant::Primary); + EditorShellVariant shellVariant = EditorShellVariant::Primary); void RenderRequestedViewports( const ::XCEngine::Rendering::RenderContext& renderContext); void Append(::XCEngine::UI::UIDrawList& drawList) const; const UIEditorShellInteractionFrame& GetShellFrame() const; const UIEditorShellInteractionState& GetShellInteractionState() const; - const std::vector& GetTraceEntries() const; - const std::vector& GetHierarchyPanelEvents() const; - const std::vector& GetProjectPanelEvents() const; + const std::vector& GetTraceEntries() const; + const std::vector& GetHierarchyPanelEvents() const; + const std::vector& GetProjectPanelEvents() const; const std::string& GetBuiltInIconError() const; - ProductProjectPanel::CursorKind GetHostedContentCursorKind() const; + ProjectPanel::CursorKind GetHostedContentCursorKind() const; Widgets::UIEditorDockHostCursorKind GetDockCursorKind() const; bool WantsHostPointerCapture() const; bool WantsHostPointerRelease() const; @@ -61,15 +61,17 @@ public: bool HasInteractiveCapture() const; private: - ProductViewportHostService m_viewportHostService = {}; - ProductBuiltInIcons m_builtInIcons = {}; - ProductConsolePanel m_consolePanel = {}; - ProductHierarchyPanel m_hierarchyPanel = {}; - ProductInspectorPanel m_inspectorPanel = {}; - ProductProjectPanel m_projectPanel = {}; + ViewportHostService m_viewportHostService = {}; + BuiltInIcons m_builtInIcons = {}; + ConsolePanel m_consolePanel = {}; + HierarchyPanel m_hierarchyPanel = {}; + InspectorPanel m_inspectorPanel = {}; + ProjectPanel m_projectPanel = {}; UIEditorShellInteractionState m_shellInteractionState = {}; UIEditorShellInteractionFrame m_shellFrame = {}; - std::vector m_traceEntries = {}; + std::vector m_traceEntries = {}; }; } // namespace XCEngine::UI::Editor::App + + diff --git a/new_editor/app/Composition/EditorShellRuntimeRendering.cpp b/new_editor/app/Composition/EditorShellRuntimeRendering.cpp new file mode 100644 index 00000000..982502e1 --- /dev/null +++ b/new_editor/app/Composition/EditorShellRuntimeRendering.cpp @@ -0,0 +1,81 @@ +#include "Composition/EditorShellRuntimeSupport.h" + +#include + +#include + +namespace XCEngine::UI::Editor::App::RuntimeSupport { + +using ::XCEngine::UI::UIDrawList; + +UIEditorShellComposeModel BuildShellComposeModelFromFrame( + const UIEditorShellInteractionFrame& frame) { + UIEditorShellComposeModel model = {}; + model.menuBarItems = frame.request.menuBarItems; + model.toolbarButtons = frame.model.toolbarButtons; + model.statusSegments = frame.model.statusSegments; + model.workspacePresentations = frame.model.workspacePresentations; + return model; +} + +void AppendShellPopups( + UIDrawList& drawList, + const UIEditorShellInteractionFrame& frame, + const UIEditorShellInteractionPalette& palette, + const UIEditorShellInteractionMetrics& metrics) { + const std::size_t popupCount = + (std::min)(frame.request.popupRequests.size(), frame.popupFrames.size()); + for (std::size_t index = 0; index < popupCount; ++index) { + const UIEditorShellInteractionPopupRequest& popupRequest = + frame.request.popupRequests[index]; + const UIEditorShellInteractionPopupFrame& popupFrame = + frame.popupFrames[index]; + Widgets::AppendUIEditorMenuPopupBackground( + drawList, + popupRequest.layout, + popupRequest.widgetItems, + popupFrame.popupState, + palette.popupPalette, + metrics.popupMetrics); + Widgets::AppendUIEditorMenuPopupForeground( + drawList, + popupRequest.layout, + popupRequest.widgetItems, + popupFrame.popupState, + palette.popupPalette, + metrics.popupMetrics); + } +} + +} // namespace XCEngine::UI::Editor::App::RuntimeSupport + +namespace XCEngine::UI::Editor::App { + +using ::XCEngine::UI::UIDrawList; +using namespace RuntimeSupport; + +void EditorShellRuntime::RenderRequestedViewports( + const ::XCEngine::Rendering::RenderContext& renderContext) { + m_viewportHostService.RenderRequestedViewports(renderContext); +} + +void EditorShellRuntime::Append(UIDrawList& drawList) const { + const auto& metrics = ResolveUIEditorShellInteractionMetrics(); + const auto& palette = ResolveUIEditorShellInteractionPalette(); + const UIEditorShellComposeModel shellComposeModel = + BuildShellComposeModelFromFrame(m_shellFrame); + AppendUIEditorShellCompose( + drawList, + m_shellFrame.shellFrame, + shellComposeModel, + m_shellInteractionState.composeState, + palette.shellPalette, + metrics.shellMetrics); + m_consolePanel.Append(drawList); + m_hierarchyPanel.Append(drawList); + m_inspectorPanel.Append(drawList); + m_projectPanel.Append(drawList); + AppendShellPopups(drawList, m_shellFrame, palette, metrics); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Composition/EditorShellRuntimeSupport.h b/new_editor/app/Composition/EditorShellRuntimeSupport.h new file mode 100644 index 00000000..e479930d --- /dev/null +++ b/new_editor/app/Composition/EditorShellRuntimeSupport.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Composition/EditorShellRuntime.h" + +#include + +namespace XCEngine::UI::Editor::App::RuntimeSupport { + +void ApplyViewportFramesToShellFrame( + UIEditorShellInteractionFrame& shellFrame, + ViewportHostService& viewportHostService); + +std::vector<::XCEngine::UI::UIInputEvent> FilterShellInputEventsForHostedContentCapture( + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); + +bool ShouldHostedContentYieldPointerStream( + const UIEditorShellInteractionFrame& shellFrame, + bool shellInteractiveCaptureActive); + +std::vector<::XCEngine::UI::UIInputEvent> FilterHostedContentInputEventsForShellOwnership( + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + bool shellOwnsPointerStream); + +UIEditorShellComposeModel BuildShellComposeModelFromFrame( + const UIEditorShellInteractionFrame& frame); + +void AppendShellPopups( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorShellInteractionFrame& frame, + const UIEditorShellInteractionPalette& palette, + const UIEditorShellInteractionMetrics& metrics); + +} // namespace XCEngine::UI::Editor::App::RuntimeSupport diff --git a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp new file mode 100644 index 00000000..3be5d9ce --- /dev/null +++ b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp @@ -0,0 +1,157 @@ +#include "Composition/EditorShellRuntimeSupport.h" + +#include + +namespace XCEngine::UI::Editor::App::RuntimeSupport { + +namespace { + +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using Widgets::UIEditorDockHostHitTargetKind; + +bool IsPointerInputEventType(UIInputEventType type) { + switch (type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + return true; + default: + return false; + } +} + +} // namespace + +std::vector FilterShellInputEventsForHostedContentCapture( + const std::vector& inputEvents) { + std::vector filteredEvents = {}; + filteredEvents.reserve(inputEvents.size()); + for (const UIInputEvent& event : inputEvents) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + break; + default: + filteredEvents.push_back(event); + break; + } + } + return filteredEvents; +} + +bool ShouldHostedContentYieldPointerStream( + const UIEditorShellInteractionFrame& shellFrame, + bool shellInteractiveCaptureActive) { + if (shellInteractiveCaptureActive || + shellFrame.result.requestPointerCapture || + shellFrame.result.releasePointerCapture) { + return true; + } + + return shellFrame.result.workspaceResult.dockHostResult.hitTarget.kind == + UIEditorDockHostHitTargetKind::SplitterHandle; +} + +std::vector FilterHostedContentInputEventsForShellOwnership( + const std::vector& inputEvents, + bool shellOwnsPointerStream) { + if (!shellOwnsPointerStream) { + return inputEvents; + } + + std::vector filteredEvents = {}; + filteredEvents.reserve(inputEvents.size() + 1u); + + bool strippedPointerInput = false; + UIInputEvent lastPointerEvent = {}; + for (const UIInputEvent& event : inputEvents) { + if (IsPointerInputEventType(event.type)) { + strippedPointerInput = true; + lastPointerEvent = event; + continue; + } + + filteredEvents.push_back(event); + } + + if (strippedPointerInput) { + UIInputEvent leaveEvent = {}; + leaveEvent.type = UIInputEventType::PointerLeave; + leaveEvent.position = lastPointerEvent.position; + leaveEvent.modifiers = lastPointerEvent.modifiers; + filteredEvents.push_back(leaveEvent); + } + + return filteredEvents; +} + +} // namespace XCEngine::UI::Editor::App::RuntimeSupport + +namespace XCEngine::UI::Editor::App { + +using namespace RuntimeSupport; + +void EditorShellRuntime::Update( + EditorContext& context, + UIEditorWorkspaceController& workspaceController, + const ::XCEngine::UI::UIRect& bounds, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + std::string_view captureText, + EditorShellVariant shellVariant) { + const auto& metrics = ResolveUIEditorShellInteractionMetrics(); + context.SyncSessionFromWorkspace(workspaceController); + UIEditorShellInteractionDefinition definition = + context.BuildShellDefinition(workspaceController, captureText, shellVariant); + m_viewportHostService.BeginFrame(); + const std::vector<::XCEngine::UI::UIInputEvent> shellEvents = + HasHostedContentCapture() + ? FilterShellInputEventsForHostedContentCapture(inputEvents) + : inputEvents; + + m_shellFrame = UpdateUIEditorShellInteraction( + m_shellInteractionState, + workspaceController, + bounds, + definition, + shellEvents, + context.GetShellServices(), + metrics); + const bool shellOwnsHostedContentPointerStream = + ShouldHostedContentYieldPointerStream(m_shellFrame, HasShellInteractiveCapture()); + const std::vector<::XCEngine::UI::UIInputEvent> hostedContentEvents = + FilterHostedContentInputEventsForShellOwnership( + inputEvents, + shellOwnsHostedContentPointerStream); + ApplyViewportFramesToShellFrame(m_shellFrame, m_viewportHostService); + context.SyncSessionFromWorkspace(workspaceController); + context.UpdateStatusFromShellResult(workspaceController, m_shellFrame.result); + + const std::string& activePanelId = workspaceController.GetWorkspace().activePanelId; + m_hierarchyPanel.Update( + m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, + hostedContentEvents, + !m_shellFrame.result.workspaceInputSuppressed, + activePanelId == "hierarchy"); + m_projectPanel.Update( + m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, + hostedContentEvents, + !m_shellFrame.result.workspaceInputSuppressed, + activePanelId == "project"); + m_traceEntries = SyncWorkspaceEvents(context, *this); + m_inspectorPanel.Update( + context.GetSession(), + m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame); + m_consolePanel.Update( + context.GetSession(), + m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Composition/EditorShellRuntimeViewport.cpp b/new_editor/app/Composition/EditorShellRuntimeViewport.cpp new file mode 100644 index 00000000..9fac20a2 --- /dev/null +++ b/new_editor/app/Composition/EditorShellRuntimeViewport.cpp @@ -0,0 +1,82 @@ +#include "Composition/EditorShellRuntimeSupport.h" + +namespace XCEngine::UI::Editor::App::RuntimeSupport { + +namespace { + +bool IsViewportPanel(std::string_view panelId) { + return panelId == "scene" || panelId == "game"; +} + +ViewportKind ResolveViewportKind(std::string_view panelId) { + return panelId == "game" + ? ViewportKind::Game + : ViewportKind::Scene; +} + +void ApplyViewportFrameToPresentation( + const ViewportFrame& viewportFrame, + UIEditorWorkspacePanelPresentationModel& presentation) { + presentation.viewportShellModel.frame.texture = viewportFrame.texture; + presentation.viewportShellModel.frame.requestedSize = viewportFrame.requestedSize; + presentation.viewportShellModel.frame.presentedSize = viewportFrame.renderSize; + presentation.viewportShellModel.frame.hasTexture = viewportFrame.hasTexture; + presentation.viewportShellModel.frame.statusText = viewportFrame.statusText; +} + +void ApplyViewportFrameToShellModel( + const ViewportFrame& viewportFrame, + UIEditorViewportShellModel& shellModel) { + shellModel.frame.texture = viewportFrame.texture; + shellModel.frame.requestedSize = viewportFrame.requestedSize; + shellModel.frame.presentedSize = viewportFrame.renderSize; + shellModel.frame.hasTexture = viewportFrame.hasTexture; + shellModel.frame.statusText = viewportFrame.statusText; +} + +UIEditorWorkspacePanelPresentationModel* FindMutableWorkspacePresentation( + std::vector& presentations, + std::string_view panelId) { + for (UIEditorWorkspacePanelPresentationModel& presentation : presentations) { + if (presentation.panelId == panelId) { + return &presentation; + } + } + + return nullptr; +} + +} // namespace + +void ApplyViewportFramesToShellFrame( + UIEditorShellInteractionFrame& shellFrame, + ViewportHostService& viewportHostService) { + auto applyToViewportFrames = + [&](std::vector& viewportFrames) { + for (UIEditorWorkspaceViewportComposeFrame& viewportComposeFrame : viewportFrames) { + if (!IsViewportPanel(viewportComposeFrame.panelId)) { + continue; + } + + const ViewportFrame viewportFrame = + viewportHostService.RequestViewport( + ResolveViewportKind(viewportComposeFrame.panelId), + viewportComposeFrame.viewportShellFrame.requestedViewportSize); + ApplyViewportFrameToShellModel( + viewportFrame, + viewportComposeFrame.viewportShellModel); + if (UIEditorWorkspacePanelPresentationModel* presentation = + FindMutableWorkspacePresentation( + shellFrame.model.workspacePresentations, + viewportComposeFrame.panelId); + presentation != nullptr) { + ApplyViewportFrameToPresentation(viewportFrame, *presentation); + } + } + }; + + applyToViewportFrames(shellFrame.workspaceInteractionFrame.composeFrame.viewportFrames); + applyToViewportFrames(shellFrame.shellFrame.workspaceFrame.viewportFrames); +} + +} // namespace XCEngine::UI::Editor::App::RuntimeSupport diff --git a/new_editor/app/Composition/WorkspaceEventSync.cpp b/new_editor/app/Composition/WorkspaceEventSync.cpp new file mode 100644 index 00000000..e58df0bb --- /dev/null +++ b/new_editor/app/Composition/WorkspaceEventSync.cpp @@ -0,0 +1,180 @@ +#include "Composition/WorkspaceEventSync.h" + +#include "State/EditorContext.h" +#include "Features/Hierarchy/HierarchyPanel.h" +#include "Features/Project/ProjectPanel.h" +#include "Composition/EditorShellRuntime.h" + +#include +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +std::string DescribeProjectPanelEvent(const ProjectPanel::Event& event) { + std::ostringstream stream = {}; + switch (event.kind) { + case ProjectPanel::EventKind::AssetSelected: + stream << "AssetSelected"; + break; + case ProjectPanel::EventKind::AssetSelectionCleared: + stream << "AssetSelectionCleared"; + break; + case ProjectPanel::EventKind::FolderNavigated: + stream << "FolderNavigated"; + break; + case ProjectPanel::EventKind::AssetOpened: + stream << "AssetOpened"; + break; + case ProjectPanel::EventKind::ContextMenuRequested: + stream << "ContextMenuRequested"; + break; + case ProjectPanel::EventKind::None: + default: + stream << "None"; + break; + } + + stream << " source="; + switch (event.source) { + case ProjectPanel::EventSource::Tree: + stream << "Tree"; + break; + case ProjectPanel::EventSource::Breadcrumb: + stream << "Breadcrumb"; + break; + case ProjectPanel::EventSource::GridPrimary: + stream << "GridPrimary"; + break; + case ProjectPanel::EventSource::GridDoubleClick: + stream << "GridDoubleClick"; + break; + case ProjectPanel::EventSource::GridSecondary: + stream << "GridSecondary"; + break; + case ProjectPanel::EventSource::Background: + stream << "Background"; + break; + case ProjectPanel::EventSource::None: + default: + stream << "None"; + break; + } + + if (!event.itemId.empty()) { + stream << " item=" << event.itemId; + } + if (!event.displayName.empty()) { + stream << " label=" << event.displayName; + } + return stream.str(); +} + +std::string DescribeHierarchyPanelEvent(const HierarchyPanel::Event& event) { + std::ostringstream stream = {}; + switch (event.kind) { + case HierarchyPanel::EventKind::SelectionChanged: + stream << "SelectionChanged"; + break; + case HierarchyPanel::EventKind::Reparented: + stream << "Reparented"; + break; + case HierarchyPanel::EventKind::MovedToRoot: + stream << "MovedToRoot"; + break; + case HierarchyPanel::EventKind::RenameRequested: + stream << "RenameRequested"; + break; + case HierarchyPanel::EventKind::None: + default: + stream << "None"; + break; + } + + if (!event.itemId.empty()) { + stream << " item=" << event.itemId; + } + if (!event.targetItemId.empty()) { + stream << " target=" << event.targetItemId; + } + if (!event.label.empty()) { + stream << " label=" << event.label; + } + return stream.str(); +} + +void ApplyHierarchySelection( + EditorContext& context, + const HierarchyPanel::Event& event) { + if (event.kind != HierarchyPanel::EventKind::SelectionChanged) { + return; + } + + if (event.itemId.empty()) { + context.ClearSelection(); + return; + } + + EditorSelectionState selection = {}; + selection.kind = EditorSelectionKind::HierarchyNode; + selection.itemId = event.itemId; + selection.displayName = event.label.empty() ? event.itemId : event.label; + context.SetSelection(std::move(selection)); +} + +void ApplyProjectSelection( + EditorContext& context, + const ProjectPanel::Event& event) { + switch (event.kind) { + case ProjectPanel::EventKind::AssetSelected: { + EditorSelectionState selection = {}; + selection.kind = EditorSelectionKind::ProjectItem; + selection.itemId = event.itemId; + selection.displayName = event.displayName.empty() ? event.itemId : event.displayName; + selection.absolutePath = event.absolutePath; + selection.directory = event.directory; + context.SetSelection(std::move(selection)); + return; + } + case ProjectPanel::EventKind::AssetSelectionCleared: + case ProjectPanel::EventKind::FolderNavigated: + if (context.GetSession().selection.kind == EditorSelectionKind::ProjectItem) { + context.ClearSelection(); + } + return; + case ProjectPanel::EventKind::AssetOpened: + case ProjectPanel::EventKind::ContextMenuRequested: + case ProjectPanel::EventKind::None: + default: + return; + } +} + +} // namespace + +std::vector SyncWorkspaceEvents( + EditorContext& context, + const EditorShellRuntime& runtime) { + std::vector entries = {}; + + for (const HierarchyPanel::Event& event : runtime.GetHierarchyPanelEvents()) { + ApplyHierarchySelection(context, event); + const std::string message = DescribeHierarchyPanelEvent(event); + context.SetStatus("Hierarchy", message); + entries.push_back(WorkspaceTraceEntry{ "hierarchy", std::move(message) }); + } + + for (const ProjectPanel::Event& event : runtime.GetProjectPanelEvents()) { + ApplyProjectSelection(context, event); + const std::string message = DescribeProjectPanelEvent(event); + context.SetStatus("Project", message); + entries.push_back(WorkspaceTraceEntry{ "project", std::move(message) }); + } + + return entries; +} + +} // namespace XCEngine::UI::Editor::App + + diff --git a/new_editor/app/Composition/WorkspaceEventSync.h b/new_editor/app/Composition/WorkspaceEventSync.h new file mode 100644 index 00000000..88c074d8 --- /dev/null +++ b/new_editor/app/Composition/WorkspaceEventSync.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace XCEngine::UI::Editor::App { + +class EditorContext; +class EditorShellRuntime; + +struct WorkspaceTraceEntry { + std::string channel = {}; + std::string message = {}; +}; + +std::vector SyncWorkspaceEvents( + EditorContext& context, + const EditorShellRuntime& runtime); + +} // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Core/EditorSession.cpp b/new_editor/app/Core/EditorSession.cpp deleted file mode 100644 index 3e651422..00000000 --- a/new_editor/app/Core/EditorSession.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "ProductEditorSession.h" - -namespace XCEngine::UI::Editor::App { - -std::string_view GetProductEditorRuntimeModeName(ProductEditorRuntimeMode mode) { - switch (mode) { - case ProductEditorRuntimeMode::Edit: - return "Edit"; - case ProductEditorRuntimeMode::Play: - return "Play"; - case ProductEditorRuntimeMode::Paused: - return "Paused"; - default: - return "Unknown"; - } -} - -std::string_view GetProductEditorActionRouteName(ProductEditorActionRoute route) { - switch (route) { - case ProductEditorActionRoute::Hierarchy: - return "Hierarchy"; - case ProductEditorActionRoute::Project: - return "Project"; - case ProductEditorActionRoute::Inspector: - return "Inspector"; - case ProductEditorActionRoute::Console: - return "Console"; - case ProductEditorActionRoute::Scene: - return "Scene"; - case ProductEditorActionRoute::Game: - return "Game"; - case ProductEditorActionRoute::None: - default: - return "None"; - } -} - -std::string_view GetProductEditorSelectionKindName(ProductEditorSelectionKind kind) { - switch (kind) { - case ProductEditorSelectionKind::HierarchyNode: - return "HierarchyNode"; - case ProductEditorSelectionKind::ProjectItem: - return "ProjectItem"; - case ProductEditorSelectionKind::None: - default: - return "None"; - } -} - -ProductEditorActionRoute ResolveProductEditorActionRoute(std::string_view panelId) { - if (panelId == "hierarchy") { - return ProductEditorActionRoute::Hierarchy; - } - if (panelId == "project") { - return ProductEditorActionRoute::Project; - } - if (panelId == "inspector") { - return ProductEditorActionRoute::Inspector; - } - if (panelId == "console") { - return ProductEditorActionRoute::Console; - } - if (panelId == "scene") { - return ProductEditorActionRoute::Scene; - } - if (panelId == "game") { - return ProductEditorActionRoute::Game; - } - return ProductEditorActionRoute::None; -} - -void SyncProductEditorSessionFromWorkspace( - ProductEditorSession& session, - const UIEditorWorkspaceController& controller) { - session.activePanelId = controller.GetWorkspace().activePanelId; - session.activeRoute = ResolveProductEditorActionRoute(session.activePanelId); -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Core/EditorSession.h b/new_editor/app/Core/EditorSession.h deleted file mode 100644 index 70261513..00000000 --- a/new_editor/app/Core/EditorSession.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - -namespace XCEngine::UI::Editor::App { - -enum class ProductEditorRuntimeMode : std::uint8_t { - Edit = 0, - Play, - Paused -}; - -enum class ProductEditorActionRoute : std::uint8_t { - None = 0, - Hierarchy, - Project, - Inspector, - Console, - Scene, - Game -}; - -enum class ProductEditorSelectionKind : std::uint8_t { - None = 0, - HierarchyNode, - ProjectItem -}; - -struct ProductEditorSelectionState { - ProductEditorSelectionKind kind = ProductEditorSelectionKind::None; - std::string itemId = {}; - std::string displayName = {}; - std::filesystem::path absolutePath = {}; - bool directory = false; -}; - -struct ProductEditorConsoleEntry { - std::string channel = {}; - std::string message = {}; -}; - -struct ProductEditorSession { - std::filesystem::path repoRoot = {}; - std::filesystem::path projectRoot = {}; - std::string activePanelId = {}; - ProductEditorRuntimeMode runtimeMode = ProductEditorRuntimeMode::Edit; - ProductEditorActionRoute activeRoute = ProductEditorActionRoute::None; - ProductEditorSelectionState selection = {}; - std::vector consoleEntries = {}; -}; - -std::string_view GetProductEditorRuntimeModeName(ProductEditorRuntimeMode mode); -std::string_view GetProductEditorActionRouteName(ProductEditorActionRoute route); -std::string_view GetProductEditorSelectionKindName(ProductEditorSelectionKind kind); - -ProductEditorActionRoute ResolveProductEditorActionRoute(std::string_view panelId); - -void SyncProductEditorSessionFromWorkspace( - ProductEditorSession& session, - const UIEditorWorkspaceController& controller); - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Console/ConsolePanel.cpp b/new_editor/app/Features/Console/ConsolePanel.cpp index 899c1043..edde8f55 100644 --- a/new_editor/app/Features/Console/ConsolePanel.cpp +++ b/new_editor/app/Features/Console/ConsolePanel.cpp @@ -1,4 +1,4 @@ -#include "ProductConsolePanel.h" +#include "ConsolePanel.h" #include @@ -20,9 +20,9 @@ constexpr float kLineHeight = 18.0f; constexpr float kFontSize = 11.0f; constexpr UIColor kSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); -constexpr UIColor kTextColor(0.860f, 0.860f, 0.860f, 1.0f); -constexpr UIColor kChannelColor(0.660f, 0.660f, 0.660f, 1.0f); -constexpr UIColor kEmptyColor(0.580f, 0.580f, 0.580f, 1.0f); +constexpr UIColor kTextColor(0.890f, 0.890f, 0.890f, 1.0f); +constexpr UIColor kChannelColor(0.680f, 0.680f, 0.680f, 1.0f); +constexpr UIColor kEmptyColor(0.620f, 0.620f, 0.620f, 1.0f); float ResolveTextTop(float rectY, float rectHeight, float fontSize) { const float lineHeight = fontSize * 1.6f; @@ -31,7 +31,7 @@ float ResolveTextTop(float rectY, float rectHeight, float fontSize) { } // namespace -const UIEditorPanelContentHostPanelState* ProductConsolePanel::FindMountedConsolePanel( +const UIEditorPanelContentHostPanelState* ConsolePanel::FindMountedConsolePanel( const UIEditorPanelContentHostFrame& contentHostFrame) const { for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { if (panelState.panelId == kConsolePanelId && panelState.mounted) { @@ -42,8 +42,8 @@ const UIEditorPanelContentHostPanelState* ProductConsolePanel::FindMountedConsol return nullptr; } -void ProductConsolePanel::Update( - const ProductEditorSession& session, +void ConsolePanel::Update( + const EditorSession& session, const UIEditorPanelContentHostFrame& contentHostFrame) { const UIEditorPanelContentHostPanelState* panelState = FindMountedConsolePanel(contentHostFrame); @@ -59,7 +59,7 @@ void ProductConsolePanel::Update( m_entries = &session.consoleEntries; } -void ProductConsolePanel::Append(UIDrawList& drawList) const { +void ConsolePanel::Append(UIDrawList& drawList) const { if (!m_visible || m_bounds.width <= 0.0f || m_bounds.height <= 0.0f) { return; } @@ -91,7 +91,7 @@ void ProductConsolePanel::Append(UIDrawList& drawList) const { drawList.PushClipRect(contentRect); float nextY = contentRect.y; for (std::size_t index = firstVisible; index < entryCount; ++index) { - const ProductEditorConsoleEntry& entry = (*m_entries)[index]; + const EditorConsoleEntry& entry = (*m_entries)[index]; const std::string line = entry.channel.empty() ? entry.message : "[" + entry.channel + "] " + entry.message; @@ -107,3 +107,4 @@ void ProductConsolePanel::Append(UIDrawList& drawList) const { } } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Features/Console/ConsolePanel.h b/new_editor/app/Features/Console/ConsolePanel.h index ce373deb..f8216370 100644 --- a/new_editor/app/Features/Console/ConsolePanel.h +++ b/new_editor/app/Features/Console/ConsolePanel.h @@ -1,17 +1,17 @@ #pragma once -#include "Core/ProductEditorSession.h" +#include "State/EditorSession.h" -#include +#include #include namespace XCEngine::UI::Editor::App { -class ProductConsolePanel { +class ConsolePanel { public: void Update( - const ProductEditorSession& session, + const EditorSession& session, const UIEditorPanelContentHostFrame& contentHostFrame); void Append(::XCEngine::UI::UIDrawList& drawList) const; @@ -21,7 +21,8 @@ private: bool m_visible = false; ::XCEngine::UI::UIRect m_bounds = {}; - const std::vector* m_entries = nullptr; + const std::vector* m_entries = nullptr; }; } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Features/Hierarchy/HierarchyModel.cpp b/new_editor/app/Features/Hierarchy/HierarchyModel.cpp index 0c1d9772..addf145a 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyModel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyModel.cpp @@ -1,4 +1,4 @@ -#include "Hierarchy/ProductHierarchyModel.h" +#include "HierarchyModel.h" #include #include @@ -10,14 +10,14 @@ namespace XCEngine::UI::Editor::App { namespace { -const ProductHierarchyNode* FindNodeRecursive( - const std::vector& nodes, +const HierarchyNode* FindNodeRecursive( + const std::vector& nodes, std::string_view nodeId) { - for (const ProductHierarchyNode& node : nodes) { + for (const HierarchyNode& node : nodes) { if (node.nodeId == nodeId) { return &node; } - if (const ProductHierarchyNode* child = + if (const HierarchyNode* child = FindNodeRecursive(node.children, nodeId); child != nullptr) { return child; @@ -26,14 +26,14 @@ const ProductHierarchyNode* FindNodeRecursive( return nullptr; } -ProductHierarchyNode* FindNodeRecursive( - std::vector& nodes, +HierarchyNode* FindNodeRecursive( + std::vector& nodes, std::string_view nodeId) { - for (ProductHierarchyNode& node : nodes) { + for (HierarchyNode& node : nodes) { if (node.nodeId == nodeId) { return &node; } - if (ProductHierarchyNode* child = + if (HierarchyNode* child = FindNodeRecursive(node.children, nodeId); child != nullptr) { return child; @@ -43,11 +43,11 @@ ProductHierarchyNode* FindNodeRecursive( } bool FindNodeParentRecursive( - const std::vector& nodes, + const std::vector& nodes, std::string_view nodeId, - const ProductHierarchyNode*& parent) { - for (const ProductHierarchyNode& node : nodes) { - for (const ProductHierarchyNode& child : node.children) { + const HierarchyNode*& parent) { + for (const HierarchyNode& node : nodes) { + for (const HierarchyNode& child : node.children) { if (child.nodeId == nodeId) { parent = &node; return true; @@ -62,9 +62,9 @@ bool FindNodeParentRecursive( } bool ExtractNodeRecursive( - std::vector& nodes, + std::vector& nodes, std::string_view nodeId, - ProductHierarchyNode& extractedNode) { + HierarchyNode& extractedNode) { for (std::size_t index = 0u; index < nodes.size(); ++index) { if (nodes[index].nodeId == nodeId) { extractedNode = std::move(nodes[index]); @@ -80,11 +80,11 @@ bool ExtractNodeRecursive( } void BuildTreeItemsRecursive( - const std::vector& nodes, + const std::vector& nodes, std::uint32_t depth, const ::XCEngine::UI::UITextureHandle& icon, std::vector& items) { - for (const ProductHierarchyNode& node : nodes) { + for (const HierarchyNode& node : nodes) { Widgets::UIEditorTreeViewItem item = {}; item.itemId = node.nodeId; item.label = node.label; @@ -98,29 +98,29 @@ void BuildTreeItemsRecursive( } // namespace -ProductHierarchyModel ProductHierarchyModel::BuildDefault() { - ProductHierarchyModel model = {}; +HierarchyModel HierarchyModel::BuildDefault() { + HierarchyModel model = {}; model.m_roots = { - ProductHierarchyNode{ "main_camera", "Main Camera", {} }, - ProductHierarchyNode{ "directional_light", "Directional Light", {} }, - ProductHierarchyNode{ + HierarchyNode{ "main_camera", "Main Camera", {} }, + HierarchyNode{ "directional_light", "Directional Light", {} }, + HierarchyNode{ "player", "Player", { - ProductHierarchyNode{ "camera_pivot", "Camera Pivot", {} }, - ProductHierarchyNode{ "player_mesh", "Mesh", {} } + HierarchyNode{ "camera_pivot", "Camera Pivot", {} }, + HierarchyNode{ "player_mesh", "Mesh", {} } } }, - ProductHierarchyNode{ + HierarchyNode{ "environment", "Environment", { - ProductHierarchyNode{ "ground", "Ground", {} }, - ProductHierarchyNode{ + HierarchyNode{ "ground", "Ground", {} }, + HierarchyNode{ "props", "Props", { - ProductHierarchyNode{ "crate_01", "Crate_01", {} }, - ProductHierarchyNode{ "barrel_01", "Barrel_01", {} } + HierarchyNode{ "crate_01", "Crate_01", {} }, + HierarchyNode{ "barrel_01", "Barrel_01", {} } } } } } }; @@ -128,24 +128,24 @@ ProductHierarchyModel ProductHierarchyModel::BuildDefault() { return model; } -bool ProductHierarchyModel::Empty() const { +bool HierarchyModel::Empty() const { return m_roots.empty(); } -bool ProductHierarchyModel::ContainsNode(std::string_view nodeId) const { +bool HierarchyModel::ContainsNode(std::string_view nodeId) const { return FindNode(nodeId) != nullptr; } -const ProductHierarchyNode* ProductHierarchyModel::FindNode(std::string_view nodeId) const { +const HierarchyNode* HierarchyModel::FindNode(std::string_view nodeId) const { return FindNodeRecursive(m_roots, nodeId); } -ProductHierarchyNode* ProductHierarchyModel::FindNode(std::string_view nodeId) { +HierarchyNode* HierarchyModel::FindNode(std::string_view nodeId) { return FindNodeRecursive(m_roots, nodeId); } -std::optional ProductHierarchyModel::GetParentId(std::string_view nodeId) const { - const ProductHierarchyNode* parent = nullptr; +std::optional HierarchyModel::GetParentId(std::string_view nodeId) const { + const HierarchyNode* parent = nullptr; if (!FindNodeParentRecursive(m_roots, nodeId, parent) || parent == nullptr) { return std::nullopt; } @@ -153,8 +153,8 @@ std::optional ProductHierarchyModel::GetParentId(std::string_view n return parent->nodeId; } -bool ProductHierarchyModel::RenameNode(std::string_view nodeId, std::string label) { - ProductHierarchyNode* node = FindNode(nodeId); +bool HierarchyModel::RenameNode(std::string_view nodeId, std::string label) { + HierarchyNode* node = FindNode(nodeId); if (node == nullptr || label.empty() || node->label == label) { return false; } @@ -163,38 +163,38 @@ bool ProductHierarchyModel::RenameNode(std::string_view nodeId, std::string labe return true; } -std::string ProductHierarchyModel::CreateChild( +std::string HierarchyModel::CreateChild( std::string_view parentId, std::string_view label) { - ProductHierarchyNode* parent = FindNode(parentId); + HierarchyNode* parent = FindNode(parentId); if (parent == nullptr) { return {}; } - ProductHierarchyNode node = {}; + HierarchyNode node = {}; node.nodeId = AllocateNodeId(); node.label = label.empty() ? std::string("GameObject") : std::string(label); parent->children.push_back(std::move(node)); return parent->children.back().nodeId; } -bool ProductHierarchyModel::DeleteNode(std::string_view nodeId) { +bool HierarchyModel::DeleteNode(std::string_view nodeId) { if (nodeId.empty()) { return false; } - ProductHierarchyNode removed = {}; + HierarchyNode removed = {}; return ExtractNodeRecursive(m_roots, nodeId, removed); } -bool ProductHierarchyModel::CanReparent( +bool HierarchyModel::CanReparent( std::string_view sourceNodeId, std::string_view targetParentId) const { if (sourceNodeId.empty()) { return false; } - const ProductHierarchyNode* source = FindNode(sourceNodeId); + const HierarchyNode* source = FindNode(sourceNodeId); if (source == nullptr) { return false; } @@ -203,11 +203,11 @@ bool ProductHierarchyModel::CanReparent( return true; } - const ProductHierarchyNode* targetParent = FindNode(targetParentId); + const HierarchyNode* targetParent = FindNode(targetParentId); return CanAdopt(sourceNodeId, targetParent); } -bool ProductHierarchyModel::Reparent( +bool HierarchyModel::Reparent( std::string_view sourceNodeId, std::string_view targetParentId) { if (!CanReparent(sourceNodeId, targetParentId) || targetParentId.empty()) { @@ -219,12 +219,12 @@ bool ProductHierarchyModel::Reparent( return false; } - ProductHierarchyNode movedNode = {}; + HierarchyNode movedNode = {}; if (!ExtractNodeRecursive(m_roots, sourceNodeId, movedNode)) { return false; } - ProductHierarchyNode* targetParent = FindNode(targetParentId); + HierarchyNode* targetParent = FindNode(targetParentId); if (targetParent == nullptr) { return false; } @@ -233,7 +233,7 @@ bool ProductHierarchyModel::Reparent( return true; } -bool ProductHierarchyModel::MoveToRoot(std::string_view sourceNodeId) { +bool HierarchyModel::MoveToRoot(std::string_view sourceNodeId) { if (sourceNodeId.empty() || !ContainsNode(sourceNodeId)) { return false; } @@ -242,7 +242,7 @@ bool ProductHierarchyModel::MoveToRoot(std::string_view sourceNodeId) { return false; } - ProductHierarchyNode movedNode = {}; + HierarchyNode movedNode = {}; if (!ExtractNodeRecursive(m_roots, sourceNodeId, movedNode)) { return false; } @@ -251,27 +251,27 @@ bool ProductHierarchyModel::MoveToRoot(std::string_view sourceNodeId) { return true; } -std::vector ProductHierarchyModel::BuildTreeItems( +std::vector HierarchyModel::BuildTreeItems( const ::XCEngine::UI::UITextureHandle& icon) const { std::vector items = {}; BuildTreeItemsRecursive(m_roots, 0u, icon, items); return items; } -std::string ProductHierarchyModel::AllocateNodeId() { +std::string HierarchyModel::AllocateNodeId() { std::ostringstream stream = {}; stream << "generated_node_" << m_nextGeneratedNodeId++; return stream.str(); } -bool ProductHierarchyModel::CanAdopt( +bool HierarchyModel::CanAdopt( std::string_view sourceNodeId, - const ProductHierarchyNode* targetParent) const { + const HierarchyNode* targetParent) const { if (targetParent == nullptr || sourceNodeId == targetParent->nodeId) { return false; } - const ProductHierarchyNode* source = FindNode(sourceNodeId); + const HierarchyNode* source = FindNode(sourceNodeId); if (source == nullptr) { return false; } @@ -279,10 +279,10 @@ bool ProductHierarchyModel::CanAdopt( return !ContainsDescendant(*source, targetParent->nodeId); } -bool ProductHierarchyModel::ContainsDescendant( - const ProductHierarchyNode& node, +bool HierarchyModel::ContainsDescendant( + const HierarchyNode& node, std::string_view candidateId) const { - for (const ProductHierarchyNode& child : node.children) { + for (const HierarchyNode& child : node.children) { if (child.nodeId == candidateId || ContainsDescendant(child, candidateId)) { return true; } @@ -291,3 +291,6 @@ bool ProductHierarchyModel::ContainsDescendant( } } // namespace XCEngine::UI::Editor::App + + + diff --git a/new_editor/app/Features/Hierarchy/HierarchyModel.h b/new_editor/app/Features/Hierarchy/HierarchyModel.h index 225ee65a..06ba8318 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyModel.h +++ b/new_editor/app/Features/Hierarchy/HierarchyModel.h @@ -11,20 +11,20 @@ namespace XCEngine::UI::Editor::App { -struct ProductHierarchyNode { +struct HierarchyNode { std::string nodeId = {}; std::string label = {}; - std::vector children = {}; + std::vector children = {}; }; -class ProductHierarchyModel { +class HierarchyModel { public: - static ProductHierarchyModel BuildDefault(); + static HierarchyModel BuildDefault(); bool Empty() const; bool ContainsNode(std::string_view nodeId) const; - const ProductHierarchyNode* FindNode(std::string_view nodeId) const; - ProductHierarchyNode* FindNode(std::string_view nodeId); + const HierarchyNode* FindNode(std::string_view nodeId) const; + HierarchyNode* FindNode(std::string_view nodeId); std::optional GetParentId(std::string_view nodeId) const; bool RenameNode(std::string_view nodeId, std::string label); @@ -42,13 +42,14 @@ private: std::string AllocateNodeId(); bool CanAdopt( std::string_view sourceNodeId, - const ProductHierarchyNode* targetParent) const; + const HierarchyNode* targetParent) const; bool ContainsDescendant( - const ProductHierarchyNode& node, + const HierarchyNode& node, std::string_view candidateId) const; - std::vector m_roots = {}; + std::vector m_roots = {}; std::uint64_t m_nextGeneratedNodeId = 1u; }; } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp index dcd7bd06..43805387 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp @@ -1,156 +1,27 @@ -#include "ProductHierarchyPanel.h" - -#include "Icons/ProductBuiltInIcons.h" -#include "Panels/ProductTreeViewStyle.h" - -#include - -#include -#include -#include -#include +#include "HierarchyPanelSupport.h" namespace XCEngine::UI::Editor::App { -namespace { +using namespace HierarchyPanelSupport; -using ::XCEngine::UI::UIColor; -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIPointerButton; -using ::XCEngine::UI::UIRect; -using Widgets::AppendUIEditorTreeViewBackground; -using Widgets::AppendUIEditorTreeViewForeground; -using Widgets::DoesUIEditorTreeViewItemHaveChildren; -using Widgets::HitTestUIEditorTreeView; -using Widgets::IsUIEditorTreeViewPointInside; -using Widgets::UIEditorTreeViewHitTarget; -using Widgets::UIEditorTreeViewHitTargetKind; -using Widgets::UIEditorTreeViewInvalidIndex; - -constexpr std::string_view kHierarchyPanelId = "hierarchy"; -constexpr float kDragThreshold = 4.0f; -constexpr UIColor kDragPreviewColor(0.92f, 0.92f, 0.92f, 0.42f); - -bool ContainsPoint(const UIRect& rect, const UIPoint& point) { - return point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; -} - -float ComputeSquaredDistance(const UIPoint& lhs, const UIPoint& rhs) { - const float dx = lhs.x - rhs.x; - const float dy = lhs.y - rhs.y; - return dx * dx + dy * dy; -} - -::XCEngine::UI::UITextureHandle ResolveGameObjectIcon( - const ProductBuiltInIcons* icons) { - return icons != nullptr - ? icons->Resolve(ProductBuiltInIconKind::GameObject) - : ::XCEngine::UI::UITextureHandle {}; -} - -std::vector FilterHierarchyInputEvents( - const UIRect& bounds, - const std::vector& inputEvents, - bool allowInteraction, - bool panelActive, - bool captureActive) { - if (!allowInteraction && !captureActive) { - return {}; - } - - std::vector filteredEvents = {}; - filteredEvents.reserve(inputEvents.size()); - for (const UIInputEvent& event : inputEvents) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - if (captureActive || ContainsPoint(bounds, event.position)) { - filteredEvents.push_back(event); - } - break; - case UIInputEventType::PointerLeave: - filteredEvents.push_back(event); - break; - case UIInputEventType::FocusGained: - case UIInputEventType::FocusLost: - if (panelActive || captureActive) { - filteredEvents.push_back(event); - } - break; - case UIInputEventType::KeyDown: - case UIInputEventType::KeyUp: - case UIInputEventType::Character: - if (panelActive) { - filteredEvents.push_back(event); - } - break; - default: - break; - } - } - - return filteredEvents; -} - -const Widgets::UIEditorTreeViewItem* ResolveHitItem( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - const UIPoint& point, - UIEditorTreeViewHitTarget* hitTargetOutput = nullptr) { - const UIEditorTreeViewHitTarget hitTarget = HitTestUIEditorTreeView(layout, point); - if (hitTargetOutput != nullptr) { - *hitTargetOutput = hitTarget; - } - - if (hitTarget.itemIndex >= items.size()) { - return nullptr; - } - - return &items[hitTarget.itemIndex]; -} - -std::size_t FindVisibleIndexForItemId( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - std::string_view itemId) { - for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); ++visibleIndex) { - const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; - if (itemIndex < items.size() && items[itemIndex].itemId == itemId) { - return visibleIndex; - } - } - - return UIEditorTreeViewInvalidIndex; -} - -} // namespace - -void ProductHierarchyPanel::Initialize() { - m_model = ProductHierarchyModel::BuildDefault(); +void HierarchyPanel::Initialize() { + m_model = HierarchyModel::BuildDefault(); RebuildItems(); } -void ProductHierarchyPanel::SetBuiltInIcons(const ProductBuiltInIcons* icons) { +void HierarchyPanel::SetBuiltInIcons(const BuiltInIcons* icons) { m_icons = icons; RebuildItems(); } -void ProductHierarchyPanel::ResetInteractionState() { +void HierarchyPanel::ResetInteractionState() { m_treeInteractionState = {}; m_treeFrame = {}; m_dragState = {}; ResetTransientState(); } -const UIEditorPanelContentHostPanelState* ProductHierarchyPanel::FindMountedHierarchyPanel( +const UIEditorPanelContentHostPanelState* HierarchyPanel::FindMountedHierarchyPanel( const UIEditorPanelContentHostFrame& contentHostFrame) const { for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { if (panelState.panelId == kHierarchyPanelId && panelState.mounted) { @@ -161,13 +32,13 @@ const UIEditorPanelContentHostPanelState* ProductHierarchyPanel::FindMountedHier return nullptr; } -void ProductHierarchyPanel::ResetTransientState() { +void HierarchyPanel::ResetTransientState() { m_frameEvents.clear(); m_dragState.requestPointerCapture = false; m_dragState.requestPointerRelease = false; } -void ProductHierarchyPanel::RebuildItems() { +void HierarchyPanel::RebuildItems() { const auto icon = ResolveGameObjectIcon(m_icons); const std::string previousSelection = m_selection.HasSelection() ? m_selection.GetSelectedId() : std::string(); @@ -187,12 +58,12 @@ void ProductHierarchyPanel::RebuildItems() { } } -void ProductHierarchyPanel::EmitSelectionEvent() { +void HierarchyPanel::EmitSelectionEvent() { if (!m_selection.HasSelection()) { return; } - const ProductHierarchyNode* node = m_model.FindNode(m_selection.GetSelectedId()); + const HierarchyNode* node = m_model.FindNode(m_selection.GetSelectedId()); if (node == nullptr) { return; } @@ -204,7 +75,7 @@ void ProductHierarchyPanel::EmitSelectionEvent() { m_frameEvents.push_back(std::move(event)); } -void ProductHierarchyPanel::EmitReparentEvent( +void HierarchyPanel::EmitReparentEvent( EventKind kind, std::string itemId, std::string targetItemId) { @@ -212,363 +83,25 @@ void ProductHierarchyPanel::EmitReparentEvent( event.kind = kind; event.itemId = std::move(itemId); event.targetItemId = std::move(targetItemId); - if (const ProductHierarchyNode* node = m_model.FindNode(event.itemId); node != nullptr) { + if (const HierarchyNode* node = m_model.FindNode(event.itemId); node != nullptr) { event.label = node->label; } m_frameEvents.push_back(std::move(event)); } -std::vector ProductHierarchyPanel::BuildInteractionInputEvents( - const std::vector& inputEvents, - const UIRect& bounds, - bool allowInteraction, - bool panelActive) const { - const std::vector rawEvents = FilterHierarchyInputEvents( - bounds, - inputEvents, - allowInteraction, - panelActive, - HasActivePointerCapture()); - - struct DragPreviewState { - std::string armedItemId = {}; - UIPoint pressPosition = {}; - bool armed = false; - bool dragging = false; - }; - - const Widgets::UIEditorTreeViewLayout layout = - m_treeFrame.layout.bounds.width > 0.0f ? m_treeFrame.layout : Widgets::BuildUIEditorTreeViewLayout( - bounds, - m_treeItems, - m_expansion, - BuildProductTreeViewMetrics()); - DragPreviewState preview = {}; - preview.armed = m_dragState.armed; - preview.armedItemId = m_dragState.armedItemId; - preview.pressPosition = m_dragState.pressPosition; - preview.dragging = m_dragState.dragging; - - std::vector filteredEvents = {}; - filteredEvents.reserve(rawEvents.size()); - for (const UIInputEvent& event : rawEvents) { - bool suppress = false; - - switch (event.type) { - case UIInputEventType::PointerButtonDown: - if (event.pointerButton == UIPointerButton::Left) { - UIEditorTreeViewHitTarget hitTarget = {}; - const Widgets::UIEditorTreeViewItem* hitItem = - ResolveHitItem(layout, m_treeItems, event.position, &hitTarget); - if (hitItem != nullptr && - hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { - preview.armed = true; - preview.armedItemId = hitItem->itemId; - preview.pressPosition = event.position; - } else { - preview.armed = false; - preview.armedItemId.clear(); - } - } - if (preview.dragging) { - suppress = true; - } - break; - - case UIInputEventType::PointerMove: - if (preview.dragging) { - suppress = true; - break; - } - - if (preview.armed && - ComputeSquaredDistance(event.position, preview.pressPosition) >= - kDragThreshold * kDragThreshold) { - preview.dragging = true; - suppress = true; - } - break; - - case UIInputEventType::PointerButtonUp: - if (event.pointerButton == UIPointerButton::Left) { - if (preview.dragging) { - suppress = true; - preview.dragging = false; - } - preview.armed = false; - preview.armedItemId.clear(); - } else if (preview.dragging) { - suppress = true; - } - break; - - case UIInputEventType::PointerLeave: - if (preview.dragging) { - suppress = true; - } - break; - - case UIInputEventType::FocusLost: - preview.armed = false; - preview.dragging = false; - preview.armedItemId.clear(); - break; - - default: - break; - } - - if (!suppress) { - filteredEvents.push_back(event); - } - } - - return filteredEvents; -} - -void ProductHierarchyPanel::ProcessDragAndFrameEvents( - const std::vector& inputEvents, - const UIRect& bounds, - bool allowInteraction, - bool panelActive) { - const std::vector filteredEvents = FilterHierarchyInputEvents( - bounds, - inputEvents, - allowInteraction, - panelActive, - HasActivePointerCapture()); - - if (m_treeFrame.result.selectionChanged) { - EmitSelectionEvent(); - } - if (m_treeFrame.result.renameRequested && - !m_treeFrame.result.renameItemId.empty()) { - Event event = {}; - event.kind = EventKind::RenameRequested; - event.itemId = m_treeFrame.result.renameItemId; - if (const ProductHierarchyNode* node = m_model.FindNode(event.itemId); node != nullptr) { - event.label = node->label; - } - m_frameEvents.push_back(std::move(event)); - } - - for (const UIInputEvent& event : filteredEvents) { - switch (event.type) { - case UIInputEventType::PointerButtonDown: - if (event.pointerButton == UIPointerButton::Left) { - UIEditorTreeViewHitTarget hitTarget = {}; - const Widgets::UIEditorTreeViewItem* hitItem = - ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget); - if (hitItem != nullptr && - hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { - m_dragState.armed = true; - m_dragState.armedItemId = hitItem->itemId; - m_dragState.pressPosition = event.position; - } else { - m_dragState.armed = false; - m_dragState.armedItemId.clear(); - } - } - break; - - case UIInputEventType::PointerMove: - if (m_dragState.armed && !m_dragState.dragging && - ComputeSquaredDistance(event.position, m_dragState.pressPosition) >= - kDragThreshold * kDragThreshold) { - m_dragState.dragging = !m_dragState.armedItemId.empty(); - m_dragState.draggedItemId = m_dragState.armedItemId; - m_dragState.dropTargetItemId.clear(); - m_dragState.dropToRoot = false; - m_dragState.validDropTarget = false; - if (m_dragState.dragging) { - m_dragState.requestPointerCapture = true; - if (!m_selection.IsSelected(m_dragState.draggedItemId)) { - m_selection.SetSelection(m_dragState.draggedItemId); - EmitSelectionEvent(); - } - } - } - - if (m_dragState.dragging) { - UIEditorTreeViewHitTarget hitTarget = {}; - const Widgets::UIEditorTreeViewItem* hitItem = - ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget); - - m_dragState.dropTargetItemId.clear(); - m_dragState.dropToRoot = false; - m_dragState.validDropTarget = false; - - if (hitItem != nullptr && - (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row || - hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) { - m_dragState.dropTargetItemId = hitItem->itemId; - m_dragState.validDropTarget = - m_model.CanReparent( - m_dragState.draggedItemId, - m_dragState.dropTargetItemId); - } else if (ContainsPoint(bounds, event.position)) { - m_dragState.dropToRoot = true; - m_dragState.validDropTarget = - m_model.GetParentId(m_dragState.draggedItemId).has_value(); - } - } - break; - - case UIInputEventType::PointerButtonUp: - if (event.pointerButton != UIPointerButton::Left) { - break; - } - - if (m_dragState.dragging) { - if (m_dragState.validDropTarget) { - const std::string draggedItemId = m_dragState.draggedItemId; - const std::string dropTargetItemId = m_dragState.dropTargetItemId; - const bool changed = - m_dragState.dropToRoot - ? m_model.MoveToRoot(draggedItemId) - : m_model.Reparent(draggedItemId, dropTargetItemId); - if (changed) { - RebuildItems(); - EmitReparentEvent( - m_dragState.dropToRoot ? EventKind::MovedToRoot : EventKind::Reparented, - draggedItemId, - dropTargetItemId); - } - } - - m_dragState.armed = false; - m_dragState.dragging = false; - m_dragState.armedItemId.clear(); - m_dragState.draggedItemId.clear(); - m_dragState.dropTargetItemId.clear(); - m_dragState.dropToRoot = false; - m_dragState.validDropTarget = false; - m_dragState.requestPointerRelease = true; - } else { - m_dragState.armed = false; - m_dragState.armedItemId.clear(); - } - break; - - case UIInputEventType::FocusLost: - if (m_dragState.dragging) { - m_dragState.requestPointerRelease = true; - } - m_dragState = {}; - break; - - default: - break; - } - } -} - -void ProductHierarchyPanel::Update( - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector& inputEvents, - bool allowInteraction, - bool panelActive) { - ResetTransientState(); - - const UIEditorPanelContentHostPanelState* panelState = - FindMountedHierarchyPanel(contentHostFrame); - if (panelState == nullptr) { - m_visible = false; - m_treeFrame = {}; - m_dragState = {}; - return; - } - - if (m_treeItems.empty()) { - RebuildItems(); - } - - m_visible = true; - const std::vector interactionEvents = - BuildInteractionInputEvents( - inputEvents, - panelState->bounds, - allowInteraction, - panelActive); - m_treeFrame = UpdateUIEditorTreeViewInteraction( - m_treeInteractionState, - m_selection, - m_expansion, - panelState->bounds, - m_treeItems, - interactionEvents, - BuildProductTreeViewMetrics()); - ProcessDragAndFrameEvents( - inputEvents, - panelState->bounds, - allowInteraction, - panelActive); -} - -void ProductHierarchyPanel::Append(UIDrawList& drawList) const { - if (!m_visible || m_treeFrame.layout.bounds.width <= 0.0f || m_treeFrame.layout.bounds.height <= 0.0f) { - return; - } - - const Widgets::UIEditorTreeViewPalette palette = BuildProductTreeViewPalette(); - const Widgets::UIEditorTreeViewMetrics metrics = BuildProductTreeViewMetrics(); - AppendUIEditorTreeViewBackground( - drawList, - m_treeFrame.layout, - m_treeItems, - m_selection, - m_treeInteractionState.treeViewState, - palette, - metrics); - AppendUIEditorTreeViewForeground( - drawList, - m_treeFrame.layout, - m_treeItems, - palette, - metrics); - - if (!m_dragState.dragging || !m_dragState.validDropTarget) { - return; - } - - if (m_dragState.dropToRoot) { - drawList.AddRectOutline( - m_treeFrame.layout.bounds, - kDragPreviewColor, - 1.0f, - 0.0f); - return; - } - - const std::size_t visibleIndex = FindVisibleIndexForItemId( - m_treeFrame.layout, - m_treeItems, - m_dragState.dropTargetItemId); - if (visibleIndex == UIEditorTreeViewInvalidIndex || - visibleIndex >= m_treeFrame.layout.rowRects.size()) { - return; - } - - drawList.AddRectOutline( - m_treeFrame.layout.rowRects[visibleIndex], - kDragPreviewColor, - 1.0f, - 0.0f); -} - -bool ProductHierarchyPanel::WantsHostPointerCapture() const { +bool HierarchyPanel::WantsHostPointerCapture() const { return m_dragState.requestPointerCapture; } -bool ProductHierarchyPanel::WantsHostPointerRelease() const { +bool HierarchyPanel::WantsHostPointerRelease() const { return m_dragState.requestPointerRelease; } -bool ProductHierarchyPanel::HasActivePointerCapture() const { +bool HierarchyPanel::HasActivePointerCapture() const { return m_dragState.dragging; } -const std::vector& ProductHierarchyPanel::GetFrameEvents() const { +const std::vector& HierarchyPanel::GetFrameEvents() const { return m_frameEvents; } diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.h b/new_editor/app/Features/Hierarchy/HierarchyPanel.h index 5651f6e9..b1ecfd79 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.h +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.h @@ -1,9 +1,9 @@ #pragma once -#include "Hierarchy/ProductHierarchyModel.h" +#include "HierarchyModel.h" #include -#include +#include #include #include @@ -15,9 +15,9 @@ namespace XCEngine::UI::Editor::App { -class ProductBuiltInIcons; +class BuiltInIcons; -class ProductHierarchyPanel { +class HierarchyPanel { public: enum class EventKind : std::uint8_t { None = 0, @@ -35,7 +35,7 @@ public: }; void Initialize(); - void SetBuiltInIcons(const ProductBuiltInIcons* icons); + void SetBuiltInIcons(const BuiltInIcons* icons); void ResetInteractionState(); void Update( const UIEditorPanelContentHostFrame& contentHostFrame, @@ -82,8 +82,8 @@ private: bool allowInteraction, bool panelActive) const; - const ProductBuiltInIcons* m_icons = nullptr; - ProductHierarchyModel m_model = {}; + const BuiltInIcons* m_icons = nullptr; + HierarchyModel m_model = {}; std::vector m_treeItems = {}; ::XCEngine::UI::Widgets::UISelectionModel m_selection = {}; ::XCEngine::UI::Widgets::UIExpansionModel m_expansion = {}; @@ -95,3 +95,6 @@ private: }; } // namespace XCEngine::UI::Editor::App + + + diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelInteraction.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanelInteraction.cpp new file mode 100644 index 00000000..6ed2d90e --- /dev/null +++ b/new_editor/app/Features/Hierarchy/HierarchyPanelInteraction.cpp @@ -0,0 +1,293 @@ +#include "HierarchyPanelSupport.h" + +namespace XCEngine::UI::Editor::App { + +using namespace HierarchyPanelSupport; + +std::vector HierarchyPanel::BuildInteractionInputEvents( + const std::vector& inputEvents, + const UIRect& bounds, + bool allowInteraction, + bool panelActive) const { + const std::vector rawEvents = FilterHierarchyInputEvents( + bounds, + inputEvents, + allowInteraction, + panelActive, + HasActivePointerCapture()); + + struct DragPreviewState { + std::string armedItemId = {}; + UIPoint pressPosition = {}; + bool armed = false; + bool dragging = false; + }; + + const Widgets::UIEditorTreeViewLayout layout = + m_treeFrame.layout.bounds.width > 0.0f + ? m_treeFrame.layout + : Widgets::BuildUIEditorTreeViewLayout( + bounds, + m_treeItems, + m_expansion, + BuildEditorTreeViewMetrics()); + DragPreviewState preview = {}; + preview.armed = m_dragState.armed; + preview.armedItemId = m_dragState.armedItemId; + preview.pressPosition = m_dragState.pressPosition; + preview.dragging = m_dragState.dragging; + + std::vector filteredEvents = {}; + filteredEvents.reserve(rawEvents.size()); + for (const UIInputEvent& event : rawEvents) { + bool suppress = false; + + switch (event.type) { + case UIInputEventType::PointerButtonDown: + if (event.pointerButton == UIPointerButton::Left) { + UIEditorTreeViewHitTarget hitTarget = {}; + const Widgets::UIEditorTreeViewItem* hitItem = + ResolveHitItem(layout, m_treeItems, event.position, &hitTarget); + if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { + preview.armed = true; + preview.armedItemId = hitItem->itemId; + preview.pressPosition = event.position; + } else { + preview.armed = false; + preview.armedItemId.clear(); + } + } + if (preview.dragging) { + suppress = true; + } + break; + + case UIInputEventType::PointerMove: + if (preview.dragging) { + suppress = true; + break; + } + + if (preview.armed && + ComputeSquaredDistance(event.position, preview.pressPosition) >= + kDragThreshold * kDragThreshold) { + preview.dragging = true; + suppress = true; + } + break; + + case UIInputEventType::PointerButtonUp: + if (event.pointerButton == UIPointerButton::Left) { + if (preview.dragging) { + suppress = true; + preview.dragging = false; + } + preview.armed = false; + preview.armedItemId.clear(); + } else if (preview.dragging) { + suppress = true; + } + break; + + case UIInputEventType::PointerLeave: + if (preview.dragging) { + suppress = true; + } + break; + + case UIInputEventType::FocusLost: + preview.armed = false; + preview.dragging = false; + preview.armedItemId.clear(); + break; + + default: + break; + } + + if (!suppress) { + filteredEvents.push_back(event); + } + } + + return filteredEvents; +} + +void HierarchyPanel::ProcessDragAndFrameEvents( + const std::vector& inputEvents, + const UIRect& bounds, + bool allowInteraction, + bool panelActive) { + const std::vector filteredEvents = FilterHierarchyInputEvents( + bounds, + inputEvents, + allowInteraction, + panelActive, + HasActivePointerCapture()); + + if (m_treeFrame.result.selectionChanged) { + EmitSelectionEvent(); + } + if (m_treeFrame.result.renameRequested && + !m_treeFrame.result.renameItemId.empty()) { + Event event = {}; + event.kind = EventKind::RenameRequested; + event.itemId = m_treeFrame.result.renameItemId; + if (const HierarchyNode* node = m_model.FindNode(event.itemId); node != nullptr) { + event.label = node->label; + } + m_frameEvents.push_back(std::move(event)); + } + + for (const UIInputEvent& event : filteredEvents) { + switch (event.type) { + case UIInputEventType::PointerButtonDown: + if (event.pointerButton == UIPointerButton::Left) { + UIEditorTreeViewHitTarget hitTarget = {}; + const Widgets::UIEditorTreeViewItem* hitItem = + ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget); + if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { + m_dragState.armed = true; + m_dragState.armedItemId = hitItem->itemId; + m_dragState.pressPosition = event.position; + } else { + m_dragState.armed = false; + m_dragState.armedItemId.clear(); + } + } + break; + + case UIInputEventType::PointerMove: + if (m_dragState.armed && + !m_dragState.dragging && + ComputeSquaredDistance(event.position, m_dragState.pressPosition) >= + kDragThreshold * kDragThreshold) { + m_dragState.dragging = !m_dragState.armedItemId.empty(); + m_dragState.draggedItemId = m_dragState.armedItemId; + m_dragState.dropTargetItemId.clear(); + m_dragState.dropToRoot = false; + m_dragState.validDropTarget = false; + if (m_dragState.dragging) { + m_dragState.requestPointerCapture = true; + if (!m_selection.IsSelected(m_dragState.draggedItemId)) { + m_selection.SetSelection(m_dragState.draggedItemId); + EmitSelectionEvent(); + } + } + } + + if (m_dragState.dragging) { + UIEditorTreeViewHitTarget hitTarget = {}; + const Widgets::UIEditorTreeViewItem* hitItem = + ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget); + + m_dragState.dropTargetItemId.clear(); + m_dragState.dropToRoot = false; + m_dragState.validDropTarget = false; + + if (hitItem != nullptr && + (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row || + hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) { + m_dragState.dropTargetItemId = hitItem->itemId; + m_dragState.validDropTarget = + m_model.CanReparent(m_dragState.draggedItemId, m_dragState.dropTargetItemId); + } else if (ContainsPoint(bounds, event.position)) { + m_dragState.dropToRoot = true; + m_dragState.validDropTarget = + m_model.GetParentId(m_dragState.draggedItemId).has_value(); + } + } + break; + + case UIInputEventType::PointerButtonUp: + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + if (m_dragState.dragging) { + if (m_dragState.validDropTarget) { + const std::string draggedItemId = m_dragState.draggedItemId; + const std::string dropTargetItemId = m_dragState.dropTargetItemId; + const bool changed = + m_dragState.dropToRoot + ? m_model.MoveToRoot(draggedItemId) + : m_model.Reparent(draggedItemId, dropTargetItemId); + if (changed) { + RebuildItems(); + EmitReparentEvent( + m_dragState.dropToRoot ? EventKind::MovedToRoot : EventKind::Reparented, + draggedItemId, + dropTargetItemId); + } + } + + m_dragState.armed = false; + m_dragState.dragging = false; + m_dragState.armedItemId.clear(); + m_dragState.draggedItemId.clear(); + m_dragState.dropTargetItemId.clear(); + m_dragState.dropToRoot = false; + m_dragState.validDropTarget = false; + m_dragState.requestPointerRelease = true; + } else { + m_dragState.armed = false; + m_dragState.armedItemId.clear(); + } + break; + + case UIInputEventType::FocusLost: + if (m_dragState.dragging) { + m_dragState.requestPointerRelease = true; + } + m_dragState = {}; + break; + + default: + break; + } + } +} + +void HierarchyPanel::Update( + const UIEditorPanelContentHostFrame& contentHostFrame, + const std::vector& inputEvents, + bool allowInteraction, + bool panelActive) { + ResetTransientState(); + + const UIEditorPanelContentHostPanelState* panelState = + FindMountedHierarchyPanel(contentHostFrame); + if (panelState == nullptr) { + m_visible = false; + m_treeFrame = {}; + m_dragState = {}; + return; + } + + if (m_treeItems.empty()) { + RebuildItems(); + } + + m_visible = true; + const std::vector interactionEvents = + BuildInteractionInputEvents( + inputEvents, + panelState->bounds, + allowInteraction, + panelActive); + m_treeFrame = UpdateUIEditorTreeViewInteraction( + m_treeInteractionState, + m_selection, + m_expansion, + panelState->bounds, + m_treeItems, + interactionEvents, + BuildEditorTreeViewMetrics()); + ProcessDragAndFrameEvents( + inputEvents, + panelState->bounds, + allowInteraction, + panelActive); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelRendering.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanelRendering.cpp new file mode 100644 index 00000000..b322ba54 --- /dev/null +++ b/new_editor/app/Features/Hierarchy/HierarchyPanelRendering.cpp @@ -0,0 +1,60 @@ +#include "HierarchyPanelSupport.h" + +namespace XCEngine::UI::Editor::App { + +using namespace HierarchyPanelSupport; + +void HierarchyPanel::Append(UIDrawList& drawList) const { + if (!m_visible || + m_treeFrame.layout.bounds.width <= 0.0f || + m_treeFrame.layout.bounds.height <= 0.0f) { + return; + } + + const Widgets::UIEditorTreeViewPalette palette = BuildEditorTreeViewPalette(); + const Widgets::UIEditorTreeViewMetrics metrics = BuildEditorTreeViewMetrics(); + AppendUIEditorTreeViewBackground( + drawList, + m_treeFrame.layout, + m_treeItems, + m_selection, + m_treeInteractionState.treeViewState, + palette, + metrics); + AppendUIEditorTreeViewForeground( + drawList, + m_treeFrame.layout, + m_treeItems, + palette, + metrics); + + if (!m_dragState.dragging || !m_dragState.validDropTarget) { + return; + } + + if (m_dragState.dropToRoot) { + drawList.AddRectOutline( + m_treeFrame.layout.bounds, + kDragPreviewColor, + 1.0f, + 0.0f); + return; + } + + const std::size_t visibleIndex = FindVisibleIndexForItemId( + m_treeFrame.layout, + m_treeItems, + m_dragState.dropTargetItemId); + if (visibleIndex == UIEditorTreeViewInvalidIndex || + visibleIndex >= m_treeFrame.layout.rowRects.size()) { + return; + } + + drawList.AddRectOutline( + m_treeFrame.layout.rowRects[visibleIndex], + kDragPreviewColor, + 1.0f, + 0.0f); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelSupport.h b/new_editor/app/Features/Hierarchy/HierarchyPanelSupport.h new file mode 100644 index 00000000..8bee84a1 --- /dev/null +++ b/new_editor/app/Features/Hierarchy/HierarchyPanelSupport.h @@ -0,0 +1,133 @@ +#pragma once + +#include "HierarchyPanel.h" + +#include "Rendering/Assets/BuiltInIcons.h" +#include "UI/Styles/EditorTreeViewStyle.h" + +#include + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::App::HierarchyPanelSupport { + +using ::XCEngine::UI::UIColor; +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIPointerButton; +using ::XCEngine::UI::UIRect; +using Widgets::AppendUIEditorTreeViewBackground; +using Widgets::AppendUIEditorTreeViewForeground; +using Widgets::DoesUIEditorTreeViewItemHaveChildren; +using Widgets::HitTestUIEditorTreeView; +using Widgets::IsUIEditorTreeViewPointInside; +using Widgets::UIEditorTreeViewHitTarget; +using Widgets::UIEditorTreeViewHitTargetKind; +using Widgets::UIEditorTreeViewInvalidIndex; + +inline constexpr std::string_view kHierarchyPanelId = "hierarchy"; +inline constexpr float kDragThreshold = 4.0f; +inline constexpr UIColor kDragPreviewColor(0.92f, 0.92f, 0.92f, 0.42f); + +inline bool ContainsPoint(const UIRect& rect, const UIPoint& point) { + return point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +inline float ComputeSquaredDistance(const UIPoint& lhs, const UIPoint& rhs) { + const float dx = lhs.x - rhs.x; + const float dy = lhs.y - rhs.y; + return dx * dx + dy * dy; +} + +inline ::XCEngine::UI::UITextureHandle ResolveGameObjectIcon(const BuiltInIcons* icons) { + return icons != nullptr + ? icons->Resolve(BuiltInIconKind::GameObject) + : ::XCEngine::UI::UITextureHandle {}; +} + +inline std::vector FilterHierarchyInputEvents( + const UIRect& bounds, + const std::vector& inputEvents, + bool allowInteraction, + bool panelActive, + bool captureActive) { + if (!allowInteraction && !captureActive) { + return {}; + } + + std::vector filteredEvents = {}; + filteredEvents.reserve(inputEvents.size()); + for (const UIInputEvent& event : inputEvents) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + if (captureActive || ContainsPoint(bounds, event.position)) { + filteredEvents.push_back(event); + } + break; + case UIInputEventType::PointerLeave: + filteredEvents.push_back(event); + break; + case UIInputEventType::FocusGained: + case UIInputEventType::FocusLost: + if (panelActive || captureActive) { + filteredEvents.push_back(event); + } + break; + case UIInputEventType::KeyDown: + case UIInputEventType::KeyUp: + case UIInputEventType::Character: + if (panelActive) { + filteredEvents.push_back(event); + } + break; + default: + break; + } + } + + return filteredEvents; +} + +inline const Widgets::UIEditorTreeViewItem* ResolveHitItem( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + const UIPoint& point, + UIEditorTreeViewHitTarget* hitTargetOutput = nullptr) { + const UIEditorTreeViewHitTarget hitTarget = HitTestUIEditorTreeView(layout, point); + if (hitTargetOutput != nullptr) { + *hitTargetOutput = hitTarget; + } + + if (hitTarget.itemIndex >= items.size()) { + return nullptr; + } + + return &items[hitTarget.itemIndex]; +} + +inline std::size_t FindVisibleIndexForItemId( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId) { + for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); ++visibleIndex) { + const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; + if (itemIndex < items.size() && items[itemIndex].itemId == itemId) { + return visibleIndex; + } + } + + return UIEditorTreeViewInvalidIndex; +} + +} // namespace XCEngine::UI::Editor::App::HierarchyPanelSupport diff --git a/new_editor/app/Features/Inspector/InspectorPanel.cpp b/new_editor/app/Features/Inspector/InspectorPanel.cpp index ae6baa46..3ccebce4 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.cpp +++ b/new_editor/app/Features/Inspector/InspectorPanel.cpp @@ -1,4 +1,4 @@ -#include "ProductInspectorPanel.h" +#include "InspectorPanel.h" #include @@ -25,12 +25,12 @@ constexpr float kSectionTitleFontSize = 11.0f; constexpr float kRowFontSize = 11.0f; constexpr UIColor kSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); -constexpr UIColor kSectionHeaderColor(0.12f, 0.12f, 0.12f, 1.0f); -constexpr UIColor kSectionBodyColor(0.11f, 0.11f, 0.11f, 1.0f); +constexpr UIColor kSectionHeaderColor(0.11f, 0.11f, 0.11f, 1.0f); +constexpr UIColor kSectionBodyColor(0.10f, 0.10f, 0.10f, 1.0f); constexpr UIColor kTitleColor(0.930f, 0.930f, 0.930f, 1.0f); -constexpr UIColor kSubtitleColor(0.640f, 0.640f, 0.640f, 1.0f); +constexpr UIColor kSubtitleColor(0.660f, 0.660f, 0.660f, 1.0f); constexpr UIColor kLabelColor(0.720f, 0.720f, 0.720f, 1.0f); -constexpr UIColor kValueColor(0.880f, 0.880f, 0.880f, 1.0f); +constexpr UIColor kValueColor(0.900f, 0.900f, 0.900f, 1.0f); float ResolveTextTop(float rectY, float rectHeight, float fontSize) { const float lineHeight = fontSize * 1.6f; @@ -44,7 +44,7 @@ std::string PathToUtf8String(const std::filesystem::path& path) { } // namespace -const UIEditorPanelContentHostPanelState* ProductInspectorPanel::FindMountedInspectorPanel( +const UIEditorPanelContentHostPanelState* InspectorPanel::FindMountedInspectorPanel( const UIEditorPanelContentHostFrame& contentHostFrame) const { for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { if (panelState.panelId == kInspectorPanelId && panelState.mounted) { @@ -55,14 +55,14 @@ const UIEditorPanelContentHostPanelState* ProductInspectorPanel::FindMountedInsp return nullptr; } -void ProductInspectorPanel::BuildPresentation(const ProductEditorSession& session) { +void InspectorPanel::BuildPresentation(const EditorSession& session) { m_sections.clear(); m_title.clear(); m_subtitle.clear(); m_hasSelection = false; switch (session.selection.kind) { - case ProductEditorSelectionKind::HierarchyNode: { + case EditorSelectionKind::HierarchyNode: { m_hasSelection = true; m_title = session.selection.displayName.empty() ? std::string("GameObject") @@ -80,7 +80,7 @@ void ProductInspectorPanel::BuildPresentation(const ProductEditorSession& sessio break; } - case ProductEditorSelectionKind::ProjectItem: { + case EditorSelectionKind::ProjectItem: { m_hasSelection = true; m_title = session.selection.displayName.empty() ? (session.selection.directory ? std::string("Folder") : std::string("Asset")) @@ -105,7 +105,7 @@ void ProductInspectorPanel::BuildPresentation(const ProductEditorSession& sessio break; } - case ProductEditorSelectionKind::None: + case EditorSelectionKind::None: default: m_title = "Nothing selected"; m_subtitle = "Select a hierarchy item or project asset."; @@ -113,8 +113,8 @@ void ProductInspectorPanel::BuildPresentation(const ProductEditorSession& sessio } } -void ProductInspectorPanel::Update( - const ProductEditorSession& session, +void InspectorPanel::Update( + const EditorSession& session, const UIEditorPanelContentHostFrame& contentHostFrame) { const UIEditorPanelContentHostPanelState* panelState = FindMountedInspectorPanel(contentHostFrame); @@ -133,7 +133,7 @@ void ProductInspectorPanel::Update( BuildPresentation(session); } -void ProductInspectorPanel::Append(UIDrawList& drawList) const { +void InspectorPanel::Append(UIDrawList& drawList) const { if (!m_visible || m_bounds.width <= 0.0f || m_bounds.height <= 0.0f) { return; } @@ -222,3 +222,4 @@ void ProductInspectorPanel::Append(UIDrawList& drawList) const { } } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Features/Inspector/InspectorPanel.h b/new_editor/app/Features/Inspector/InspectorPanel.h index 96b72b86..dab6050f 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.h +++ b/new_editor/app/Features/Inspector/InspectorPanel.h @@ -1,8 +1,8 @@ #pragma once -#include "Core/ProductEditorSession.h" +#include "State/EditorSession.h" -#include +#include #include @@ -11,10 +11,10 @@ namespace XCEngine::UI::Editor::App { -class ProductInspectorPanel { +class InspectorPanel { public: void Update( - const ProductEditorSession& session, + const EditorSession& session, const UIEditorPanelContentHostFrame& contentHostFrame); void Append(::XCEngine::UI::UIDrawList& drawList) const; @@ -31,7 +31,7 @@ private: const UIEditorPanelContentHostPanelState* FindMountedInspectorPanel( const UIEditorPanelContentHostFrame& contentHostFrame) const; - void BuildPresentation(const ProductEditorSession& session); + void BuildPresentation(const EditorSession& session); bool m_visible = false; bool m_hasSelection = false; @@ -42,3 +42,4 @@ private: }; } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Features/Project/ProjectBrowserModel.cpp b/new_editor/app/Features/Project/ProjectBrowserModel.cpp index 418c3611..a1bbc4b3 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModel.cpp +++ b/new_editor/app/Features/Project/ProjectBrowserModel.cpp @@ -1,179 +1,52 @@ -#include "Project/ProductProjectBrowserModel.h" +#include "ProjectBrowserModelSupport.h" -#include -#include -#include -#include +namespace XCEngine::UI::Editor::App { -#include +using namespace ProjectBrowserModelSupport; -namespace XCEngine::UI::Editor::App::Project { - -namespace { - -constexpr std::string_view kAssetsRootId = "Assets"; - -std::string ToLowerCopy(std::string value) { - std::transform( - value.begin(), - value.end(), - value.begin(), - [](unsigned char character) { - return static_cast(std::tolower(character)); - }); - return value; -} - -std::string WideToUtf8(std::wstring_view value) { - if (value.empty()) { - return {}; - } - - const int requiredSize = WideCharToMultiByte( - CP_UTF8, - 0, - value.data(), - static_cast(value.size()), - nullptr, - 0, - nullptr, - nullptr); - if (requiredSize <= 0) { - return {}; - } - - std::string result(static_cast(requiredSize), '\0'); - WideCharToMultiByte( - CP_UTF8, - 0, - value.data(), - static_cast(value.size()), - result.data(), - requiredSize, - nullptr, - nullptr); - return result; -} - -std::string PathToUtf8String(const std::filesystem::path& path) { - return WideToUtf8(path.native()); -} - -std::string NormalizePathSeparators(std::string value) { - std::replace(value.begin(), value.end(), '\\', '/'); - return value; -} - -std::string BuildRelativeItemId( - const std::filesystem::path& path, - const std::filesystem::path& assetsRoot) { - const std::filesystem::path relative = - std::filesystem::relative(path, assetsRoot.parent_path()); - const std::string normalized = - NormalizePathSeparators(PathToUtf8String(relative.lexically_normal())); - return normalized.empty() ? std::string(kAssetsRootId) : normalized; -} - -std::string BuildAssetDisplayName(const std::filesystem::path& path, bool directory) { - if (directory) { - return PathToUtf8String(path.filename()); - } - - const std::string filename = PathToUtf8String(path.filename()); - const std::size_t extensionOffset = filename.find_last_of('.'); - if (extensionOffset == std::string::npos || extensionOffset == 0u) { - return filename; - } - - return filename.substr(0u, extensionOffset); -} - -bool IsMetaFile(const std::filesystem::path& path) { - return ToLowerCopy(path.extension().string()) == ".meta"; -} - -bool HasChildDirectories(const std::filesystem::path& folderPath) { - std::error_code errorCode = {}; - const std::filesystem::directory_iterator end = {}; - for (std::filesystem::directory_iterator iterator(folderPath, errorCode); - !errorCode && iterator != end; - iterator.increment(errorCode)) { - if (iterator->is_directory(errorCode)) { - return true; - } - } - - return false; -} - -std::vector CollectSortedChildDirectories( - const std::filesystem::path& folderPath) { - std::vector paths = {}; - std::error_code errorCode = {}; - const std::filesystem::directory_iterator end = {}; - for (std::filesystem::directory_iterator iterator(folderPath, errorCode); - !errorCode && iterator != end; - iterator.increment(errorCode)) { - if (iterator->is_directory(errorCode)) { - paths.push_back(iterator->path()); - } - } - - std::sort( - paths.begin(), - paths.end(), - [](const std::filesystem::path& lhs, const std::filesystem::path& rhs) { - return ToLowerCopy(PathToUtf8String(lhs.filename())) < - ToLowerCopy(PathToUtf8String(rhs.filename())); - }); - return paths; -} - -} // namespace - -void ProductProjectBrowserModel::Initialize(const std::filesystem::path& repoRoot) { +void ProjectBrowserModel::Initialize(const std::filesystem::path& repoRoot) { m_assetsRootPath = (repoRoot / "project/Assets").lexically_normal(); Refresh(); } -void ProductProjectBrowserModel::SetFolderIcon(const ::XCEngine::UI::UITextureHandle& icon) { +void ProjectBrowserModel::SetFolderIcon(const ::XCEngine::UI::UITextureHandle& icon) { m_folderIcon = icon; if (!m_assetsRootPath.empty()) { RefreshFolderTree(); } } -void ProductProjectBrowserModel::Refresh() { +void ProjectBrowserModel::Refresh() { RefreshFolderTree(); EnsureValidCurrentFolder(); RefreshAssetList(); } -bool ProductProjectBrowserModel::Empty() const { +bool ProjectBrowserModel::Empty() const { return m_treeItems.empty(); } -const std::filesystem::path& ProductProjectBrowserModel::GetAssetsRootPath() const { +const std::filesystem::path& ProjectBrowserModel::GetAssetsRootPath() const { return m_assetsRootPath; } -const std::vector& ProductProjectBrowserModel::GetFolderEntries() const { +const std::vector& ProjectBrowserModel::GetFolderEntries() const { return m_folderEntries; } -const std::vector& ProductProjectBrowserModel::GetTreeItems() const { +const std::vector& ProjectBrowserModel::GetTreeItems() const { return m_treeItems; } -const std::vector& ProductProjectBrowserModel::GetAssetEntries() const { +const std::vector& ProjectBrowserModel::GetAssetEntries() const { return m_assetEntries; } -const std::string& ProductProjectBrowserModel::GetCurrentFolderId() const { +const std::string& ProjectBrowserModel::GetCurrentFolderId() const { return m_currentFolderId; } -const ProductProjectBrowserModel::FolderEntry* ProductProjectBrowserModel::FindFolderEntry( +const ProjectBrowserModel::FolderEntry* ProjectBrowserModel::FindFolderEntry( std::string_view itemId) const { for (const FolderEntry& entry : m_folderEntries) { if (entry.itemId == itemId) { @@ -184,7 +57,7 @@ const ProductProjectBrowserModel::FolderEntry* ProductProjectBrowserModel::FindF return nullptr; } -const ProductProjectBrowserModel::AssetEntry* ProductProjectBrowserModel::FindAssetEntry( +const ProjectBrowserModel::AssetEntry* ProjectBrowserModel::FindAssetEntry( std::string_view itemId) const { for (const AssetEntry& entry : m_assetEntries) { if (entry.itemId == itemId) { @@ -195,7 +68,7 @@ const ProductProjectBrowserModel::AssetEntry* ProductProjectBrowserModel::FindAs return nullptr; } -bool ProductProjectBrowserModel::NavigateToFolder(std::string_view itemId) { +bool ProjectBrowserModel::NavigateToFolder(std::string_view itemId) { if (itemId.empty() || FindFolderEntry(itemId) == nullptr || itemId == m_currentFolderId) { return false; } @@ -206,7 +79,7 @@ bool ProductProjectBrowserModel::NavigateToFolder(std::string_view itemId) { return true; } -std::vector ProductProjectBrowserModel::BuildBreadcrumbSegments() const { +std::vector ProjectBrowserModel::BuildBreadcrumbSegments() const { std::vector segments = {}; if (m_currentFolderId.empty()) { segments.push_back(BreadcrumbSegment{ std::string(kAssetsRootId), std::string(kAssetsRootId), true }); @@ -251,117 +124,7 @@ std::vector ProductProjectBrowser return segments; } -std::vector ProductProjectBrowserModel::CollectCurrentFolderAncestorIds() const { - return BuildAncestorFolderIds(m_currentFolderId); -} - -std::vector ProductProjectBrowserModel::BuildAncestorFolderIds( - std::string_view itemId) const { - std::vector ancestors = {}; - const FolderEntry* folderEntry = FindFolderEntry(itemId); - if (folderEntry == nullptr) { - return ancestors; - } - - std::filesystem::path path = folderEntry->absolutePath; - while (true) { - ancestors.push_back(BuildRelativeItemId(path, m_assetsRootPath)); - if (path == m_assetsRootPath) { - break; - } - path = path.parent_path(); - } - - std::reverse(ancestors.begin(), ancestors.end()); - return ancestors; -} - -void ProductProjectBrowserModel::RefreshFolderTree() { - m_folderEntries.clear(); - m_treeItems.clear(); - - if (m_assetsRootPath.empty() || !std::filesystem::exists(m_assetsRootPath)) { - return; - } - - const auto appendFolderRecursive = - [&](auto&& self, const std::filesystem::path& folderPath, std::uint32_t depth) -> void { - const std::string itemId = BuildRelativeItemId(folderPath, m_assetsRootPath); - - FolderEntry folderEntry = {}; - folderEntry.itemId = itemId; - folderEntry.absolutePath = folderPath; - folderEntry.label = PathToUtf8String(folderPath.filename()); - m_folderEntries.push_back(folderEntry); - - Widgets::UIEditorTreeViewItem item = {}; - item.itemId = itemId; - item.label = folderEntry.label; - item.depth = depth; - item.forceLeaf = !HasChildDirectories(folderPath); - item.leadingIcon = m_folderIcon; - m_treeItems.push_back(std::move(item)); - - const std::vector childFolders = - CollectSortedChildDirectories(folderPath); - for (const std::filesystem::path& childPath : childFolders) { - self(self, childPath, depth + 1u); - } - }; - - appendFolderRecursive(appendFolderRecursive, m_assetsRootPath, 0u); -} - -void ProductProjectBrowserModel::RefreshAssetList() { - EnsureValidCurrentFolder(); - - m_assetEntries.clear(); - const FolderEntry* currentFolder = FindFolderEntry(m_currentFolderId); - if (currentFolder == nullptr) { - return; - } - - std::vector entries = {}; - std::error_code errorCode = {}; - const std::filesystem::directory_iterator end = {}; - for (std::filesystem::directory_iterator iterator(currentFolder->absolutePath, errorCode); - !errorCode && iterator != end; - iterator.increment(errorCode)) { - if (!iterator->exists(errorCode) || IsMetaFile(iterator->path())) { - continue; - } - if (!iterator->is_directory(errorCode) && !iterator->is_regular_file(errorCode)) { - continue; - } - - entries.push_back(*iterator); - } - - std::sort( - entries.begin(), - entries.end(), - [](const std::filesystem::directory_entry& lhs, const std::filesystem::directory_entry& rhs) { - const bool lhsDirectory = lhs.is_directory(); - const bool rhsDirectory = rhs.is_directory(); - if (lhsDirectory != rhsDirectory) { - return lhsDirectory && !rhsDirectory; - } - - return ToLowerCopy(PathToUtf8String(lhs.path().filename())) < - ToLowerCopy(PathToUtf8String(rhs.path().filename())); - }); - - for (const std::filesystem::directory_entry& entry : entries) { - AssetEntry assetEntry = {}; - assetEntry.itemId = BuildRelativeItemId(entry.path(), m_assetsRootPath); - assetEntry.absolutePath = entry.path(); - assetEntry.displayName = BuildAssetDisplayName(entry.path(), entry.is_directory()); - assetEntry.directory = entry.is_directory(); - m_assetEntries.push_back(std::move(assetEntry)); - } -} - -void ProductProjectBrowserModel::EnsureValidCurrentFolder() { +void ProjectBrowserModel::EnsureValidCurrentFolder() { if (m_currentFolderId.empty()) { m_currentFolderId = std::string(kAssetsRootId); } @@ -373,4 +136,4 @@ void ProductProjectBrowserModel::EnsureValidCurrentFolder() { } } -} // namespace XCEngine::UI::Editor::App::Project +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectBrowserModel.h b/new_editor/app/Features/Project/ProjectBrowserModel.h index 232516ca..3833f598 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModel.h +++ b/new_editor/app/Features/Project/ProjectBrowserModel.h @@ -10,9 +10,9 @@ #include #include -namespace XCEngine::UI::Editor::App::Project { +namespace XCEngine::UI::Editor::App { -class ProductProjectBrowserModel { +class ProjectBrowserModel { public: struct BreadcrumbSegment { std::string label = {}; @@ -64,4 +64,6 @@ private: std::string m_currentFolderId = {}; }; -} // namespace XCEngine::UI::Editor::App::Project +} // namespace XCEngine::UI::Editor::App + + diff --git a/new_editor/app/Features/Project/ProjectBrowserModelAssets.cpp b/new_editor/app/Features/Project/ProjectBrowserModelAssets.cpp new file mode 100644 index 00000000..212cab42 --- /dev/null +++ b/new_editor/app/Features/Project/ProjectBrowserModelAssets.cpp @@ -0,0 +1,56 @@ +#include "ProjectBrowserModelSupport.h" + +namespace XCEngine::UI::Editor::App { + +using namespace ProjectBrowserModelSupport; + +void ProjectBrowserModel::RefreshAssetList() { + EnsureValidCurrentFolder(); + + m_assetEntries.clear(); + const FolderEntry* currentFolder = FindFolderEntry(m_currentFolderId); + if (currentFolder == nullptr) { + return; + } + + std::vector entries = {}; + std::error_code errorCode = {}; + const std::filesystem::directory_iterator end = {}; + for (std::filesystem::directory_iterator iterator(currentFolder->absolutePath, errorCode); + !errorCode && iterator != end; + iterator.increment(errorCode)) { + if (!iterator->exists(errorCode) || IsMetaFile(iterator->path())) { + continue; + } + if (!iterator->is_directory(errorCode) && !iterator->is_regular_file(errorCode)) { + continue; + } + + entries.push_back(*iterator); + } + + std::sort( + entries.begin(), + entries.end(), + [](const std::filesystem::directory_entry& lhs, const std::filesystem::directory_entry& rhs) { + const bool lhsDirectory = lhs.is_directory(); + const bool rhsDirectory = rhs.is_directory(); + if (lhsDirectory != rhsDirectory) { + return lhsDirectory && !rhsDirectory; + } + + return ToLowerCopy(PathToUtf8String(lhs.path().filename())) < + ToLowerCopy(PathToUtf8String(rhs.path().filename())); + }); + + for (const std::filesystem::directory_entry& entry : entries) { + AssetEntry assetEntry = {}; + assetEntry.itemId = BuildRelativeItemId(entry.path(), m_assetsRootPath); + assetEntry.absolutePath = entry.path(); + assetEntry.displayName = BuildAssetDisplayName(entry.path(), entry.is_directory()); + assetEntry.directory = entry.is_directory(); + m_assetEntries.push_back(std::move(assetEntry)); + } +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectBrowserModelFolders.cpp b/new_editor/app/Features/Project/ProjectBrowserModelFolders.cpp new file mode 100644 index 00000000..db7696d8 --- /dev/null +++ b/new_editor/app/Features/Project/ProjectBrowserModelFolders.cpp @@ -0,0 +1,68 @@ +#include "ProjectBrowserModelSupport.h" + +namespace XCEngine::UI::Editor::App { + +using namespace ProjectBrowserModelSupport; + +void ProjectBrowserModel::RefreshFolderTree() { + m_folderEntries.clear(); + m_treeItems.clear(); + + if (m_assetsRootPath.empty() || !std::filesystem::exists(m_assetsRootPath)) { + return; + } + + const auto appendFolderRecursive = + [&](auto&& self, const std::filesystem::path& folderPath, std::uint32_t depth) -> void { + const std::string itemId = BuildRelativeItemId(folderPath, m_assetsRootPath); + + FolderEntry folderEntry = {}; + folderEntry.itemId = itemId; + folderEntry.absolutePath = folderPath; + folderEntry.label = PathToUtf8String(folderPath.filename()); + m_folderEntries.push_back(folderEntry); + + Widgets::UIEditorTreeViewItem item = {}; + item.itemId = itemId; + item.label = folderEntry.label; + item.depth = depth; + item.forceLeaf = !HasChildDirectories(folderPath); + item.leadingIcon = m_folderIcon; + m_treeItems.push_back(std::move(item)); + + const std::vector childFolders = + CollectSortedChildDirectories(folderPath); + for (const std::filesystem::path& childPath : childFolders) { + self(self, childPath, depth + 1u); + } + }; + + appendFolderRecursive(appendFolderRecursive, m_assetsRootPath, 0u); +} + +std::vector ProjectBrowserModel::CollectCurrentFolderAncestorIds() const { + return BuildAncestorFolderIds(m_currentFolderId); +} + +std::vector ProjectBrowserModel::BuildAncestorFolderIds( + std::string_view itemId) const { + std::vector ancestors = {}; + const FolderEntry* folderEntry = FindFolderEntry(itemId); + if (folderEntry == nullptr) { + return ancestors; + } + + std::filesystem::path path = folderEntry->absolutePath; + while (true) { + ancestors.push_back(BuildRelativeItemId(path, m_assetsRootPath)); + if (path == m_assetsRootPath) { + break; + } + path = path.parent_path(); + } + + std::reverse(ancestors.begin(), ancestors.end()); + return ancestors; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectBrowserModelSupport.h b/new_editor/app/Features/Project/ProjectBrowserModelSupport.h new file mode 100644 index 00000000..fa3ece5d --- /dev/null +++ b/new_editor/app/Features/Project/ProjectBrowserModelSupport.h @@ -0,0 +1,101 @@ +#pragma once + +#include "ProjectBrowserModel.h" + +#include "Support/StringEncoding.h" + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::App::ProjectBrowserModelSupport { + +inline constexpr std::string_view kAssetsRootId = "Assets"; + +inline std::string ToLowerCopy(std::string value) { + std::transform( + value.begin(), + value.end(), + value.begin(), + [](unsigned char character) { + return static_cast(std::tolower(character)); + }); + return value; +} + +inline std::string PathToUtf8String(const std::filesystem::path& path) { + return Support::WideToUtf8(path.native()); +} + +inline std::string NormalizePathSeparators(std::string value) { + std::replace(value.begin(), value.end(), '\\', '/'); + return value; +} + +inline std::string BuildRelativeItemId( + const std::filesystem::path& path, + const std::filesystem::path& assetsRoot) { + const std::filesystem::path relative = + std::filesystem::relative(path, assetsRoot.parent_path()); + const std::string normalized = + NormalizePathSeparators(PathToUtf8String(relative.lexically_normal())); + return normalized.empty() ? std::string(kAssetsRootId) : normalized; +} + +inline std::string BuildAssetDisplayName(const std::filesystem::path& path, bool directory) { + if (directory) { + return PathToUtf8String(path.filename()); + } + + const std::string filename = PathToUtf8String(path.filename()); + const std::size_t extensionOffset = filename.find_last_of('.'); + if (extensionOffset == std::string::npos || extensionOffset == 0u) { + return filename; + } + + return filename.substr(0u, extensionOffset); +} + +inline bool IsMetaFile(const std::filesystem::path& path) { + return ToLowerCopy(path.extension().string()) == ".meta"; +} + +inline bool HasChildDirectories(const std::filesystem::path& folderPath) { + std::error_code errorCode = {}; + const std::filesystem::directory_iterator end = {}; + for (std::filesystem::directory_iterator iterator(folderPath, errorCode); + !errorCode && iterator != end; + iterator.increment(errorCode)) { + if (iterator->is_directory(errorCode)) { + return true; + } + } + + return false; +} + +inline std::vector CollectSortedChildDirectories( + const std::filesystem::path& folderPath) { + std::vector paths = {}; + std::error_code errorCode = {}; + const std::filesystem::directory_iterator end = {}; + for (std::filesystem::directory_iterator iterator(folderPath, errorCode); + !errorCode && iterator != end; + iterator.increment(errorCode)) { + if (iterator->is_directory(errorCode)) { + paths.push_back(iterator->path()); + } + } + + std::sort( + paths.begin(), + paths.end(), + [](const std::filesystem::path& lhs, const std::filesystem::path& rhs) { + return ToLowerCopy(PathToUtf8String(lhs.filename())) < + ToLowerCopy(PathToUtf8String(rhs.filename())); + }); + return paths; +} + +} // namespace XCEngine::UI::Editor::App::ProjectBrowserModelSupport diff --git a/new_editor/app/Features/Project/ProjectPanel.cpp b/new_editor/app/Features/Project/ProjectPanel.cpp index 976ad157..26dfcac8 100644 --- a/new_editor/app/Features/Project/ProjectPanel.cpp +++ b/new_editor/app/Features/Project/ProjectPanel.cpp @@ -1,243 +1,27 @@ -#include "ProductProjectPanel.h" +#include "ProjectPanelSupport.h" -#include "Icons/ProductBuiltInIcons.h" -#include "Panels/ProductTreeViewStyle.h" - -#include -#include - -#include -#include -#include -#include #include -#include - namespace XCEngine::UI::Editor::App { -namespace { +using namespace ProjectPanelSupport; -using ::XCEngine::UI::Editor::UIEditorTextMeasureRequest; -using ::XCEngine::UI::Editor::UIEditorTextMeasurer; -using ::XCEngine::UI::UIColor; -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; -using Widgets::AppendUIEditorTreeViewBackground; -using Widgets::AppendUIEditorTreeViewForeground; - -constexpr std::string_view kProjectPanelId = "project"; -constexpr std::size_t kInvalidLayoutIndex = static_cast(-1); - -constexpr float kBrowserHeaderHeight = 24.0f; -constexpr float kNavigationMinWidth = 180.0f; -constexpr float kBrowserMinWidth = 260.0f; -constexpr float kHeaderHorizontalPadding = 10.0f; -constexpr float kHeaderBottomBorderThickness = 1.0f; -constexpr float kBreadcrumbItemPaddingX = 4.0f; -constexpr float kBreadcrumbItemPaddingY = 1.0f; -constexpr float kBreadcrumbSpacing = 3.0f; -constexpr float kTreeTopPadding = 0.0f; -constexpr float kGridInsetX = 16.0f; -constexpr float kGridInsetY = 12.0f; -constexpr float kGridTileWidth = 92.0f; -constexpr float kGridTileHeight = 92.0f; -constexpr float kGridTileGapX = 12.0f; -constexpr float kGridTileGapY = 12.0f; -constexpr float kGridPreviewWidth = 68.0f; -constexpr float kGridPreviewHeight = 54.0f; -constexpr float kHeaderFontSize = 12.0f; -constexpr float kTileLabelFontSize = 11.0f; - -constexpr UIColor kSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); -constexpr UIColor kPaneColor(0.10f, 0.10f, 0.10f, 1.0f); -constexpr UIColor kHeaderColor(0.11f, 0.11f, 0.11f, 1.0f); -constexpr UIColor kTextPrimary(0.880f, 0.880f, 0.880f, 1.0f); -constexpr UIColor kTextStrong(0.930f, 0.930f, 0.930f, 1.0f); -constexpr UIColor kTextMuted(0.640f, 0.640f, 0.640f, 1.0f); -constexpr UIColor kTileHoverColor(0.14f, 0.14f, 0.14f, 1.0f); -constexpr UIColor kTileSelectedColor(0.18f, 0.18f, 0.18f, 1.0f); -constexpr UIColor kTilePreviewFillColor(0.15f, 0.15f, 0.15f, 1.0f); -constexpr UIColor kTilePreviewShadeColor(0.12f, 0.12f, 0.12f, 1.0f); -constexpr UIColor kTilePreviewOutlineColor(0.920f, 0.920f, 0.920f, 0.20f); - -bool ContainsPoint(const UIRect& rect, const UIPoint& point) { - return point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; -} - -float ClampNonNegative(float value) { - return (std::max)(value, 0.0f); -} - -float ResolveTextTop(float rectY, float rectHeight, float fontSize) { - const float lineHeight = fontSize * 1.6f; - return rectY + std::floor((rectHeight - lineHeight) * 0.5f); -} - -float MeasureTextWidth( - const UIEditorTextMeasurer* textMeasurer, - std::string_view text, - float fontSize) { - if (text.empty()) { - return 0.0f; - } - - if (textMeasurer != nullptr) { - const float measuredWidth = - textMeasurer->MeasureTextWidth(UIEditorTextMeasureRequest{ text, fontSize }); - if (measuredWidth > 0.0f) { - return measuredWidth; - } - } - - return static_cast(text.size()) * fontSize * 0.56f; -} - -std::vector FilterProjectPanelInputEvents( - const UIRect& bounds, - const std::vector& inputEvents, - bool allowInteraction, - bool panelActive, - bool captureActive) { - if (!allowInteraction && !captureActive) { - return {}; - } - - std::vector filteredEvents = {}; - filteredEvents.reserve(inputEvents.size()); - for (const UIInputEvent& event : inputEvents) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - if (captureActive || ContainsPoint(bounds, event.position)) { - filteredEvents.push_back(event); - } - break; - case UIInputEventType::PointerLeave: - filteredEvents.push_back(event); - break; - case UIInputEventType::FocusGained: - case UIInputEventType::FocusLost: - if (panelActive || captureActive) { - filteredEvents.push_back(event); - } - break; - case UIInputEventType::KeyDown: - case UIInputEventType::KeyUp: - case UIInputEventType::Character: - if (panelActive) { - filteredEvents.push_back(event); - } - break; - default: - break; - } - } - - return filteredEvents; -} - -std::vector FilterTreeInputEvents( - const std::vector& inputEvents, - bool suppressPointerInput) { - if (!suppressPointerInput) { - return inputEvents; - } - - std::vector filteredEvents = {}; - filteredEvents.reserve(inputEvents.size()); - for (const UIInputEvent& event : inputEvents) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - case UIInputEventType::PointerEnter: - break; - default: - filteredEvents.push_back(event); - break; - } - } - return filteredEvents; -} - -::XCEngine::UI::UITextureHandle ResolveFolderIcon(const ProductBuiltInIcons* icons) { - return icons != nullptr - ? icons->Resolve(ProductBuiltInIconKind::Folder) - : ::XCEngine::UI::UITextureHandle {}; -} - -float ClampNavigationWidth(float value, float totalWidth) { - const float maxWidth = - (std::max)( - kNavigationMinWidth, - totalWidth - kBrowserMinWidth - ResolveUIEditorDockHostMetrics().splitterMetrics.thickness); - return std::clamp(value, kNavigationMinWidth, maxWidth); -} - -void AppendTilePreview( - UIDrawList& drawList, - const UIRect& previewRect, - bool directory) { - if (directory) { - const UIRect tabRect( - previewRect.x + 8.0f, - previewRect.y + 7.0f, - 22.0f, - 7.0f); - const UIRect bodyRect( - previewRect.x + 4.0f, - previewRect.y + 13.0f, - previewRect.width - 8.0f, - previewRect.height - 18.0f); - drawList.AddFilledRect(tabRect, kTilePreviewShadeColor, 1.0f); - drawList.AddFilledRect(bodyRect, kTilePreviewFillColor, 1.0f); - drawList.AddRectOutline(bodyRect, kTilePreviewOutlineColor, 1.0f, 1.0f); - return; - } - - const UIRect sheetRect( - previewRect.x + 10.0f, - previewRect.y + 4.0f, - previewRect.width - 20.0f, - previewRect.height - 8.0f); - const UIRect accentRect( - sheetRect.x, - sheetRect.y, - sheetRect.width, - 7.0f); - drawList.AddFilledRect(sheetRect, kTilePreviewFillColor, 1.0f); - drawList.AddFilledRect(accentRect, kTilePreviewShadeColor, 1.0f); - drawList.AddRectOutline(sheetRect, kTilePreviewOutlineColor, 1.0f, 1.0f); -} - -} // namespace - -void ProductProjectPanel::Initialize(const std::filesystem::path& repoRoot) { +void ProjectPanel::Initialize(const std::filesystem::path& repoRoot) { m_browserModel.Initialize(repoRoot); SyncCurrentFolderSelection(); } -void ProductProjectPanel::SetBuiltInIcons(const ProductBuiltInIcons* icons) { +void ProjectPanel::SetBuiltInIcons(const BuiltInIcons* icons) { m_icons = icons; m_browserModel.SetFolderIcon(ResolveFolderIcon(m_icons)); SyncCurrentFolderSelection(); } -void ProductProjectPanel::SetTextMeasurer(const UIEditorTextMeasurer* textMeasurer) { +void ProjectPanel::SetTextMeasurer(const UIEditorTextMeasurer* textMeasurer) { m_textMeasurer = textMeasurer; } -void ProductProjectPanel::ResetInteractionState() { +void ProjectPanel::ResetInteractionState() { m_treeInteractionState = {}; m_treeFrame = {}; m_frameEvents.clear(); @@ -253,37 +37,35 @@ void ProductProjectPanel::ResetInteractionState() { m_requestPointerRelease = false; } -ProductProjectPanel::CursorKind ProductProjectPanel::GetCursorKind() const { +ProjectPanel::CursorKind ProjectPanel::GetCursorKind() const { return (m_splitterHovered || m_splitterDragging) ? CursorKind::ResizeEW : CursorKind::Arrow; } -bool ProductProjectPanel::WantsHostPointerCapture() const { +bool ProjectPanel::WantsHostPointerCapture() const { return m_requestPointerCapture; } -bool ProductProjectPanel::WantsHostPointerRelease() const { +bool ProjectPanel::WantsHostPointerRelease() const { return m_requestPointerRelease; } -bool ProductProjectPanel::HasActivePointerCapture() const { +bool ProjectPanel::HasActivePointerCapture() const { return m_splitterDragging; } -const std::vector& ProductProjectPanel::GetFrameEvents() const { +const std::vector& ProjectPanel::GetFrameEvents() const { return m_frameEvents; } -const ProductProjectPanel::FolderEntry* ProductProjectPanel::FindFolderEntry( - std::string_view itemId) const { +const ProjectPanel::FolderEntry* ProjectPanel::FindFolderEntry(std::string_view itemId) const { return m_browserModel.FindFolderEntry(itemId); } -const ProductProjectPanel::AssetEntry* ProductProjectPanel::FindAssetEntry( - std::string_view itemId) const { +const ProjectPanel::AssetEntry* ProjectPanel::FindAssetEntry(std::string_view itemId) const { return m_browserModel.FindAssetEntry(itemId); } -const UIEditorPanelContentHostPanelState* ProductProjectPanel::FindMountedProjectPanel( +const UIEditorPanelContentHostPanelState* ProjectPanel::FindMountedProjectPanel( const UIEditorPanelContentHostFrame& contentHostFrame) const { for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { if (panelState.panelId == kProjectPanelId && panelState.mounted) { @@ -294,164 +76,7 @@ const UIEditorPanelContentHostPanelState* ProductProjectPanel::FindMountedProjec return nullptr; } -ProductProjectPanel::Layout ProductProjectPanel::BuildLayout(const UIRect& bounds) const { - Layout layout = {}; - const auto& assetEntries = m_browserModel.GetAssetEntries(); - const std::vector breadcrumbSegments = - m_browserModel.BuildBreadcrumbSegments(); - const float dividerThickness = ResolveUIEditorDockHostMetrics().splitterMetrics.thickness; - layout.bounds = UIRect( - bounds.x, - bounds.y, - ClampNonNegative(bounds.width), - ClampNonNegative(bounds.height)); - - const float leftWidth = ClampNavigationWidth(m_navigationWidth, layout.bounds.width); - layout.leftPaneRect = UIRect( - layout.bounds.x, - layout.bounds.y, - leftWidth, - layout.bounds.height); - layout.dividerRect = UIRect( - layout.leftPaneRect.x + layout.leftPaneRect.width, - layout.bounds.y, - dividerThickness, - layout.bounds.height); - layout.rightPaneRect = UIRect( - layout.dividerRect.x + layout.dividerRect.width, - layout.bounds.y, - ClampNonNegative(layout.bounds.width - layout.leftPaneRect.width - layout.dividerRect.width), - layout.bounds.height); - - layout.treeRect = UIRect( - layout.leftPaneRect.x, - layout.leftPaneRect.y + kTreeTopPadding, - layout.leftPaneRect.width, - ClampNonNegative(layout.leftPaneRect.height - kTreeTopPadding)); - - layout.browserHeaderRect = UIRect( - layout.rightPaneRect.x, - layout.rightPaneRect.y, - layout.rightPaneRect.width, - (std::min)(kBrowserHeaderHeight, layout.rightPaneRect.height)); - layout.browserBodyRect = UIRect( - layout.rightPaneRect.x, - layout.browserHeaderRect.y + layout.browserHeaderRect.height, - layout.rightPaneRect.width, - ClampNonNegative(layout.rightPaneRect.height - layout.browserHeaderRect.height)); - layout.gridRect = UIRect( - layout.browserBodyRect.x + kGridInsetX, - layout.browserBodyRect.y + kGridInsetY, - ClampNonNegative(layout.browserBodyRect.width - kGridInsetX * 2.0f), - ClampNonNegative(layout.browserBodyRect.height - kGridInsetY * 2.0f)); - - const float breadcrumbRowHeight = kHeaderFontSize + kBreadcrumbItemPaddingY * 2.0f; - const float breadcrumbY = - layout.browserHeaderRect.y + std::floor((layout.browserHeaderRect.height - breadcrumbRowHeight) * 0.5f); - const float headerRight = - layout.browserHeaderRect.x + layout.browserHeaderRect.width - kHeaderHorizontalPadding; - float nextItemX = layout.browserHeaderRect.x + kHeaderHorizontalPadding; - for (std::size_t index = 0u; index < breadcrumbSegments.size(); ++index) { - if (index > 0u) { - const float separatorWidth = - MeasureTextWidth(m_textMeasurer, ">", kHeaderFontSize); - if (nextItemX < headerRight && separatorWidth > 0.0f) { - layout.breadcrumbItems.push_back({ - ">", - {}, - UIRect( - nextItemX, - breadcrumbY, - ClampNonNegative((std::min)(separatorWidth, headerRight - nextItemX)), - breadcrumbRowHeight), - true, - false, - false - }); - } - nextItemX += separatorWidth + kBreadcrumbSpacing; - } - - const ProductProjectPanel::BrowserModel::BreadcrumbSegment& segment = breadcrumbSegments[index]; - const float labelWidth = - MeasureTextWidth(m_textMeasurer, segment.label, kHeaderFontSize); - const float itemWidth = labelWidth + kBreadcrumbItemPaddingX * 2.0f; - const float availableWidth = headerRight - nextItemX; - if (availableWidth <= 0.0f) { - break; - } - - layout.breadcrumbItems.push_back({ - segment.label, - segment.targetFolderId, - UIRect( - nextItemX, - breadcrumbY, - ClampNonNegative((std::min)(itemWidth, availableWidth)), - breadcrumbRowHeight), - false, - !segment.current, - segment.current - }); - nextItemX += itemWidth + kBreadcrumbSpacing; - } - - const float effectiveTileWidth = kGridTileWidth + kGridTileGapX; - int columnCount = effectiveTileWidth > 0.0f - ? static_cast((layout.gridRect.width + kGridTileGapX) / effectiveTileWidth) - : 1; - if (columnCount < 1) { - columnCount = 1; - } - - layout.assetTiles.reserve(assetEntries.size()); - for (std::size_t index = 0; index < assetEntries.size(); ++index) { - const int column = static_cast(index % static_cast(columnCount)); - const int row = static_cast(index / static_cast(columnCount)); - const float tileX = layout.gridRect.x + static_cast(column) * (kGridTileWidth + kGridTileGapX); - const float tileY = layout.gridRect.y + static_cast(row) * (kGridTileHeight + kGridTileGapY); - - AssetTileLayout tile = {}; - tile.itemIndex = index; - tile.tileRect = UIRect(tileX, tileY, kGridTileWidth, kGridTileHeight); - tile.previewRect = UIRect( - tile.tileRect.x + (tile.tileRect.width - kGridPreviewWidth) * 0.5f, - tile.tileRect.y + 6.0f, - kGridPreviewWidth, - kGridPreviewHeight); - tile.labelRect = UIRect( - tile.tileRect.x + 4.0f, - tile.previewRect.y + tile.previewRect.height + 8.0f, - tile.tileRect.width - 8.0f, - 18.0f); - layout.assetTiles.push_back(tile); - } - - return layout; -} - -std::size_t ProductProjectPanel::HitTestBreadcrumbItem(const UIPoint& point) const { - for (std::size_t index = 0u; index < m_layout.breadcrumbItems.size(); ++index) { - const BreadcrumbItemLayout& item = m_layout.breadcrumbItems[index]; - if (!item.separator && ContainsPoint(item.rect, point)) { - return index; - } - } - - return kInvalidLayoutIndex; -} - -std::size_t ProductProjectPanel::HitTestAssetTile(const UIPoint& point) const { - for (const AssetTileLayout& tile : m_layout.assetTiles) { - if (ContainsPoint(tile.tileRect, point)) { - return tile.itemIndex; - } - } - - return kInvalidLayoutIndex; -} - -void ProductProjectPanel::SyncCurrentFolderSelection() { +void ProjectPanel::SyncCurrentFolderSelection() { const std::string& currentFolderId = m_browserModel.GetCurrentFolderId(); if (currentFolderId.empty()) { m_folderSelection.ClearSelection(); @@ -466,7 +91,7 @@ void ProductProjectPanel::SyncCurrentFolderSelection() { m_folderSelection.SetSelection(currentFolderId); } -bool ProductProjectPanel::NavigateToFolder(std::string_view itemId, EventSource source) { +bool ProjectPanel::NavigateToFolder(std::string_view itemId, EventSource source) { if (!m_browserModel.NavigateToFolder(itemId)) { return false; } @@ -479,7 +104,7 @@ bool ProductProjectPanel::NavigateToFolder(std::string_view itemId, EventSource return true; } -void ProductProjectPanel::EmitEvent( +void ProjectPanel::EmitEvent( EventKind kind, EventSource source, const FolderEntry* folder) { @@ -497,7 +122,7 @@ void ProductProjectPanel::EmitEvent( m_frameEvents.push_back(std::move(event)); } -void ProductProjectPanel::EmitEvent( +void ProjectPanel::EmitEvent( EventKind kind, EventSource source, const AssetEntry* asset) { @@ -517,336 +142,11 @@ void ProductProjectPanel::EmitEvent( m_frameEvents.push_back(std::move(event)); } -void ProductProjectPanel::EmitSelectionClearedEvent(EventSource source) { +void ProjectPanel::EmitSelectionClearedEvent(EventSource source) { Event event = {}; event.kind = EventKind::AssetSelectionCleared; event.source = source; m_frameEvents.push_back(std::move(event)); } -void ProductProjectPanel::ResetTransientFrames() { - m_treeFrame = {}; - m_frameEvents.clear(); - m_layout = {}; - m_hoveredAssetItemId.clear(); - m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; - m_pressedBreadcrumbIndex = kInvalidLayoutIndex; - m_splitterHovered = false; - m_splitterDragging = false; -} - -void ProductProjectPanel::Update( - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector& inputEvents, - bool allowInteraction, - bool panelActive) { - m_requestPointerCapture = false; - m_requestPointerRelease = false; - m_frameEvents.clear(); - - const UIEditorPanelContentHostPanelState* panelState = - FindMountedProjectPanel(contentHostFrame); - if (panelState == nullptr) { - if (m_splitterDragging) { - m_requestPointerRelease = true; - } - m_visible = false; - ResetTransientFrames(); - return; - } - - if (m_browserModel.GetTreeItems().empty()) { - m_browserModel.Refresh(); - SyncCurrentFolderSelection(); - } - - m_visible = true; - const std::vector filteredEvents = - FilterProjectPanelInputEvents( - panelState->bounds, - inputEvents, - allowInteraction, - panelActive, - m_splitterDragging); - - m_navigationWidth = ClampNavigationWidth(m_navigationWidth, panelState->bounds.width); - m_layout = BuildLayout(panelState->bounds); - const Widgets::UIEditorTreeViewMetrics treeMetrics = BuildProductTreeViewMetrics(); - const std::vector treeEvents = - FilterTreeInputEvents(filteredEvents, m_splitterDragging); - m_treeFrame = UpdateUIEditorTreeViewInteraction( - m_treeInteractionState, - m_folderSelection, - m_folderExpansion, - m_layout.treeRect, - m_browserModel.GetTreeItems(), - treeEvents, - treeMetrics); - - if (m_treeFrame.result.selectionChanged && - !m_treeFrame.result.selectedItemId.empty() && - m_treeFrame.result.selectedItemId != m_browserModel.GetCurrentFolderId()) { - NavigateToFolder(m_treeFrame.result.selectedItemId, EventSource::Tree); - m_layout = BuildLayout(panelState->bounds); - } - - for (const UIInputEvent& event : filteredEvents) { - switch (event.type) { - case UIInputEventType::FocusLost: - m_hoveredAssetItemId.clear(); - m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; - m_pressedBreadcrumbIndex = kInvalidLayoutIndex; - m_splitterHovered = false; - if (m_splitterDragging) { - m_splitterDragging = false; - m_requestPointerRelease = true; - } - break; - - case UIInputEventType::PointerMove: { - if (m_splitterDragging) { - m_navigationWidth = - ClampNavigationWidth(event.position.x - panelState->bounds.x, panelState->bounds.width); - m_layout = BuildLayout(panelState->bounds); - } - - m_splitterHovered = - m_splitterDragging || ContainsPoint(m_layout.dividerRect, event.position); - m_hoveredBreadcrumbIndex = HitTestBreadcrumbItem(event.position); - const std::size_t hoveredAssetIndex = HitTestAssetTile(event.position); - const auto& assetEntries = m_browserModel.GetAssetEntries(); - m_hoveredAssetItemId = - hoveredAssetIndex < assetEntries.size() - ? assetEntries[hoveredAssetIndex].itemId - : std::string(); - break; - } - - case UIInputEventType::PointerLeave: - if (!m_splitterDragging) { - m_splitterHovered = false; - } - m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; - m_hoveredAssetItemId.clear(); - break; - - case UIInputEventType::PointerButtonDown: - if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Left) { - if (ContainsPoint(m_layout.dividerRect, event.position)) { - m_splitterDragging = true; - m_splitterHovered = true; - m_pressedBreadcrumbIndex = kInvalidLayoutIndex; - m_requestPointerCapture = true; - break; - } - - m_pressedBreadcrumbIndex = HitTestBreadcrumbItem(event.position); - - if (!ContainsPoint(m_layout.gridRect, event.position)) { - break; - } - - const auto& assetEntries = m_browserModel.GetAssetEntries(); - const std::size_t hitIndex = HitTestAssetTile(event.position); - if (hitIndex >= assetEntries.size()) { - if (m_assetSelection.HasSelection()) { - m_assetSelection.ClearSelection(); - EmitSelectionClearedEvent(EventSource::Background); - } - break; - } - - const AssetEntry& assetEntry = assetEntries[hitIndex]; - const bool alreadySelected = m_assetSelection.IsSelected(assetEntry.itemId); - const bool selectionChanged = m_assetSelection.SetSelection(assetEntry.itemId); - if (selectionChanged) { - EmitEvent(EventKind::AssetSelected, EventSource::GridPrimary, &assetEntry); - } - - const std::uint64_t nowMs = GetTickCount64(); - const std::uint64_t doubleClickThresholdMs = - static_cast(GetDoubleClickTime()); - const bool doubleClicked = - alreadySelected && - m_lastPrimaryClickedAssetId == assetEntry.itemId && - nowMs >= m_lastPrimaryClickTimeMs && - nowMs - m_lastPrimaryClickTimeMs <= doubleClickThresholdMs; - - m_lastPrimaryClickedAssetId = assetEntry.itemId; - m_lastPrimaryClickTimeMs = nowMs; - if (!doubleClicked) { - break; - } - - if (assetEntry.directory) { - NavigateToFolder(assetEntry.itemId, EventSource::GridDoubleClick); - m_layout = BuildLayout(panelState->bounds); - m_hoveredAssetItemId.clear(); - } else { - EmitEvent(EventKind::AssetOpened, EventSource::GridDoubleClick, &assetEntry); - } - break; - } - - if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Right && - ContainsPoint(m_layout.gridRect, event.position)) { - const auto& assetEntries = m_browserModel.GetAssetEntries(); - const std::size_t hitIndex = HitTestAssetTile(event.position); - if (hitIndex >= assetEntries.size()) { - EmitEvent( - EventKind::ContextMenuRequested, - EventSource::Background, - static_cast(nullptr)); - break; - } - - const AssetEntry& assetEntry = assetEntries[hitIndex]; - if (!m_assetSelection.IsSelected(assetEntry.itemId)) { - m_assetSelection.SetSelection(assetEntry.itemId); - EmitEvent(EventKind::AssetSelected, EventSource::GridSecondary, &assetEntry); - } - EmitEvent(EventKind::ContextMenuRequested, EventSource::GridSecondary, &assetEntry); - } - break; - - case UIInputEventType::PointerButtonUp: - if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) { - break; - } - - if (m_splitterDragging) { - m_splitterDragging = false; - m_splitterHovered = ContainsPoint(m_layout.dividerRect, event.position); - m_requestPointerRelease = true; - break; - } - - { - const std::size_t releasedBreadcrumbIndex = - HitTestBreadcrumbItem(event.position); - if (m_pressedBreadcrumbIndex != kInvalidLayoutIndex && - m_pressedBreadcrumbIndex == releasedBreadcrumbIndex && - releasedBreadcrumbIndex < m_layout.breadcrumbItems.size()) { - const BreadcrumbItemLayout& item = - m_layout.breadcrumbItems[releasedBreadcrumbIndex]; - if (item.clickable) { - NavigateToFolder(item.targetFolderId, EventSource::Breadcrumb); - m_layout = BuildLayout(panelState->bounds); - } - } - m_pressedBreadcrumbIndex = kInvalidLayoutIndex; - } - break; - - default: - break; - } - } -} - -void ProductProjectPanel::Append(UIDrawList& drawList) const { - if (!m_visible || m_layout.bounds.width <= 0.0f || m_layout.bounds.height <= 0.0f) { - return; - } - - const auto& assetEntries = m_browserModel.GetAssetEntries(); - - drawList.AddFilledRect(m_layout.bounds, kSurfaceColor); - drawList.AddFilledRect(m_layout.leftPaneRect, kPaneColor); - drawList.AddFilledRect(m_layout.rightPaneRect, kPaneColor); - drawList.AddFilledRect( - m_layout.dividerRect, - ResolveUIEditorDockHostPalette().splitterColor); - - drawList.AddFilledRect(m_layout.browserHeaderRect, kHeaderColor); - drawList.AddFilledRect( - UIRect( - m_layout.browserHeaderRect.x, - m_layout.browserHeaderRect.y + m_layout.browserHeaderRect.height - kHeaderBottomBorderThickness, - m_layout.browserHeaderRect.width, - kHeaderBottomBorderThickness), - ResolveUIEditorDockHostPalette().splitterColor); - - const Widgets::UIEditorTreeViewPalette treePalette = BuildProductTreeViewPalette(); - const Widgets::UIEditorTreeViewMetrics treeMetrics = BuildProductTreeViewMetrics(); - AppendUIEditorTreeViewBackground( - drawList, - m_treeFrame.layout, - m_browserModel.GetTreeItems(), - m_folderSelection, - m_treeInteractionState.treeViewState, - treePalette, - treeMetrics); - AppendUIEditorTreeViewForeground( - drawList, - m_treeFrame.layout, - m_browserModel.GetTreeItems(), - treePalette, - treeMetrics); - - drawList.PushClipRect(m_layout.browserHeaderRect); - for (std::size_t index = 0u; index < m_layout.breadcrumbItems.size(); ++index) { - const BreadcrumbItemLayout& item = m_layout.breadcrumbItems[index]; - const UIColor textColor = - item.separator - ? kTextMuted - : (index == m_hoveredBreadcrumbIndex && item.clickable - ? kTextStrong - : (item.current ? kTextPrimary : kTextMuted)); - const float textWidth = MeasureTextWidth(m_textMeasurer, item.label, kHeaderFontSize); - const float textX = item.separator - ? item.rect.x - : item.rect.x + (item.rect.width - textWidth) * 0.5f; - drawList.AddText( - UIPoint(textX, ResolveTextTop(item.rect.y, item.rect.height, kHeaderFontSize)), - item.label, - textColor, - kHeaderFontSize); - } - drawList.PopClipRect(); - - for (const AssetTileLayout& tile : m_layout.assetTiles) { - if (tile.itemIndex >= assetEntries.size()) { - continue; - } - - const AssetEntry& assetEntry = assetEntries[tile.itemIndex]; - const bool selected = m_assetSelection.IsSelected(assetEntry.itemId); - const bool hovered = m_hoveredAssetItemId == assetEntry.itemId; - - if (selected || hovered) { - drawList.AddFilledRect( - tile.tileRect, - selected ? kTileSelectedColor : kTileHoverColor); - } - - AppendTilePreview(drawList, tile.previewRect, assetEntry.directory); - - drawList.PushClipRect(tile.labelRect); - const float textWidth = - MeasureTextWidth(m_textMeasurer, assetEntry.displayName, kTileLabelFontSize); - drawList.AddText( - UIPoint( - tile.labelRect.x + (tile.labelRect.width - textWidth) * 0.5f, - ResolveTextTop(tile.labelRect.y, tile.labelRect.height, kTileLabelFontSize)), - assetEntry.displayName, - kTextPrimary, - kTileLabelFontSize); - drawList.PopClipRect(); - } - - if (assetEntries.empty()) { - const UIRect messageRect( - m_layout.gridRect.x, - m_layout.gridRect.y, - m_layout.gridRect.width, - 18.0f); - drawList.AddText( - UIPoint(messageRect.x, ResolveTextTop(messageRect.y, messageRect.height, kHeaderFontSize)), - "Current folder is empty.", - kTextMuted, - kHeaderFontSize); - } -} - } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectPanel.h b/new_editor/app/Features/Project/ProjectPanel.h index f20703b3..0e172b94 100644 --- a/new_editor/app/Features/Project/ProjectPanel.h +++ b/new_editor/app/Features/Project/ProjectPanel.h @@ -1,10 +1,10 @@ #pragma once -#include "Project/ProductProjectBrowserModel.h" +#include "ProjectBrowserModel.h" #include #include -#include +#include #include #include @@ -18,9 +18,9 @@ namespace XCEngine::UI::Editor::App { -class ProductBuiltInIcons; +class BuiltInIcons; -class ProductProjectPanel { +class ProjectPanel { public: enum class CursorKind : std::uint8_t { Arrow = 0, @@ -56,7 +56,7 @@ public: }; void Initialize(const std::filesystem::path& repoRoot); - void SetBuiltInIcons(const ProductBuiltInIcons* icons); + void SetBuiltInIcons(const BuiltInIcons* icons); void SetTextMeasurer(const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer); void ResetInteractionState(); void Update( @@ -73,7 +73,7 @@ public: const std::vector& GetFrameEvents() const; private: - using BrowserModel = ::XCEngine::UI::Editor::App::Project::ProductProjectBrowserModel; + using BrowserModel = ::XCEngine::UI::Editor::App::ProjectBrowserModel; using FolderEntry = BrowserModel::FolderEntry; using AssetEntry = BrowserModel::AssetEntry; @@ -121,7 +121,7 @@ private: void ResetTransientFrames(); BrowserModel m_browserModel = {}; - const ProductBuiltInIcons* m_icons = nullptr; + const BuiltInIcons* m_icons = nullptr; const ::XCEngine::UI::Editor::UIEditorTextMeasurer* m_textMeasurer = nullptr; ::XCEngine::UI::Widgets::UISelectionModel m_folderSelection = {}; ::XCEngine::UI::Widgets::UIExpansionModel m_folderExpansion = {}; @@ -144,3 +144,6 @@ private: }; } // namespace XCEngine::UI::Editor::App + + + diff --git a/new_editor/app/Features/Project/ProjectPanelInteraction.cpp b/new_editor/app/Features/Project/ProjectPanelInteraction.cpp new file mode 100644 index 00000000..4d227dc3 --- /dev/null +++ b/new_editor/app/Features/Project/ProjectPanelInteraction.cpp @@ -0,0 +1,229 @@ +#include "ProjectPanelSupport.h" + +#include + +namespace XCEngine::UI::Editor::App { + +using namespace ProjectPanelSupport; + +void ProjectPanel::ResetTransientFrames() { + m_treeFrame = {}; + m_frameEvents.clear(); + m_layout = {}; + m_hoveredAssetItemId.clear(); + m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; + m_pressedBreadcrumbIndex = kInvalidLayoutIndex; + m_splitterHovered = false; + m_splitterDragging = false; +} + +void ProjectPanel::Update( + const UIEditorPanelContentHostFrame& contentHostFrame, + const std::vector& inputEvents, + bool allowInteraction, + bool panelActive) { + m_requestPointerCapture = false; + m_requestPointerRelease = false; + m_frameEvents.clear(); + + const UIEditorPanelContentHostPanelState* panelState = + FindMountedProjectPanel(contentHostFrame); + if (panelState == nullptr) { + if (m_splitterDragging) { + m_requestPointerRelease = true; + } + m_visible = false; + ResetTransientFrames(); + return; + } + + if (m_browserModel.GetTreeItems().empty()) { + m_browserModel.Refresh(); + SyncCurrentFolderSelection(); + } + + m_visible = true; + const std::vector filteredEvents = + FilterProjectPanelInputEvents( + panelState->bounds, + inputEvents, + allowInteraction, + panelActive, + m_splitterDragging); + + m_navigationWidth = ClampNavigationWidth(m_navigationWidth, panelState->bounds.width); + m_layout = BuildLayout(panelState->bounds); + const Widgets::UIEditorTreeViewMetrics treeMetrics = BuildEditorTreeViewMetrics(); + const std::vector treeEvents = + FilterTreeInputEvents(filteredEvents, m_splitterDragging); + m_treeFrame = UpdateUIEditorTreeViewInteraction( + m_treeInteractionState, + m_folderSelection, + m_folderExpansion, + m_layout.treeRect, + m_browserModel.GetTreeItems(), + treeEvents, + treeMetrics); + + if (m_treeFrame.result.selectionChanged && + !m_treeFrame.result.selectedItemId.empty() && + m_treeFrame.result.selectedItemId != m_browserModel.GetCurrentFolderId()) { + NavigateToFolder(m_treeFrame.result.selectedItemId, EventSource::Tree); + m_layout = BuildLayout(panelState->bounds); + } + + for (const UIInputEvent& event : filteredEvents) { + switch (event.type) { + case UIInputEventType::FocusLost: + m_hoveredAssetItemId.clear(); + m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; + m_pressedBreadcrumbIndex = kInvalidLayoutIndex; + m_splitterHovered = false; + if (m_splitterDragging) { + m_splitterDragging = false; + m_requestPointerRelease = true; + } + break; + + case UIInputEventType::PointerMove: { + if (m_splitterDragging) { + m_navigationWidth = + ClampNavigationWidth(event.position.x - panelState->bounds.x, panelState->bounds.width); + m_layout = BuildLayout(panelState->bounds); + } + + m_splitterHovered = + m_splitterDragging || ContainsPoint(m_layout.dividerRect, event.position); + m_hoveredBreadcrumbIndex = HitTestBreadcrumbItem(event.position); + const std::size_t hoveredAssetIndex = HitTestAssetTile(event.position); + const auto& assetEntries = m_browserModel.GetAssetEntries(); + m_hoveredAssetItemId = + hoveredAssetIndex < assetEntries.size() + ? assetEntries[hoveredAssetIndex].itemId + : std::string(); + break; + } + + case UIInputEventType::PointerLeave: + if (!m_splitterDragging) { + m_splitterHovered = false; + } + m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; + m_hoveredAssetItemId.clear(); + break; + + case UIInputEventType::PointerButtonDown: + if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Left) { + if (ContainsPoint(m_layout.dividerRect, event.position)) { + m_splitterDragging = true; + m_splitterHovered = true; + m_pressedBreadcrumbIndex = kInvalidLayoutIndex; + m_requestPointerCapture = true; + break; + } + + m_pressedBreadcrumbIndex = HitTestBreadcrumbItem(event.position); + + if (!ContainsPoint(m_layout.gridRect, event.position)) { + break; + } + + const auto& assetEntries = m_browserModel.GetAssetEntries(); + const std::size_t hitIndex = HitTestAssetTile(event.position); + if (hitIndex >= assetEntries.size()) { + if (m_assetSelection.HasSelection()) { + m_assetSelection.ClearSelection(); + EmitSelectionClearedEvent(EventSource::Background); + } + break; + } + + const AssetEntry& assetEntry = assetEntries[hitIndex]; + const bool alreadySelected = m_assetSelection.IsSelected(assetEntry.itemId); + const bool selectionChanged = m_assetSelection.SetSelection(assetEntry.itemId); + if (selectionChanged) { + EmitEvent(EventKind::AssetSelected, EventSource::GridPrimary, &assetEntry); + } + + const std::uint64_t nowMs = GetTickCount64(); + const std::uint64_t doubleClickThresholdMs = + static_cast(GetDoubleClickTime()); + const bool doubleClicked = + alreadySelected && + m_lastPrimaryClickedAssetId == assetEntry.itemId && + nowMs >= m_lastPrimaryClickTimeMs && + nowMs - m_lastPrimaryClickTimeMs <= doubleClickThresholdMs; + + m_lastPrimaryClickedAssetId = assetEntry.itemId; + m_lastPrimaryClickTimeMs = nowMs; + if (!doubleClicked) { + break; + } + + if (assetEntry.directory) { + NavigateToFolder(assetEntry.itemId, EventSource::GridDoubleClick); + m_layout = BuildLayout(panelState->bounds); + m_hoveredAssetItemId.clear(); + } else { + EmitEvent(EventKind::AssetOpened, EventSource::GridDoubleClick, &assetEntry); + } + break; + } + + if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Right && + ContainsPoint(m_layout.gridRect, event.position)) { + const auto& assetEntries = m_browserModel.GetAssetEntries(); + const std::size_t hitIndex = HitTestAssetTile(event.position); + if (hitIndex >= assetEntries.size()) { + EmitEvent( + EventKind::ContextMenuRequested, + EventSource::Background, + static_cast(nullptr)); + break; + } + + const AssetEntry& assetEntry = assetEntries[hitIndex]; + if (!m_assetSelection.IsSelected(assetEntry.itemId)) { + m_assetSelection.SetSelection(assetEntry.itemId); + EmitEvent(EventKind::AssetSelected, EventSource::GridSecondary, &assetEntry); + } + EmitEvent(EventKind::ContextMenuRequested, EventSource::GridSecondary, &assetEntry); + } + break; + + case UIInputEventType::PointerButtonUp: + if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) { + break; + } + + if (m_splitterDragging) { + m_splitterDragging = false; + m_splitterHovered = ContainsPoint(m_layout.dividerRect, event.position); + m_requestPointerRelease = true; + break; + } + + { + const std::size_t releasedBreadcrumbIndex = + HitTestBreadcrumbItem(event.position); + if (m_pressedBreadcrumbIndex != kInvalidLayoutIndex && + m_pressedBreadcrumbIndex == releasedBreadcrumbIndex && + releasedBreadcrumbIndex < m_layout.breadcrumbItems.size()) { + const BreadcrumbItemLayout& item = + m_layout.breadcrumbItems[releasedBreadcrumbIndex]; + if (item.clickable) { + NavigateToFolder(item.targetFolderId, EventSource::Breadcrumb); + m_layout = BuildLayout(panelState->bounds); + } + } + m_pressedBreadcrumbIndex = kInvalidLayoutIndex; + } + break; + + default: + break; + } + } +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectPanelLayout.cpp b/new_editor/app/Features/Project/ProjectPanelLayout.cpp new file mode 100644 index 00000000..7af277a7 --- /dev/null +++ b/new_editor/app/Features/Project/ProjectPanelLayout.cpp @@ -0,0 +1,162 @@ +#include "ProjectPanelSupport.h" + +namespace XCEngine::UI::Editor::App { + +using namespace ProjectPanelSupport; + +ProjectPanel::Layout ProjectPanel::BuildLayout(const UIRect& bounds) const { + Layout layout = {}; + const auto& assetEntries = m_browserModel.GetAssetEntries(); + const std::vector breadcrumbSegments = + m_browserModel.BuildBreadcrumbSegments(); + const float dividerThickness = ResolveUIEditorDockHostMetrics().splitterMetrics.thickness; + layout.bounds = UIRect( + bounds.x, + bounds.y, + ClampNonNegative(bounds.width), + ClampNonNegative(bounds.height)); + + const float leftWidth = ClampNavigationWidth(m_navigationWidth, layout.bounds.width); + layout.leftPaneRect = UIRect( + layout.bounds.x, + layout.bounds.y, + leftWidth, + layout.bounds.height); + layout.dividerRect = UIRect( + layout.leftPaneRect.x + layout.leftPaneRect.width, + layout.bounds.y, + dividerThickness, + layout.bounds.height); + layout.rightPaneRect = UIRect( + layout.dividerRect.x + layout.dividerRect.width, + layout.bounds.y, + ClampNonNegative(layout.bounds.width - layout.leftPaneRect.width - layout.dividerRect.width), + layout.bounds.height); + + layout.treeRect = UIRect( + layout.leftPaneRect.x, + layout.leftPaneRect.y + kTreeTopPadding, + layout.leftPaneRect.width, + ClampNonNegative(layout.leftPaneRect.height - kTreeTopPadding)); + + layout.browserHeaderRect = UIRect( + layout.rightPaneRect.x, + layout.rightPaneRect.y, + layout.rightPaneRect.width, + (std::min)(kBrowserHeaderHeight, layout.rightPaneRect.height)); + layout.browserBodyRect = UIRect( + layout.rightPaneRect.x, + layout.browserHeaderRect.y + layout.browserHeaderRect.height, + layout.rightPaneRect.width, + ClampNonNegative(layout.rightPaneRect.height - layout.browserHeaderRect.height)); + layout.gridRect = UIRect( + layout.browserBodyRect.x + kGridInsetX, + layout.browserBodyRect.y + kGridInsetY, + ClampNonNegative(layout.browserBodyRect.width - kGridInsetX * 2.0f), + ClampNonNegative(layout.browserBodyRect.height - kGridInsetY * 2.0f)); + + const float breadcrumbRowHeight = kHeaderFontSize + kBreadcrumbItemPaddingY * 2.0f; + const float breadcrumbY = + layout.browserHeaderRect.y + std::floor((layout.browserHeaderRect.height - breadcrumbRowHeight) * 0.5f); + const float headerRight = + layout.browserHeaderRect.x + layout.browserHeaderRect.width - kHeaderHorizontalPadding; + float nextItemX = layout.browserHeaderRect.x + kHeaderHorizontalPadding; + for (std::size_t index = 0u; index < breadcrumbSegments.size(); ++index) { + if (index > 0u) { + const float separatorWidth = MeasureTextWidth(m_textMeasurer, ">", kHeaderFontSize); + if (nextItemX < headerRight && separatorWidth > 0.0f) { + layout.breadcrumbItems.push_back({ + ">", + {}, + UIRect( + nextItemX, + breadcrumbY, + ClampNonNegative((std::min)(separatorWidth, headerRight - nextItemX)), + breadcrumbRowHeight), + true, + false, + false + }); + } + nextItemX += separatorWidth + kBreadcrumbSpacing; + } + + const ProjectBrowserModel::BreadcrumbSegment& segment = breadcrumbSegments[index]; + const float labelWidth = MeasureTextWidth(m_textMeasurer, segment.label, kHeaderFontSize); + const float itemWidth = labelWidth + kBreadcrumbItemPaddingX * 2.0f; + const float availableWidth = headerRight - nextItemX; + if (availableWidth <= 0.0f) { + break; + } + + layout.breadcrumbItems.push_back({ + segment.label, + segment.targetFolderId, + UIRect( + nextItemX, + breadcrumbY, + ClampNonNegative((std::min)(itemWidth, availableWidth)), + breadcrumbRowHeight), + false, + !segment.current, + segment.current + }); + nextItemX += itemWidth + kBreadcrumbSpacing; + } + + const float effectiveTileWidth = kGridTileWidth + kGridTileGapX; + int columnCount = effectiveTileWidth > 0.0f + ? static_cast((layout.gridRect.width + kGridTileGapX) / effectiveTileWidth) + : 1; + if (columnCount < 1) { + columnCount = 1; + } + + layout.assetTiles.reserve(assetEntries.size()); + for (std::size_t index = 0; index < assetEntries.size(); ++index) { + const int column = static_cast(index % static_cast(columnCount)); + const int row = static_cast(index / static_cast(columnCount)); + const float tileX = layout.gridRect.x + static_cast(column) * (kGridTileWidth + kGridTileGapX); + const float tileY = layout.gridRect.y + static_cast(row) * (kGridTileHeight + kGridTileGapY); + + AssetTileLayout tile = {}; + tile.itemIndex = index; + tile.tileRect = UIRect(tileX, tileY, kGridTileWidth, kGridTileHeight); + tile.previewRect = UIRect( + tile.tileRect.x + (tile.tileRect.width - kGridPreviewWidth) * 0.5f, + tile.tileRect.y + 6.0f, + kGridPreviewWidth, + kGridPreviewHeight); + tile.labelRect = UIRect( + tile.tileRect.x + 4.0f, + tile.previewRect.y + tile.previewRect.height + 8.0f, + tile.tileRect.width - 8.0f, + 18.0f); + layout.assetTiles.push_back(tile); + } + + return layout; +} + +std::size_t ProjectPanel::HitTestBreadcrumbItem(const UIPoint& point) const { + for (std::size_t index = 0u; index < m_layout.breadcrumbItems.size(); ++index) { + const BreadcrumbItemLayout& item = m_layout.breadcrumbItems[index]; + if (!item.separator && ContainsPoint(item.rect, point)) { + return index; + } + } + + return kInvalidLayoutIndex; +} + +std::size_t ProjectPanel::HitTestAssetTile(const UIPoint& point) const { + for (const AssetTileLayout& tile : m_layout.assetTiles) { + if (ContainsPoint(tile.tileRect, point)) { + return tile.itemIndex; + } + } + + return kInvalidLayoutIndex; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectPanelRendering.cpp b/new_editor/app/Features/Project/ProjectPanelRendering.cpp new file mode 100644 index 00000000..af352bc5 --- /dev/null +++ b/new_editor/app/Features/Project/ProjectPanelRendering.cpp @@ -0,0 +1,112 @@ +#include "ProjectPanelSupport.h" + +namespace XCEngine::UI::Editor::App { + +using namespace ProjectPanelSupport; + +void ProjectPanel::Append(UIDrawList& drawList) const { + if (!m_visible || m_layout.bounds.width <= 0.0f || m_layout.bounds.height <= 0.0f) { + return; + } + + const auto& assetEntries = m_browserModel.GetAssetEntries(); + + drawList.AddFilledRect(m_layout.bounds, kSurfaceColor); + drawList.AddFilledRect(m_layout.leftPaneRect, kPaneColor); + drawList.AddFilledRect(m_layout.rightPaneRect, kPaneColor); + drawList.AddFilledRect( + m_layout.dividerRect, + ResolveUIEditorDockHostPalette().splitterColor); + + drawList.AddFilledRect(m_layout.browserHeaderRect, kHeaderColor); + drawList.AddFilledRect( + UIRect( + m_layout.browserHeaderRect.x, + m_layout.browserHeaderRect.y + m_layout.browserHeaderRect.height - kHeaderBottomBorderThickness, + m_layout.browserHeaderRect.width, + kHeaderBottomBorderThickness), + ResolveUIEditorDockHostPalette().splitterColor); + + const Widgets::UIEditorTreeViewPalette treePalette = BuildEditorTreeViewPalette(); + const Widgets::UIEditorTreeViewMetrics treeMetrics = BuildEditorTreeViewMetrics(); + AppendUIEditorTreeViewBackground( + drawList, + m_treeFrame.layout, + m_browserModel.GetTreeItems(), + m_folderSelection, + m_treeInteractionState.treeViewState, + treePalette, + treeMetrics); + AppendUIEditorTreeViewForeground( + drawList, + m_treeFrame.layout, + m_browserModel.GetTreeItems(), + treePalette, + treeMetrics); + + drawList.PushClipRect(m_layout.browserHeaderRect); + for (std::size_t index = 0u; index < m_layout.breadcrumbItems.size(); ++index) { + const BreadcrumbItemLayout& item = m_layout.breadcrumbItems[index]; + const UIColor textColor = + item.separator + ? kTextMuted + : (index == m_hoveredBreadcrumbIndex && item.clickable + ? kTextStrong + : (item.current ? kTextPrimary : kTextMuted)); + const float textWidth = MeasureTextWidth(m_textMeasurer, item.label, kHeaderFontSize); + const float textX = item.separator + ? item.rect.x + : item.rect.x + (item.rect.width - textWidth) * 0.5f; + drawList.AddText( + UIPoint(textX, ResolveTextTop(item.rect.y, item.rect.height, kHeaderFontSize)), + item.label, + textColor, + kHeaderFontSize); + } + drawList.PopClipRect(); + + for (const AssetTileLayout& tile : m_layout.assetTiles) { + if (tile.itemIndex >= assetEntries.size()) { + continue; + } + + const AssetEntry& assetEntry = assetEntries[tile.itemIndex]; + const bool selected = m_assetSelection.IsSelected(assetEntry.itemId); + const bool hovered = m_hoveredAssetItemId == assetEntry.itemId; + + if (selected || hovered) { + drawList.AddFilledRect( + tile.tileRect, + selected ? kTileSelectedColor : kTileHoverColor); + } + + AppendTilePreview(drawList, tile.previewRect, assetEntry.directory); + + drawList.PushClipRect(tile.labelRect); + const float textWidth = + MeasureTextWidth(m_textMeasurer, assetEntry.displayName, kTileLabelFontSize); + drawList.AddText( + UIPoint( + tile.labelRect.x + (tile.labelRect.width - textWidth) * 0.5f, + ResolveTextTop(tile.labelRect.y, tile.labelRect.height, kTileLabelFontSize)), + assetEntry.displayName, + kTextPrimary, + kTileLabelFontSize); + drawList.PopClipRect(); + } + + if (assetEntries.empty()) { + const UIRect messageRect( + m_layout.gridRect.x, + m_layout.gridRect.y, + m_layout.gridRect.width, + 18.0f); + drawList.AddText( + UIPoint(messageRect.x, ResolveTextTop(messageRect.y, messageRect.height, kHeaderFontSize)), + "Current folder is empty.", + kTextMuted, + kHeaderFontSize); + } +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectPanelSupport.h b/new_editor/app/Features/Project/ProjectPanelSupport.h new file mode 100644 index 00000000..960ab529 --- /dev/null +++ b/new_editor/app/Features/Project/ProjectPanelSupport.h @@ -0,0 +1,219 @@ +#pragma once + +#include "ProjectPanel.h" + +#include "Rendering/Assets/BuiltInIcons.h" +#include "UI/Styles/EditorTreeViewStyle.h" + +#include +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::App::ProjectPanelSupport { + +using ::XCEngine::UI::Editor::UIEditorTextMeasureRequest; +using ::XCEngine::UI::Editor::UIEditorTextMeasurer; +using ::XCEngine::UI::UIColor; +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; +using Widgets::AppendUIEditorTreeViewBackground; +using Widgets::AppendUIEditorTreeViewForeground; + +inline constexpr std::string_view kProjectPanelId = "project"; +inline constexpr std::size_t kInvalidLayoutIndex = static_cast(-1); + +inline constexpr float kBrowserHeaderHeight = 24.0f; +inline constexpr float kNavigationMinWidth = 180.0f; +inline constexpr float kBrowserMinWidth = 260.0f; +inline constexpr float kHeaderHorizontalPadding = 10.0f; +inline constexpr float kHeaderBottomBorderThickness = 1.0f; +inline constexpr float kBreadcrumbItemPaddingX = 4.0f; +inline constexpr float kBreadcrumbItemPaddingY = 1.0f; +inline constexpr float kBreadcrumbSpacing = 3.0f; +inline constexpr float kTreeTopPadding = 0.0f; +inline constexpr float kGridInsetX = 16.0f; +inline constexpr float kGridInsetY = 12.0f; +inline constexpr float kGridTileWidth = 92.0f; +inline constexpr float kGridTileHeight = 92.0f; +inline constexpr float kGridTileGapX = 12.0f; +inline constexpr float kGridTileGapY = 12.0f; +inline constexpr float kGridPreviewWidth = 68.0f; +inline constexpr float kGridPreviewHeight = 54.0f; +inline constexpr float kHeaderFontSize = 12.0f; +inline constexpr float kTileLabelFontSize = 11.0f; + +inline constexpr UIColor kSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); +inline constexpr UIColor kPaneColor(0.10f, 0.10f, 0.10f, 1.0f); +inline constexpr UIColor kHeaderColor(0.11f, 0.11f, 0.11f, 1.0f); +inline constexpr UIColor kTextPrimary(0.880f, 0.880f, 0.880f, 1.0f); +inline constexpr UIColor kTextStrong(0.930f, 0.930f, 0.930f, 1.0f); +inline constexpr UIColor kTextMuted(0.640f, 0.640f, 0.640f, 1.0f); +inline constexpr UIColor kTileHoverColor(0.14f, 0.14f, 0.14f, 1.0f); +inline constexpr UIColor kTileSelectedColor(0.18f, 0.18f, 0.18f, 1.0f); +inline constexpr UIColor kTilePreviewFillColor(0.15f, 0.15f, 0.15f, 1.0f); +inline constexpr UIColor kTilePreviewShadeColor(0.12f, 0.12f, 0.12f, 1.0f); +inline constexpr UIColor kTilePreviewOutlineColor(0.920f, 0.920f, 0.920f, 0.20f); + +inline bool ContainsPoint(const UIRect& rect, const UIPoint& point) { + return point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +inline float ClampNonNegative(float value) { + return (std::max)(value, 0.0f); +} + +inline float ResolveTextTop(float rectY, float rectHeight, float fontSize) { + const float lineHeight = fontSize * 1.6f; + return rectY + std::floor((rectHeight - lineHeight) * 0.5f); +} + +inline float MeasureTextWidth( + const UIEditorTextMeasurer* textMeasurer, + std::string_view text, + float fontSize) { + if (text.empty()) { + return 0.0f; + } + + if (textMeasurer != nullptr) { + const float measuredWidth = + textMeasurer->MeasureTextWidth(UIEditorTextMeasureRequest{ text, fontSize }); + if (measuredWidth > 0.0f) { + return measuredWidth; + } + } + + return static_cast(text.size()) * fontSize * 0.56f; +} + +inline std::vector FilterProjectPanelInputEvents( + const UIRect& bounds, + const std::vector& inputEvents, + bool allowInteraction, + bool panelActive, + bool captureActive) { + if (!allowInteraction && !captureActive) { + return {}; + } + + std::vector filteredEvents = {}; + filteredEvents.reserve(inputEvents.size()); + for (const UIInputEvent& event : inputEvents) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + if (captureActive || ContainsPoint(bounds, event.position)) { + filteredEvents.push_back(event); + } + break; + case UIInputEventType::PointerLeave: + filteredEvents.push_back(event); + break; + case UIInputEventType::FocusGained: + case UIInputEventType::FocusLost: + if (panelActive || captureActive) { + filteredEvents.push_back(event); + } + break; + case UIInputEventType::KeyDown: + case UIInputEventType::KeyUp: + case UIInputEventType::Character: + if (panelActive) { + filteredEvents.push_back(event); + } + break; + default: + break; + } + } + + return filteredEvents; +} + +inline std::vector FilterTreeInputEvents( + const std::vector& inputEvents, + bool suppressPointerInput) { + if (!suppressPointerInput) { + return inputEvents; + } + + std::vector filteredEvents = {}; + filteredEvents.reserve(inputEvents.size()); + for (const UIInputEvent& event : inputEvents) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + case UIInputEventType::PointerEnter: + break; + default: + filteredEvents.push_back(event); + break; + } + } + return filteredEvents; +} + +inline ::XCEngine::UI::UITextureHandle ResolveFolderIcon(const BuiltInIcons* icons) { + return icons != nullptr + ? icons->Resolve(BuiltInIconKind::Folder) + : ::XCEngine::UI::UITextureHandle {}; +} + +inline float ClampNavigationWidth(float value, float totalWidth) { + const float maxWidth = + (std::max)( + kNavigationMinWidth, + totalWidth - kBrowserMinWidth - ResolveUIEditorDockHostMetrics().splitterMetrics.thickness); + return std::clamp(value, kNavigationMinWidth, maxWidth); +} + +inline void AppendTilePreview( + UIDrawList& drawList, + const UIRect& previewRect, + bool directory) { + if (directory) { + const UIRect tabRect( + previewRect.x + 8.0f, + previewRect.y + 7.0f, + 22.0f, + 7.0f); + const UIRect bodyRect( + previewRect.x + 4.0f, + previewRect.y + 13.0f, + previewRect.width - 8.0f, + previewRect.height - 18.0f); + drawList.AddFilledRect(tabRect, kTilePreviewShadeColor, 1.0f); + drawList.AddFilledRect(bodyRect, kTilePreviewFillColor, 1.0f); + drawList.AddRectOutline(bodyRect, kTilePreviewOutlineColor, 1.0f, 1.0f); + return; + } + + const UIRect sheetRect( + previewRect.x + 10.0f, + previewRect.y + 4.0f, + previewRect.width - 20.0f, + previewRect.height - 8.0f); + const UIRect accentRect( + sheetRect.x, + sheetRect.y, + sheetRect.width, + 7.0f); + drawList.AddFilledRect(sheetRect, kTilePreviewFillColor, 1.0f); + drawList.AddFilledRect(accentRect, kTilePreviewShadeColor, 1.0f); + drawList.AddRectOutline(sheetRect, kTilePreviewOutlineColor, 1.0f, 1.0f); +} + +} // namespace XCEngine::UI::Editor::App::ProjectPanelSupport diff --git a/new_editor/app/Features/Shared/BuiltInIcons.cpp b/new_editor/app/Features/Shared/BuiltInIcons.cpp deleted file mode 100644 index 2974d844..00000000 --- a/new_editor/app/Features/Shared/BuiltInIcons.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include "ProductBuiltInIcons.h" -#include "EditorResources.h" - -#include - -namespace XCEngine::UI::Editor::App { - -namespace { - -void AppendLoadError( - std::ostringstream& stream, - std::string_view label, - const std::string& error) { - if (error.empty()) { - return; - } - - if (stream.tellp() > 0) { - stream << '\n'; - } - stream << label << ": " << error; -} - -bool LoadEmbeddedPngBytes( - UINT resourceId, - const std::uint8_t*& outData, - std::size_t& outSize, - std::string& outError) { - outData = nullptr; - outSize = 0u; - outError.clear(); - - HMODULE module = GetModuleHandleW(nullptr); - if (module == nullptr) { - outError = "GetModuleHandleW(nullptr) returned null."; - return false; - } - - HRSRC resource = FindResourceW(module, MAKEINTRESOURCEW(resourceId), L"PNG"); - if (resource == nullptr) { - outError = "FindResourceW failed."; - return false; - } - - HGLOBAL resourceData = LoadResource(module, resource); - if (resourceData == nullptr) { - outError = "LoadResource failed."; - return false; - } - - const DWORD resourceSize = SizeofResource(module, resource); - if (resourceSize == 0u) { - outError = "SizeofResource returned zero."; - return false; - } - - const void* lockedBytes = LockResource(resourceData); - if (lockedBytes == nullptr) { - outError = "LockResource failed."; - return false; - } - - outData = reinterpret_cast(lockedBytes); - outSize = static_cast(resourceSize); - return true; -} - -void LoadEmbeddedPngTexture( - Host::NativeRenderer& renderer, - UINT resourceId, - std::string_view label, - ::XCEngine::UI::UITextureHandle& outTexture, - std::ostringstream& errorStream) { - const std::uint8_t* bytes = nullptr; - std::size_t byteCount = 0u; - std::string error = {}; - if (!LoadEmbeddedPngBytes(resourceId, bytes, byteCount, error)) { - AppendLoadError(errorStream, label, error); - return; - } - - error.clear(); - if (!renderer.LoadTextureFromMemory(bytes, byteCount, outTexture, error)) { - AppendLoadError(errorStream, label, error); - } -} - -} // namespace - -void ProductBuiltInIcons::Initialize(Host::NativeRenderer& renderer) { - Shutdown(); - - m_renderer = &renderer; - - std::ostringstream errorStream = {}; - LoadEmbeddedPngTexture( - renderer, - IDR_PNG_FOLDER_ICON, - "folder_icon.png", - m_folderIcon, - errorStream); - LoadEmbeddedPngTexture( - renderer, - IDR_PNG_GAMEOBJECT_ICON, - "gameobject_icon.png", - m_gameObjectIcon, - errorStream); - LoadEmbeddedPngTexture( - renderer, - IDR_PNG_SCENE_ICON, - "scene_icon.png", - m_sceneIcon, - errorStream); - - m_lastError = errorStream.str(); -} - -void ProductBuiltInIcons::Shutdown() { - if (m_renderer != nullptr) { - m_renderer->ReleaseTexture(m_folderIcon); - m_renderer->ReleaseTexture(m_gameObjectIcon); - m_renderer->ReleaseTexture(m_sceneIcon); - } - - m_renderer = nullptr; - m_lastError.clear(); -} - -const ::XCEngine::UI::UITextureHandle& ProductBuiltInIcons::Resolve( - ProductBuiltInIconKind kind) const { - switch (kind) { - case ProductBuiltInIconKind::Folder: - return m_folderIcon; - case ProductBuiltInIconKind::GameObject: - return m_gameObjectIcon; - case ProductBuiltInIconKind::Scene: - return m_sceneIcon; - default: - return m_folderIcon; - } -} - -const std::string& ProductBuiltInIcons::GetLastError() const { - return m_lastError; -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Shared/EditorTreeViewStyle.h b/new_editor/app/Features/Shared/EditorTreeViewStyle.h deleted file mode 100644 index bff7c4b4..00000000 --- a/new_editor/app/Features/Shared/EditorTreeViewStyle.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include - -namespace XCEngine::UI::Editor::App { - -inline constexpr ::XCEngine::UI::UIColor kProductTreeSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); -inline constexpr ::XCEngine::UI::UIColor kProductTreeHoverColor(0.13f, 0.13f, 0.13f, 1.0f); -inline constexpr ::XCEngine::UI::UIColor kProductTreeSelectedColor(0.16f, 0.16f, 0.16f, 1.0f); -inline constexpr ::XCEngine::UI::UIColor kProductTreeDisclosureColor(0.620f, 0.620f, 0.620f, 1.0f); -inline constexpr ::XCEngine::UI::UIColor kProductTreeTextColor(0.900f, 0.900f, 0.900f, 1.0f); - -inline Widgets::UIEditorTreeViewMetrics BuildProductTreeViewMetrics() { - Widgets::UIEditorTreeViewMetrics metrics = {}; - metrics.rowHeight = 20.0f; - metrics.rowGap = 0.0f; - metrics.horizontalPadding = 6.0f; - metrics.indentWidth = 14.0f; - metrics.disclosureExtent = 18.0f; - metrics.disclosureLabelGap = 2.0f; - metrics.iconExtent = 18.0f; - metrics.iconLabelGap = 2.0f; - metrics.iconInsetY = -1.0f; - metrics.labelInsetY = 0.0f; - metrics.cornerRounding = 0.0f; - metrics.borderThickness = 0.0f; - metrics.focusedBorderThickness = 0.0f; - return metrics; -} - -inline Widgets::UIEditorTreeViewPalette BuildProductTreeViewPalette() { - Widgets::UIEditorTreeViewPalette palette = {}; - palette.surfaceColor = kProductTreeSurfaceColor; - palette.borderColor = kProductTreeSurfaceColor; - palette.focusedBorderColor = kProductTreeSurfaceColor; - palette.rowHoverColor = kProductTreeHoverColor; - palette.rowSelectedColor = kProductTreeSelectedColor; - palette.rowSelectedFocusedColor = kProductTreeSelectedColor; - palette.disclosureColor = kProductTreeDisclosureColor; - palette.textColor = kProductTreeTextColor; - return palette; -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Host/BorderlessWindowChrome.cpp b/new_editor/app/Host/BorderlessWindowChrome.cpp deleted file mode 100644 index 961f7710..00000000 --- a/new_editor/app/Host/BorderlessWindowChrome.cpp +++ /dev/null @@ -1,471 +0,0 @@ -#include "BorderlessWindowChrome.h" - -#include -#include -#include - -namespace XCEngine::UI::Editor::Host { - -namespace { - -using ::XCEngine::UI::UIColor; -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; - -constexpr UIColor kTransparentColor(0.0f, 0.0f, 0.0f, 0.0f); - -float ResolveGlyphBoxSize(const UIRect& rect, float ratio, float minSize) { - return (std::max)(minSize, static_cast(std::round(rect.height * ratio))); -} - -bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) { - return rect.width > 0.0f && - rect.height > 0.0f && - point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; -} - -void AppendMinimizeGlyph( - UIDrawList& drawList, - const UIRect& rect, - const UIColor& color, - float thickness) { - const float centerX = rect.x + rect.width * 0.5f; - const float centerY = rect.y + rect.height * 0.5f; - const float halfWidth = ResolveGlyphBoxSize(rect, 0.38f, 10.0f) * 0.5f; - const float y = centerY; - drawList.AddLine( - UIPoint(centerX - halfWidth, y), - UIPoint(centerX + halfWidth, y), - color, - thickness); -} - -void AppendMaximizeGlyph( - UIDrawList& drawList, - const UIRect& rect, - const UIColor& color, - float thickness) { - const float centerX = rect.x + rect.width * 0.5f; - const float centerY = rect.y + rect.height * 0.5f; - const float boxSize = ResolveGlyphBoxSize(rect, 0.32f, 9.0f); - const float halfExtent = boxSize * 0.5f; - const float left = centerX - halfExtent; - const float top = centerY - halfExtent; - const float right = left + boxSize; - const float bottom = top + boxSize; - drawList.AddLine(UIPoint(left, top), UIPoint(right, top), color, thickness); - drawList.AddLine(UIPoint(left, top), UIPoint(left, bottom), color, thickness); - drawList.AddLine(UIPoint(right, top), UIPoint(right, bottom), color, thickness); - drawList.AddLine(UIPoint(left, bottom), UIPoint(right, bottom), color, thickness); -} - -void AppendRestoreGlyph( - UIDrawList& drawList, - const UIRect& rect, - const UIColor& color, - float thickness) { - const float centerX = rect.x + rect.width * 0.5f; - const float centerY = rect.y + rect.height * 0.5f; - const float boxSize = ResolveGlyphBoxSize(rect, 0.29f, 8.0f); - const float halfExtent = boxSize * 0.5f; - const float offset = 1.0f; - - const float backLeft = centerX - halfExtent + offset; - const float backTop = centerY - halfExtent - offset; - const float backRight = backLeft + boxSize; - const float backBottom = backTop + boxSize; - - const float frontLeft = centerX - halfExtent - offset; - const float frontTop = centerY - halfExtent + offset; - const float frontRight = frontLeft + boxSize; - const float frontBottom = frontTop + boxSize; - - // Only draw the exposed segments of the back window. - drawList.AddLine(UIPoint(backLeft, backTop), UIPoint(backRight, backTop), color, thickness); - drawList.AddLine(UIPoint(backLeft, backTop), UIPoint(backLeft, frontTop), color, thickness); - drawList.AddLine(UIPoint(backRight, backTop), UIPoint(backRight, backBottom), color, thickness); - drawList.AddLine(UIPoint(frontRight, backBottom), UIPoint(backRight, backBottom), color, thickness); - - drawList.AddLine(UIPoint(frontLeft, frontTop), UIPoint(frontRight, frontTop), color, thickness); - drawList.AddLine(UIPoint(frontLeft, frontTop), UIPoint(frontLeft, frontBottom), color, thickness); - drawList.AddLine(UIPoint(frontRight, frontTop), UIPoint(frontRight, frontBottom), color, thickness); - drawList.AddLine(UIPoint(frontLeft, frontBottom), UIPoint(frontRight, frontBottom), color, thickness); -} - -void AppendCloseGlyph( - UIDrawList& drawList, - const UIRect& rect, - const UIColor& color, - float thickness) { - const float centerX = rect.x + rect.width * 0.5f; - const float centerY = rect.y + rect.height * 0.5f; - const float halfWidth = ResolveGlyphBoxSize(rect, 0.29f, 8.0f) * 0.5f; - const float halfHeight = halfWidth; - drawList.AddLine( - UIPoint(centerX - halfWidth, centerY - halfHeight), - UIPoint(centerX + halfWidth, centerY + halfHeight), - color, - thickness); - drawList.AddLine( - UIPoint(centerX + halfWidth, centerY - halfHeight), - UIPoint(centerX - halfWidth, centerY + halfHeight), - color, - thickness); -} - -::XCEngine::UI::UIColor ResolveButtonFillColor( - BorderlessWindowChromeHitTarget target, - const BorderlessWindowChromeState& state, - const BorderlessWindowChromePalette& palette) { - const bool hovered = state.hoveredTarget == target; - const bool pressed = state.pressedTarget == target; - if (target == BorderlessWindowChromeHitTarget::CloseButton) { - if (pressed) { - return palette.closeButtonPressedColor; - } - if (hovered) { - return palette.closeButtonHoverColor; - } - return kTransparentColor; - } - - if (pressed) { - return palette.buttonPressedColor; - } - if (hovered) { - return palette.buttonHoverColor; - } - return kTransparentColor; -} - -::XCEngine::UI::UIColor ResolveIconColor( - BorderlessWindowChromeHitTarget target, - const BorderlessWindowChromeState& state, - const BorderlessWindowChromePalette& palette) { - if (target == BorderlessWindowChromeHitTarget::CloseButton && - (state.hoveredTarget == target || state.pressedTarget == target)) { - return palette.closeIconHoverColor; - } - - return palette.iconColor; -} - -int QuerySystemMetricForDpi(int index, UINT dpi) { - const HMODULE user32 = GetModuleHandleW(L"user32.dll"); - if (user32 != nullptr) { - using GetSystemMetricsForDpiFn = int(WINAPI*)(int, UINT); - const auto getSystemMetricsForDpi = - reinterpret_cast( - GetProcAddress(user32, "GetSystemMetricsForDpi")); - if (getSystemMetricsForDpi != nullptr) { - return getSystemMetricsForDpi(index, dpi); - } - } - - return GetSystemMetrics(index); -} - -bool IsWindowAlignedToMonitorWorkArea(HWND hwnd) { - if (hwnd == nullptr) { - return false; - } - - RECT windowRect = {}; - if (!GetWindowRect(hwnd, &windowRect)) { - return false; - } - - const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); - if (monitor == nullptr) { - return false; - } - - MONITORINFO monitorInfo = {}; - monitorInfo.cbSize = sizeof(monitorInfo); - if (!GetMonitorInfoW(monitor, &monitorInfo)) { - return false; - } - - const RECT& workArea = monitorInfo.rcWork; - return windowRect.left == workArea.left && - windowRect.top == workArea.top && - windowRect.right == workArea.right && - windowRect.bottom == workArea.bottom; -} - -void ApplyDwmBoolWindowAttribute(HWND hwnd, DWORD attribute, BOOL value) { - if (hwnd == nullptr) { - return; - } - - using DwmSetWindowAttributeFn = HRESULT(WINAPI*)(HWND, DWORD, LPCVOID, DWORD); - static const auto setWindowAttribute = []() -> DwmSetWindowAttributeFn { - HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll"); - if (dwmapi == nullptr) { - dwmapi = LoadLibraryW(L"dwmapi.dll"); - } - if (dwmapi == nullptr) { - return nullptr; - } - - return reinterpret_cast( - GetProcAddress(dwmapi, "DwmSetWindowAttribute")); - }(); - - if (setWindowAttribute != nullptr) { - setWindowAttribute(hwnd, attribute, &value, sizeof(value)); - } -} - -void ApplyDwmColorWindowAttribute(HWND hwnd, DWORD attribute, COLORREF value) { - if (hwnd == nullptr) { - return; - } - - using DwmSetWindowAttributeFn = HRESULT(WINAPI*)(HWND, DWORD, LPCVOID, DWORD); - static const auto setWindowAttribute = []() -> DwmSetWindowAttributeFn { - HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll"); - if (dwmapi == nullptr) { - dwmapi = LoadLibraryW(L"dwmapi.dll"); - } - if (dwmapi == nullptr) { - return nullptr; - } - - return reinterpret_cast( - GetProcAddress(dwmapi, "DwmSetWindowAttribute")); - }(); - - if (setWindowAttribute != nullptr) { - setWindowAttribute(hwnd, attribute, &value, sizeof(value)); - } -} - -void ApplyDwmIntWindowAttribute(HWND hwnd, DWORD attribute, int value) { - if (hwnd == nullptr) { - return; - } - - using DwmSetWindowAttributeFn = HRESULT(WINAPI*)(HWND, DWORD, LPCVOID, DWORD); - static const auto setWindowAttribute = []() -> DwmSetWindowAttributeFn { - HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll"); - if (dwmapi == nullptr) { - dwmapi = LoadLibraryW(L"dwmapi.dll"); - } - if (dwmapi == nullptr) { - return nullptr; - } - - return reinterpret_cast( - GetProcAddress(dwmapi, "DwmSetWindowAttribute")); - }(); - - if (setWindowAttribute != nullptr) { - setWindowAttribute(hwnd, attribute, &value, sizeof(value)); - } -} - -} // namespace - -BorderlessWindowChromeLayout BuildBorderlessWindowChromeLayout( - const UIRect& titleBarRect, - float leadingOccupiedRight, - const BorderlessWindowChromeMetrics& metrics) { - BorderlessWindowChromeLayout layout = {}; - layout.titleBarRect = titleBarRect; - if (titleBarRect.width <= 0.0f || titleBarRect.height <= 0.0f) { - return layout; - } - - const float buttonWidth = (std::max)(metrics.buttonWidth, 0.0f); - const float buttonX3 = titleBarRect.x + titleBarRect.width - metrics.buttonInsetX - buttonWidth; - const float buttonX2 = buttonX3 - buttonWidth; - const float buttonX1 = buttonX2 - buttonWidth; - - layout.minimizeButtonRect = UIRect(buttonX1, titleBarRect.y, buttonWidth, titleBarRect.height); - layout.maximizeRestoreButtonRect = UIRect(buttonX2, titleBarRect.y, buttonWidth, titleBarRect.height); - layout.closeButtonRect = UIRect(buttonX3, titleBarRect.y, buttonWidth, titleBarRect.height); - - const float dragLeft = - (std::max)(titleBarRect.x, leadingOccupiedRight + metrics.dragPaddingLeft); - const float dragRight = - (std::max)(dragLeft, layout.minimizeButtonRect.x - metrics.dragPaddingRight); - layout.dragRect = UIRect( - dragLeft, - titleBarRect.y, - (std::max)(0.0f, dragRight - dragLeft), - titleBarRect.height); - return layout; -} - -BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome( - const BorderlessWindowChromeLayout& layout, - const UIPoint& point) { - if (IsPointInsideRect(layout.closeButtonRect, point)) { - return BorderlessWindowChromeHitTarget::CloseButton; - } - if (IsPointInsideRect(layout.maximizeRestoreButtonRect, point)) { - return BorderlessWindowChromeHitTarget::MaximizeRestoreButton; - } - if (IsPointInsideRect(layout.minimizeButtonRect, point)) { - return BorderlessWindowChromeHitTarget::MinimizeButton; - } - if (IsPointInsideRect(layout.dragRect, point)) { - return BorderlessWindowChromeHitTarget::DragRegion; - } - return BorderlessWindowChromeHitTarget::None; -} - -void AppendBorderlessWindowChrome( - UIDrawList& drawList, - const BorderlessWindowChromeLayout& layout, - const BorderlessWindowChromeState& state, - bool maximized, - const BorderlessWindowChromePalette& palette, - const BorderlessWindowChromeMetrics& metrics) { - const struct ButtonEntry { - BorderlessWindowChromeHitTarget target; - UIRect rect; - } buttons[] = { - { BorderlessWindowChromeHitTarget::MinimizeButton, layout.minimizeButtonRect }, - { BorderlessWindowChromeHitTarget::MaximizeRestoreButton, layout.maximizeRestoreButtonRect }, - { BorderlessWindowChromeHitTarget::CloseButton, layout.closeButtonRect } - }; - - for (const ButtonEntry& button : buttons) { - const UIColor fill = ResolveButtonFillColor(button.target, state, palette); - if (fill.a > 0.0f) { - drawList.AddFilledRect(button.rect, fill); - } - - const UIColor iconColor = ResolveIconColor(button.target, state, palette); - switch (button.target) { - case BorderlessWindowChromeHitTarget::MinimizeButton: - AppendMinimizeGlyph( - drawList, - button.rect, - iconColor, - metrics.iconThickness); - break; - case BorderlessWindowChromeHitTarget::MaximizeRestoreButton: - if (maximized) { - AppendRestoreGlyph( - drawList, - button.rect, - iconColor, - metrics.iconThickness); - } else { - AppendMaximizeGlyph( - drawList, - button.rect, - iconColor, - metrics.iconThickness); - } - break; - case BorderlessWindowChromeHitTarget::CloseButton: - AppendCloseGlyph( - drawList, - button.rect, - iconColor, - metrics.iconThickness); - break; - case BorderlessWindowChromeHitTarget::DragRegion: - case BorderlessWindowChromeHitTarget::None: - default: - break; - } - } -} - -void EnableBorderlessWindowShadow(HWND hwnd) { - if (hwnd == nullptr) { - return; - } - - using DwmExtendFrameIntoClientAreaFn = HRESULT(WINAPI*)(HWND, const MARGINS*); - static const auto extendFrameIntoClientArea = []() -> DwmExtendFrameIntoClientAreaFn { - HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll"); - if (dwmapi == nullptr) { - dwmapi = LoadLibraryW(L"dwmapi.dll"); - } - if (dwmapi == nullptr) { - return nullptr; - } - return reinterpret_cast( - GetProcAddress(dwmapi, "DwmExtendFrameIntoClientArea")); - }(); - if (extendFrameIntoClientArea != nullptr) { - const bool maximized = IsZoomed(hwnd) || IsWindowAlignedToMonitorWorkArea(hwnd); - const MARGINS margins = maximized - ? MARGINS{ 0, 0, 0, 0 } - : MARGINS{ 1, 1, 1, 1 }; - extendFrameIntoClientArea(hwnd, &margins); - } -} - -void RefreshBorderlessWindowDwmDecorations(HWND hwnd) { - if (hwnd == nullptr) { - return; - } - - // Borderless host cannot participate in compositor-driven minimize/maximize - // transitions without Windows stretching the last presented client frame. - // Disable those transitions for this window, then refresh the shadow state. - ApplyDwmIntWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, DWMNCRP_DISABLED); - ApplyDwmBoolWindowAttribute(hwnd, DWMWA_ALLOW_NCPAINT, FALSE); - ApplyDwmBoolWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED, TRUE); - ApplyDwmBoolWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, TRUE); - ApplyDwmColorWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, RGB(26, 26, 26)); - ApplyDwmColorWindowAttribute(hwnd, DWMWA_TEXT_COLOR, RGB(235, 235, 235)); - ApplyDwmColorWindowAttribute(hwnd, DWMWA_BORDER_COLOR, RGB(26, 26, 26)); - EnableBorderlessWindowShadow(hwnd); -} - -bool HandleBorderlessWindowGetMinMaxInfo(HWND hwnd, LPARAM lParam) { - if (hwnd == nullptr || lParam == 0) { - return false; - } - - auto* minMaxInfo = reinterpret_cast(lParam); - if (minMaxInfo == nullptr) { - return false; - } - - const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); - if (monitor == nullptr) { - return false; - } - - MONITORINFO monitorInfo = {}; - monitorInfo.cbSize = sizeof(monitorInfo); - if (!GetMonitorInfoW(monitor, &monitorInfo)) { - return false; - } - - const RECT& workArea = monitorInfo.rcWork; - const RECT& monitorArea = monitorInfo.rcMonitor; - minMaxInfo->ptMaxPosition.x = workArea.left - monitorArea.left; - minMaxInfo->ptMaxPosition.y = workArea.top - monitorArea.top; - minMaxInfo->ptMaxSize.x = workArea.right - workArea.left; - minMaxInfo->ptMaxSize.y = workArea.bottom - workArea.top; - minMaxInfo->ptMaxTrackSize = minMaxInfo->ptMaxSize; - return true; -} - -LRESULT HandleBorderlessWindowNcCalcSize( - HWND hwnd, - WPARAM wParam, - LPARAM lParam, - UINT dpi) { - (void)hwnd; - (void)wParam; - (void)lParam; - (void)dpi; - return 0; -} - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/D3D12WindowInteropContext.cpp b/new_editor/app/Host/D3D12WindowInteropContext.cpp deleted file mode 100644 index ce8f1137..00000000 --- a/new_editor/app/Host/D3D12WindowInteropContext.cpp +++ /dev/null @@ -1,444 +0,0 @@ -#include "D3D12WindowInteropContext.h" - -#include -#include - -#include -#include - -namespace XCEngine::UI::Editor::Host { - -namespace { - -std::string HrToInteropString(const char* operation, HRESULT hr) { - char buffer[128] = {}; - sprintf_s(buffer, "%s failed with hr=0x%08X.", operation, static_cast(hr)); - return buffer; -} - -D2D1_BITMAP_PROPERTIES1 BuildD2DBitmapProperties( - DXGI_FORMAT format, - D2D1_BITMAP_OPTIONS options) { - return D2D1::BitmapProperties1( - options, - D2D1::PixelFormat(format, D2D1_ALPHA_MODE_IGNORE), - 96.0f, - 96.0f); -} - -bool IsInteropTextureHandle(const ::XCEngine::UI::UITextureHandle& texture) { - return texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView && - texture.resourceHandle != 0u; -} - -void CollectInteropTextureHandles( - const ::XCEngine::UI::UIDrawData& drawData, - std::vector<::XCEngine::UI::UITextureHandle>& outTextures) { - outTextures.clear(); - std::unordered_set seenKeys = {}; - for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) { - for (const ::XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) { - if (!IsInteropTextureHandle(command.texture) || - !seenKeys.insert(command.texture.resourceHandle).second) { - continue; - } - - outTextures.push_back(command.texture); - } - } -} - -} // namespace - -bool D3D12WindowInteropContext::Attach( - D3D12WindowRenderer& windowRenderer, - ID2D1Factory1& d2dFactory) { - if (m_windowRenderer != &windowRenderer) { - Detach(); - m_windowRenderer = &windowRenderer; - } - - m_d2dFactory = &d2dFactory; - if (!EnsureInterop()) { - return false; - } - - if (HasBackBufferTargets()) { - return true; - } - - return RebuildBackBufferTargets(); -} - -void D3D12WindowInteropContext::Detach() { - ReleaseInteropState(); - m_windowRenderer = nullptr; - m_d2dFactory = nullptr; - m_lastError.clear(); -} - -void D3D12WindowInteropContext::ReleaseBackBufferTargets() { - ClearSourceTextures(); - if (m_d2dDeviceContext != nullptr) { - m_d2dDeviceContext->SetTarget(nullptr); - } - if (m_d3d11DeviceContext != nullptr) { - m_d3d11DeviceContext->ClearState(); - } - m_backBufferTargets.clear(); - if (m_d2dDeviceContext != nullptr) { - D2D1_TAG firstTag = 0u; - D2D1_TAG secondTag = 0u; - m_d2dDeviceContext->Flush(&firstTag, &secondTag); - } - if (m_d3d11DeviceContext != nullptr) { - m_d3d11DeviceContext->Flush(); - } -} - -bool D3D12WindowInteropContext::RebuildBackBufferTargets() { - m_backBufferTargets.clear(); - if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) { - return false; - } - - const std::uint32_t backBufferCount = m_windowRenderer->GetBackBufferCount(); - m_backBufferTargets.resize(backBufferCount); - for (std::uint32_t index = 0u; index < backBufferCount; ++index) { - const ::XCEngine::RHI::D3D12Texture* backBufferTexture = - m_windowRenderer->GetBackBufferTexture(index); - if (backBufferTexture == nullptr || backBufferTexture->GetResource() == nullptr) { - m_lastError = "Failed to resolve a D3D12 swap chain back buffer."; - m_backBufferTargets.clear(); - return false; - } - - D3D11_RESOURCE_FLAGS resourceFlags = {}; - resourceFlags.BindFlags = D3D11_BIND_RENDER_TARGET; - HRESULT hr = m_d3d11On12Device->CreateWrappedResource( - backBufferTexture->GetResource(), - &resourceFlags, - D3D12_RESOURCE_STATE_RENDER_TARGET, - D3D12_RESOURCE_STATE_PRESENT, - IID_PPV_ARGS(m_backBufferTargets[index].wrappedResource.ReleaseAndGetAddressOf())); - if (FAILED(hr) || m_backBufferTargets[index].wrappedResource == nullptr) { - m_lastError = HrToInteropString("ID3D11On12Device::CreateWrappedResource(backbuffer)", hr); - m_backBufferTargets.clear(); - return false; - } - - Microsoft::WRL::ComPtr dxgiSurface = {}; - hr = m_backBufferTargets[index].wrappedResource.As(&dxgiSurface); - if (FAILED(hr) || dxgiSurface == nullptr) { - m_lastError = HrToInteropString("ID3D11Resource::QueryInterface(IDXGISurface)", hr); - m_backBufferTargets.clear(); - return false; - } - - const D2D1_BITMAP_PROPERTIES1 bitmapProperties = - BuildD2DBitmapProperties( - backBufferTexture->GetDesc().Format, - D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW); - hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface( - dxgiSurface.Get(), - &bitmapProperties, - m_backBufferTargets[index].targetBitmap.ReleaseAndGetAddressOf()); - if (FAILED(hr) || m_backBufferTargets[index].targetBitmap == nullptr) { - m_lastError = HrToInteropString( - "ID2D1DeviceContext::CreateBitmapFromDxgiSurface(backbuffer)", - hr); - m_backBufferTargets.clear(); - return false; - } - } - - m_lastError.clear(); - return true; -} - -bool D3D12WindowInteropContext::HasAttachedWindowRenderer() const { - return m_windowRenderer != nullptr && - m_d3d11On12Device != nullptr && - m_d2dDeviceContext != nullptr && - !m_backBufferTargets.empty(); -} - -bool D3D12WindowInteropContext::HasBackBufferTargets() const { - return !m_backBufferTargets.empty(); -} - -bool D3D12WindowInteropContext::PrepareSourceTextures( - const ::XCEngine::UI::UIDrawData& drawData) { - ClearSourceTextures(); - if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) { - return false; - } - - std::vector<::XCEngine::UI::UITextureHandle> textureHandles = {}; - CollectInteropTextureHandles(drawData, textureHandles); - m_activeSourceTextures.reserve(textureHandles.size()); - - for (const ::XCEngine::UI::UITextureHandle& textureHandle : textureHandles) { - auto* texture = - reinterpret_cast<::XCEngine::RHI::RHITexture*>(textureHandle.resourceHandle); - auto* nativeTexture = dynamic_cast<::XCEngine::RHI::D3D12Texture*>(texture); - if (nativeTexture == nullptr || nativeTexture->GetResource() == nullptr) { - m_lastError = "Failed to resolve a D3D12 source texture for UI composition."; - ClearSourceTextures(); - return false; - } - - D3D11_RESOURCE_FLAGS resourceFlags = {}; - resourceFlags.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - SourceTextureResource resource = {}; - resource.key = textureHandle.resourceHandle; - HRESULT hr = m_d3d11On12Device->CreateWrappedResource( - nativeTexture->GetResource(), - &resourceFlags, - D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, - D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, - IID_PPV_ARGS(resource.wrappedResource.ReleaseAndGetAddressOf())); - if (FAILED(hr) || resource.wrappedResource == nullptr) { - m_lastError = HrToInteropString("ID3D11On12Device::CreateWrappedResource(source)", hr); - ClearSourceTextures(); - return false; - } - - Microsoft::WRL::ComPtr dxgiSurface = {}; - hr = resource.wrappedResource.As(&dxgiSurface); - if (FAILED(hr) || dxgiSurface == nullptr) { - m_lastError = HrToInteropString("ID3D11Resource::QueryInterface(IDXGISurface)", hr); - ClearSourceTextures(); - return false; - } - - const D2D1_BITMAP_PROPERTIES1 bitmapProperties = - BuildD2DBitmapProperties( - nativeTexture->GetDesc().Format, - D2D1_BITMAP_OPTIONS_NONE); - hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface( - dxgiSurface.Get(), - &bitmapProperties, - resource.bitmap.ReleaseAndGetAddressOf()); - if (FAILED(hr) || resource.bitmap == nullptr) { - m_lastError = HrToInteropString("ID2D1DeviceContext::CreateBitmapFromDxgiSurface(source)", hr); - ClearSourceTextures(); - return false; - } - - m_activeBitmaps.emplace(resource.key, resource.bitmap); - m_activeSourceTextures.push_back(std::move(resource)); - } - - m_lastError.clear(); - return true; -} - -void D3D12WindowInteropContext::ClearSourceTextures() { - m_activeBitmaps.clear(); - m_activeSourceTextures.clear(); -} - -bool D3D12WindowInteropContext::ResolveInteropBitmap( - const ::XCEngine::UI::UITextureHandle& texture, - Microsoft::WRL::ComPtr& outBitmap) const { - outBitmap.Reset(); - if (!IsInteropTextureHandle(texture)) { - return false; - } - - const auto found = m_activeBitmaps.find(texture.resourceHandle); - if (found == m_activeBitmaps.end() || found->second == nullptr) { - return false; - } - - outBitmap = found->second; - return true; -} - -D3D12WindowRenderer* D3D12WindowInteropContext::GetWindowRenderer() const { - return m_windowRenderer; -} - -ID3D11On12Device* D3D12WindowInteropContext::GetD3D11On12Device() const { - return m_d3d11On12Device.Get(); -} - -ID3D11DeviceContext* D3D12WindowInteropContext::GetD3D11DeviceContext() const { - return m_d3d11DeviceContext.Get(); -} - -ID2D1DeviceContext* D3D12WindowInteropContext::GetD2DDeviceContext() const { - return m_d2dDeviceContext.Get(); -} - -ID2D1SolidColorBrush* D3D12WindowInteropContext::GetInteropBrush() const { - return m_interopBrush.Get(); -} - -void D3D12WindowInteropContext::BuildAcquiredResources( - std::uint32_t backBufferIndex, - std::vector& outResources) const { - outResources.clear(); - - ID3D11Resource* backBufferResource = GetWrappedBackBufferResource(backBufferIndex); - if (backBufferResource != nullptr) { - outResources.push_back(backBufferResource); - } - - for (const SourceTextureResource& resource : m_activeSourceTextures) { - if (resource.wrappedResource != nullptr) { - outResources.push_back(resource.wrappedResource.Get()); - } - } -} - -ID3D11Resource* D3D12WindowInteropContext::GetWrappedBackBufferResource(std::uint32_t index) const { - return index < m_backBufferTargets.size() ? m_backBufferTargets[index].wrappedResource.Get() : nullptr; -} - -ID2D1Bitmap1* D3D12WindowInteropContext::GetBackBufferTargetBitmap(std::uint32_t index) const { - return index < m_backBufferTargets.size() ? m_backBufferTargets[index].targetBitmap.Get() : nullptr; -} - -std::uint32_t D3D12WindowInteropContext::GetCurrentBackBufferIndex() const { - return m_windowRenderer != nullptr && m_windowRenderer->GetSwapChain() != nullptr - ? m_windowRenderer->GetSwapChain()->GetCurrentBackBufferIndex() - : 0u; -} - -const std::string& D3D12WindowInteropContext::GetLastError() const { - return m_lastError; -} - -bool D3D12WindowInteropContext::EnsureInterop() { - if (m_windowRenderer == nullptr) { - m_lastError = "EnsureInterop requires an attached D3D12 window renderer."; - return false; - } - if (m_d2dFactory == nullptr) { - m_lastError = "EnsureInterop requires an initialized D2D factory."; - return false; - } - if (m_d3d11On12Device != nullptr && - m_d2dDeviceContext != nullptr && - m_interopBrush != nullptr) { - return true; - } - - ReleaseInteropState(); - - ID3D12Device* d3d12Device = m_windowRenderer->GetDevice(); - ID3D12CommandQueue* d3d12CommandQueue = m_windowRenderer->GetCommandQueue(); - if (d3d12Device == nullptr || d3d12CommandQueue == nullptr) { - m_lastError = "The attached D3D12 window renderer does not expose a native device/queue."; - return false; - } - - const std::array featureLevels = { - D3D_FEATURE_LEVEL_12_1, - D3D_FEATURE_LEVEL_12_0, - D3D_FEATURE_LEVEL_11_1, - D3D_FEATURE_LEVEL_11_0 - }; - const std::array commandQueues = { - reinterpret_cast(d3d12CommandQueue) - }; - - UINT createFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; -#ifdef _DEBUG - createFlags |= D3D11_CREATE_DEVICE_DEBUG; -#endif - - D3D_FEATURE_LEVEL actualFeatureLevel = D3D_FEATURE_LEVEL_11_0; - HRESULT hr = D3D11On12CreateDevice( - d3d12Device, - createFlags, - featureLevels.data(), - static_cast(featureLevels.size()), - commandQueues.data(), - static_cast(commandQueues.size()), - 0u, - m_d3d11Device.ReleaseAndGetAddressOf(), - m_d3d11DeviceContext.ReleaseAndGetAddressOf(), - &actualFeatureLevel); -#ifdef _DEBUG - if (FAILED(hr)) { - createFlags &= ~D3D11_CREATE_DEVICE_DEBUG; - hr = D3D11On12CreateDevice( - d3d12Device, - createFlags, - featureLevels.data(), - static_cast(featureLevels.size()), - commandQueues.data(), - static_cast(commandQueues.size()), - 0u, - m_d3d11Device.ReleaseAndGetAddressOf(), - m_d3d11DeviceContext.ReleaseAndGetAddressOf(), - &actualFeatureLevel); - } -#endif - if (FAILED(hr) || m_d3d11Device == nullptr || m_d3d11DeviceContext == nullptr) { - m_lastError = HrToInteropString("D3D11On12CreateDevice", hr); - ReleaseInteropState(); - return false; - } - - hr = m_d3d11Device.As(&m_d3d11On12Device); - if (FAILED(hr) || m_d3d11On12Device == nullptr) { - m_lastError = HrToInteropString("ID3D11Device::QueryInterface(ID3D11On12Device)", hr); - ReleaseInteropState(); - return false; - } - - Microsoft::WRL::ComPtr dxgiDevice = {}; - hr = m_d3d11Device.As(&dxgiDevice); - if (FAILED(hr) || dxgiDevice == nullptr) { - m_lastError = HrToInteropString("ID3D11Device::QueryInterface(IDXGIDevice)", hr); - ReleaseInteropState(); - return false; - } - - hr = m_d2dFactory->CreateDevice(dxgiDevice.Get(), m_d2dDevice.ReleaseAndGetAddressOf()); - if (FAILED(hr) || m_d2dDevice == nullptr) { - m_lastError = HrToInteropString("ID2D1Factory1::CreateDevice", hr); - ReleaseInteropState(); - return false; - } - - hr = m_d2dDevice->CreateDeviceContext( - D2D1_DEVICE_CONTEXT_OPTIONS_NONE, - m_d2dDeviceContext.ReleaseAndGetAddressOf()); - if (FAILED(hr) || m_d2dDeviceContext == nullptr) { - m_lastError = HrToInteropString("ID2D1Device::CreateDeviceContext", hr); - ReleaseInteropState(); - return false; - } - - hr = m_d2dDeviceContext->CreateSolidColorBrush( - D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), - m_interopBrush.ReleaseAndGetAddressOf()); - if (FAILED(hr) || m_interopBrush == nullptr) { - m_lastError = HrToInteropString("ID2D1DeviceContext::CreateSolidColorBrush", hr); - ReleaseInteropState(); - return false; - } - - m_d2dDeviceContext->SetDpi(96.0f, 96.0f); - m_d2dDeviceContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); - m_lastError.clear(); - return true; -} - -void D3D12WindowInteropContext::ReleaseInteropState() { - ReleaseBackBufferTargets(); - m_interopBrush.Reset(); - m_d2dDeviceContext.Reset(); - m_d2dDevice.Reset(); - m_d3d11On12Device.Reset(); - m_d3d11DeviceContext.Reset(); - m_d3d11Device.Reset(); -} - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/D3D12WindowSwapChainPresenter.cpp b/new_editor/app/Host/D3D12WindowSwapChainPresenter.cpp deleted file mode 100644 index dac7bb25..00000000 --- a/new_editor/app/Host/D3D12WindowSwapChainPresenter.cpp +++ /dev/null @@ -1,388 +0,0 @@ -#include "D3D12WindowSwapChainPresenter.h" - -#include - -namespace XCEngine::UI::Editor::Host { - -using ::XCEngine::RHI::D3D12SwapChain; -using ::XCEngine::RHI::D3D12Texture; -using ::XCEngine::RHI::Format; -using ::XCEngine::RHI::ResourceStates; -using ::XCEngine::RHI::ResourceViewDesc; -using ::XCEngine::RHI::ResourceViewDimension; -using ::XCEngine::RHI::RHISwapChain; -using ::XCEngine::RHI::SwapChainDesc; - -bool D3D12WindowSwapChainPresenter::Initialize( - D3D12HostDevice& hostDevice, - HWND hwnd, - int width, - int height) { - Shutdown(); - - if (hwnd == nullptr || width <= 0 || height <= 0) { - m_lastError = "Initialize rejected an invalid hwnd or size."; - return false; - } - - if (hostDevice.GetRHIDevice() == nullptr || hostDevice.GetRHICommandQueue() == nullptr) { - m_lastError = "Initialize requires an initialized host D3D12 device."; - return false; - } - - m_hwnd = hwnd; - m_width = width; - m_height = height; - m_hostDevice = &hostDevice; - - if (!CreateSwapChain(width, height)) { - Shutdown(); - return false; - } - - m_lastError.clear(); - return true; -} - -bool D3D12WindowSwapChainPresenter::CreateSwapChain(int width, int height) { - if (m_hostDevice == nullptr || m_hostDevice->GetRHIDevice() == nullptr || m_hostDevice->GetRHICommandQueue() == nullptr) { - m_lastError = "CreateSwapChain requires an initialized host D3D12 device."; - return false; - } - - SwapChainDesc swapChainDesc = {}; - swapChainDesc.windowHandle = m_hwnd; - swapChainDesc.width = static_cast(width); - swapChainDesc.height = static_cast(height); - swapChainDesc.bufferCount = kSwapChainBufferCount; - m_swapChain = m_hostDevice->GetRHIDevice()->CreateSwapChain( - swapChainDesc, - m_hostDevice->GetRHICommandQueue()); - if (m_swapChain == nullptr || GetD3D12SwapChain() == nullptr) { - m_lastError = "Failed to create the D3D12 swap chain."; - return false; - } - - ConfigureFrameLatency(); - - if (!RecreateBackBufferViews()) { - m_lastError = "Failed to create swap chain back buffer views."; - return false; - } - - m_width = width; - m_height = height; - return true; -} - -void D3D12WindowSwapChainPresenter::ConfigureFrameLatency() { - D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); - if (d3d12SwapChain == nullptr) { - return; - } - - auto* nativeSwapChain = - static_cast(d3d12SwapChain->GetNativeHandle()); - if (nativeSwapChain == nullptr) { - return; - } - - nativeSwapChain->SetMaximumFrameLatency(1u); -} - -void D3D12WindowSwapChainPresenter::DestroySwapChain() { - ReleaseBackBufferViews(); - - if (m_swapChain != nullptr) { - m_swapChain->Shutdown(); - delete m_swapChain; - m_swapChain = nullptr; - } -} - -bool D3D12WindowSwapChainPresenter::RecreateSwapChain(int width, int height) { - DestroySwapChain(); - m_hostDevice->ResetFrameTracking(); - return CreateSwapChain(width, height); -} - -void D3D12WindowSwapChainPresenter::Shutdown() { - if (m_hostDevice != nullptr) { - m_hostDevice->WaitForGpuIdle(); - } - - DestroySwapChain(); - - m_hwnd = nullptr; - m_width = 0; - m_height = 0; - m_hostDevice = nullptr; - m_lastError.clear(); -} - -bool D3D12WindowSwapChainPresenter::Resize(int width, int height) { - if (width <= 0 || height <= 0 || m_swapChain == nullptr || m_hostDevice == nullptr) { - return false; - } - - if (m_width == width && m_height == height) { - m_lastError.clear(); - return true; - } - - m_hostDevice->WaitForGpuIdle(); - ReleaseBackBufferCommandReferences(); - ReleaseBackBufferViews(); - - D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); - if (d3d12SwapChain == nullptr) { - m_lastError = "Resize could not resolve the native D3D12 swap chain."; - return false; - } - - d3d12SwapChain->Resize( - static_cast(width), - static_cast(height)); - const HRESULT resizeHr = d3d12SwapChain->GetLastResizeResult(); - if (FAILED(resizeHr)) { - if (RecreateSwapChain(width, height)) { - m_lastError.clear(); - return true; - } - - std::ostringstream error = {}; - error << "ResizeBuffers failed. requested=" - << width - << 'x' - << height - << " hr=0x" - << std::uppercase - << std::hex - << static_cast(resizeHr); - m_lastError = error.str(); - return false; - } - - const D3D12Texture* backBufferTexture = GetBackBufferTexture(0u); - if (backBufferTexture == nullptr) { - if (RecreateSwapChain(width, height)) { - m_lastError.clear(); - return true; - } - - m_lastError = "Resize failed to restore swap chain back buffers after ResizeBuffers."; - return false; - } - - m_width = static_cast(backBufferTexture->GetWidth()); - m_height = static_cast(backBufferTexture->GetHeight()); - m_hostDevice->ResetFrameTracking(); - if (!RecreateBackBufferViews()) { - if (RecreateSwapChain(width, height)) { - m_lastError.clear(); - return true; - } - - m_lastError = "Resize failed to recreate swap chain back buffer views."; - return false; - } - - if (m_width != width || m_height != height) { - if (RecreateSwapChain(width, height)) { - m_lastError.clear(); - return true; - } - - std::ostringstream error = {}; - error << "Resize ended with an unexpected swap chain size. requested=" - << width - << 'x' - << height - << " actual=" - << m_width - << 'x' - << m_height; - m_lastError = error.str(); - return false; - } - - m_lastError.clear(); - return true; -} - -bool D3D12WindowSwapChainPresenter::PreparePresentSurface( - const ::XCEngine::Rendering::RenderContext& renderContext) { - if (!renderContext.IsValid() || renderContext.commandList == nullptr) { - m_lastError = "PreparePresentSurface requires a valid render context."; - return false; - } - - if (m_swapChain == nullptr) { - m_lastError = "PreparePresentSurface requires an initialized swap chain."; - return false; - } - - const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); - if (backBufferIndex >= m_backBufferViews.size() || - m_backBufferViews[backBufferIndex] == nullptr) { - std::ostringstream error = {}; - error << "PreparePresentSurface could not find the current swap chain RTV. index=" - << backBufferIndex - << " rtvCount=" - << m_backBufferViews.size(); - m_lastError = error.str(); - return false; - } - - renderContext.commandList->TransitionBarrier( - m_backBufferViews[backBufferIndex], - ::XCEngine::RHI::ResourceStates::Present, - ::XCEngine::RHI::ResourceStates::RenderTarget); - m_lastError.clear(); - return true; -} - -bool D3D12WindowSwapChainPresenter::PresentFrame() { - if (m_swapChain == nullptr) { - m_lastError = "PresentFrame requires an initialized swap chain."; - return false; - } - - // Editor shell presentation prioritizes interaction latency over display sync. - // Blocking on every vblank makes host-window resize feel sticky even when the - // UI tree itself is cheap to rebuild. - m_swapChain->Present(0, 0); - m_lastError.clear(); - return true; -} - -const std::string& D3D12WindowSwapChainPresenter::GetLastError() const { - return m_lastError; -} - -RHISwapChain* D3D12WindowSwapChainPresenter::GetSwapChain() const { - return m_swapChain; -} - -const ::XCEngine::Rendering::RenderSurface* -D3D12WindowSwapChainPresenter::GetCurrentRenderSurface() const { - if (m_swapChain == nullptr) { - return nullptr; - } - - const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); - if (backBufferIndex >= m_backBufferSurfaces.size()) { - return nullptr; - } - - return &m_backBufferSurfaces[backBufferIndex]; -} - -const D3D12Texture* D3D12WindowSwapChainPresenter::GetCurrentBackBufferTexture() const { - if (m_swapChain == nullptr) { - return nullptr; - } - - return GetBackBufferTexture(m_swapChain->GetCurrentBackBufferIndex()); -} - -const D3D12Texture* D3D12WindowSwapChainPresenter::GetBackBufferTexture(std::uint32_t index) const { - const D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); - if (d3d12SwapChain == nullptr) { - return nullptr; - } - - return d3d12SwapChain->TryGetBackBuffer(index); -} - -std::uint32_t D3D12WindowSwapChainPresenter::GetBackBufferCount() const { - return kSwapChainBufferCount; -} - -std::uint32_t D3D12WindowSwapChainPresenter::GetCurrentBackBufferIndex() const { - return m_swapChain != nullptr ? m_swapChain->GetCurrentBackBufferIndex() : 0u; -} - -D3D12SwapChain* D3D12WindowSwapChainPresenter::GetD3D12SwapChain() const { - return m_swapChain != nullptr ? static_cast(m_swapChain) : nullptr; -} - -void D3D12WindowSwapChainPresenter::ReleaseBackBufferCommandReferences() { - if (m_hostDevice == nullptr) { - return; - } - - for (std::uint32_t frameIndex = 0u; - frameIndex < D3D12HostDevice::kFrameContextCount; - ++frameIndex) { - auto* commandList = m_hostDevice->GetCommandList(frameIndex); - if (commandList == nullptr) { - continue; - } - - commandList->Reset(); - commandList->Close(); - } -} - -void D3D12WindowSwapChainPresenter::ReleaseBackBufferViews() { - for (auto* view : m_backBufferViews) { - if (view != nullptr) { - view->Shutdown(); - delete view; - } - } - - m_backBufferViews.clear(); - m_backBufferSurfaces.clear(); -} - -bool D3D12WindowSwapChainPresenter::RecreateBackBufferViews() { - D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); - if (m_hostDevice == nullptr || m_hostDevice->GetRHIDevice() == nullptr || d3d12SwapChain == nullptr) { - return false; - } - - m_backBufferViews.resize(kSwapChainBufferCount, nullptr); - m_backBufferSurfaces.resize(kSwapChainBufferCount); - - ResourceViewDesc viewDesc = {}; - viewDesc.format = static_cast(Format::R8G8B8A8_UNorm); - viewDesc.dimension = ResourceViewDimension::Texture2D; - - for (std::uint32_t backBufferIndex = 0u; - backBufferIndex < kSwapChainBufferCount; - ++backBufferIndex) { - D3D12Texture* backBufferTexture = d3d12SwapChain->TryGetBackBuffer(backBufferIndex); - if (backBufferTexture == nullptr) { - ReleaseBackBufferViews(); - m_lastError = "RecreateBackBufferViews could not resolve swap chain back buffer " + - std::to_string(backBufferIndex) + "."; - return false; - } - - m_backBufferViews[backBufferIndex] = m_hostDevice->GetRHIDevice()->CreateRenderTargetView( - backBufferTexture, - viewDesc); - if (m_backBufferViews[backBufferIndex] == nullptr) { - ReleaseBackBufferViews(); - m_lastError = "RecreateBackBufferViews failed to create RTV for swap chain back buffer " + - std::to_string(backBufferIndex) + "."; - return false; - } - - ::XCEngine::Rendering::RenderSurface& surface = m_backBufferSurfaces[backBufferIndex]; - surface = ::XCEngine::Rendering::RenderSurface( - static_cast(m_width), - static_cast(m_height)); - surface.SetColorAttachment(m_backBufferViews[backBufferIndex]); - surface.SetAutoTransitionEnabled(false); - surface.SetColorStateBefore(ResourceStates::RenderTarget); - surface.SetColorStateAfter(ResourceStates::RenderTarget); - } - - m_lastError.clear(); - return true; -} - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/NativeRenderer.cpp b/new_editor/app/Host/NativeRenderer.cpp deleted file mode 100644 index 3d7a3c57..00000000 --- a/new_editor/app/Host/NativeRenderer.cpp +++ /dev/null @@ -1,1241 +0,0 @@ -#include "NativeRenderer.h" - -#include -#include -#include -#include -#include -#include - -namespace XCEngine::UI::Editor::Host { - -namespace { - -constexpr float kBaseDpi = 96.0f; -constexpr float kDefaultFontSize = 16.0f; - -std::string HrToString(const char* operation, HRESULT hr) { - char buffer[128] = {}; - sprintf_s(buffer, "%s failed with hr=0x%08X.", operation, static_cast(hr)); - return buffer; -} - -float ClampDpiScale(float dpiScale) { - return dpiScale > 0.0f ? dpiScale : 1.0f; -} - -float ResolveFontSize(float fontSize) { - return fontSize > 0.0f ? fontSize : kDefaultFontSize; -} - -float SnapToPixel(float value, float dpiScale) { - const float scale = ClampDpiScale(dpiScale); - return std::round(value * scale); -} - -D2D1_RECT_F ToD2DRect(const ::XCEngine::UI::UIRect& rect, float dpiScale) { - const float left = SnapToPixel(rect.x, dpiScale); - const float top = SnapToPixel(rect.y, dpiScale); - const float right = SnapToPixel(rect.x + rect.width, dpiScale); - const float bottom = SnapToPixel(rect.y + rect.height, dpiScale); - return D2D1::RectF(left, top, right, bottom); -} - -D2D1_POINT_2F ToD2DPoint( - const ::XCEngine::UI::UIPoint& point, - float dpiScale, - float pixelOffset = 0.0f) { - return D2D1::Point2F( - SnapToPixel(point.x, dpiScale) + pixelOffset, - SnapToPixel(point.y, dpiScale) + pixelOffset); -} - -float ResolveStrokePixelOffset(float thickness) { - const float roundedThickness = std::round(thickness); - return std::fmod(roundedThickness, 2.0f) == 1.0f ? 0.5f : 0.0f; -} - -D2D1_BITMAP_PROPERTIES1 BuildD2DBitmapProperties( - DXGI_FORMAT format, - D2D1_BITMAP_OPTIONS options, - D2D1_ALPHA_MODE alphaMode = D2D1_ALPHA_MODE_IGNORE) { - return D2D1::BitmapProperties1( - options, - D2D1::PixelFormat(format, alphaMode), - kBaseDpi, - kBaseDpi); -} - -bool IsInteropTextureHandle(const ::XCEngine::UI::UITextureHandle& texture) { - return texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView && - texture.resourceHandle != 0u; -} - -bool CollectInteropTextureHandles( - const ::XCEngine::UI::UIDrawData& drawData, - std::vector<::XCEngine::UI::UITextureHandle>& outTextures) { - outTextures.clear(); - std::unordered_set seenKeys = {}; - for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) { - for (const ::XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) { - if (!IsInteropTextureHandle(command.texture) || - !seenKeys.insert(command.texture.resourceHandle).second) { - continue; - } - - outTextures.push_back(command.texture); - } - } - - return true; -} - -} // namespace - -bool NativeRenderer::Initialize(HWND hwnd) { - Shutdown(); - - if (hwnd == nullptr) { - m_lastRenderError = "Initialize rejected a null hwnd."; - return false; - } - - m_hwnd = hwnd; - D2D1_FACTORY_OPTIONS factoryOptions = {}; -#ifdef _DEBUG - factoryOptions.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; -#endif - HRESULT hr = D2D1CreateFactory( - D2D1_FACTORY_TYPE_SINGLE_THREADED, - __uuidof(ID2D1Factory1), - &factoryOptions, - reinterpret_cast(m_d2dFactory.ReleaseAndGetAddressOf())); - if (FAILED(hr)) { - m_lastRenderError = HrToString("D2D1CreateFactory", hr); - Shutdown(); - return false; - } - - hr = DWriteCreateFactory( - DWRITE_FACTORY_TYPE_SHARED, - __uuidof(IDWriteFactory), - reinterpret_cast(m_dwriteFactory.ReleaseAndGetAddressOf())); - if (FAILED(hr)) { - m_lastRenderError = HrToString("DWriteCreateFactory", hr); - Shutdown(); - return false; - } - - m_lastRenderError.clear(); - return true; -} - -void NativeRenderer::Shutdown() { - DetachWindowRenderer(); - while (!m_liveTextures.empty()) { - auto it = m_liveTextures.begin(); - delete *it; - m_liveTextures.erase(it); - } - m_textFormats.clear(); - m_solidBrush.Reset(); - m_renderTarget.Reset(); - m_wicFactory.Reset(); - m_dwriteFactory.Reset(); - m_d2dFactory.Reset(); - if (m_wicComInitialized) { - CoUninitialize(); - m_wicComInitialized = false; - } - m_hwnd = nullptr; -} - -void NativeRenderer::SetDpiScale(float dpiScale) { - m_dpiScale = ClampDpiScale(dpiScale); - if (m_renderTarget) { - m_renderTarget->SetDpi(kBaseDpi, kBaseDpi); - } -} - -float NativeRenderer::GetDpiScale() const { - return m_dpiScale; -} - -void NativeRenderer::Resize(UINT width, UINT height) { - if (!m_renderTarget || width == 0 || height == 0) { - return; - } - - const HRESULT hr = m_renderTarget->Resize(D2D1::SizeU(width, height)); - if (hr == D2DERR_RECREATE_TARGET) { - DiscardRenderTarget(); - } -} - -bool NativeRenderer::AttachWindowRenderer(D3D12WindowRenderer& windowRenderer) { - if (m_windowRenderer != &windowRenderer) { - ReleaseWindowRendererInterop(); - m_windowRenderer = &windowRenderer; - } - - if (!EnsureWindowRendererInterop()) { - return false; - } - - // Keep a single real window presentation path. The hwnd render target is - // fallback-only and should not stay alive while D3D11On12 interop is healthy. - DiscardRenderTarget(); - - if (m_windowInterop.HasBackBufferTargets()) { - return true; - } - - return m_windowInterop.RebuildBackBufferTargets(); -} - -void NativeRenderer::DetachWindowRenderer() { - ReleaseWindowRendererInterop(); - m_windowRenderer = nullptr; -} - -void NativeRenderer::ReleaseWindowRendererBackBufferTargets() { - m_windowInterop.ReleaseBackBufferTargets(); -} - -bool NativeRenderer::RebuildWindowRendererBackBufferTargets() { - if (!EnsureWindowRendererInterop()) { - return false; - } - - DiscardRenderTarget(); - ReleaseWindowRendererBackBufferTargets(); - return m_windowInterop.RebuildBackBufferTargets(); -} - -bool NativeRenderer::HasAttachedWindowRenderer() const { - return m_windowInterop.HasAttachedWindowRenderer(); -} - -bool NativeRenderer::Render(const ::XCEngine::UI::UIDrawData& drawData) { - if (!EnsureRenderTarget()) { - if (m_lastRenderError.empty()) { - m_lastRenderError = "EnsureRenderTarget failed."; - } - return false; - } - - const bool rendered = RenderToTarget(*m_renderTarget.Get(), *m_solidBrush.Get(), drawData); - const HRESULT hr = m_renderTarget->EndDraw(); - if (hr == D2DERR_RECREATE_TARGET) { - m_lastRenderError = HrToString("ID2D1HwndRenderTarget::EndDraw", hr); - DiscardRenderTarget(); - return false; - } - - if (!rendered || FAILED(hr)) { - m_lastRenderError = HrToString("ID2D1HwndRenderTarget::EndDraw", hr); - return false; - } - - m_lastRenderError.clear(); - return true; -} - -bool NativeRenderer::RenderToWindowRenderer(const ::XCEngine::UI::UIDrawData& drawData) { - if (!EnsureWindowRendererInterop()) { - if (m_lastRenderError.empty()) { - m_lastRenderError = "Window renderer interop is not available."; - } - return false; - } - - if (!m_windowInterop.HasBackBufferTargets() && - !m_windowInterop.RebuildBackBufferTargets()) { - if (m_lastRenderError.empty()) { - m_lastRenderError = "Window renderer back buffer interop targets are unavailable."; - } - return false; - } - - ID3D11On12Device* d3d11On12Device = m_windowInterop.GetD3D11On12Device(); - ID3D11DeviceContext* d3d11DeviceContext = m_windowInterop.GetD3D11DeviceContext(); - ID2D1DeviceContext* d2dDeviceContext = m_windowInterop.GetD2DDeviceContext(); - ID2D1SolidColorBrush* interopBrush = m_windowInterop.GetInteropBrush(); - if (d3d11On12Device == nullptr || - d3d11DeviceContext == nullptr || - d2dDeviceContext == nullptr || - interopBrush == nullptr) { - m_lastRenderError = "Window renderer interop resources are incomplete."; - return false; - } - - const std::uint32_t backBufferIndex = m_windowInterop.GetCurrentBackBufferIndex(); - if (m_windowInterop.GetWrappedBackBufferResource(backBufferIndex) == nullptr || - m_windowInterop.GetBackBufferTargetBitmap(backBufferIndex) == nullptr) { - m_lastRenderError = "Back buffer interop target index is out of range."; - return false; - } - - if (!m_windowRenderer->PreparePresentSurface()) { - m_lastRenderError = "Failed to prepare the D3D12 present surface: " + - m_windowRenderer->GetLastError(); - return false; - } - - if (!m_windowRenderer->SubmitFrame(false)) { - m_lastRenderError = "Failed to submit the D3D12 frame before UI composition."; - return false; - } - - if (!m_windowInterop.PrepareSourceTextures(drawData)) { - ID3D11Resource* backBufferResource = - m_windowInterop.GetWrappedBackBufferResource(backBufferIndex); - if (backBufferResource != nullptr) { - d3d11On12Device->AcquireWrappedResources(&backBufferResource, 1u); - d3d11On12Device->ReleaseWrappedResources(&backBufferResource, 1u); - } - d3d11DeviceContext->Flush(); - m_windowInterop.ClearSourceTextures(); - const bool signaled = m_windowRenderer->SignalFrameCompletion(); - ReleaseWindowRendererInterop(); - if (!signaled) { - m_lastRenderError = - "Failed to signal D3D12 frame completion after interop preparation failed."; - } - return false; - } - - std::vector acquiredResources = {}; - m_windowInterop.BuildAcquiredResources(backBufferIndex, acquiredResources); - if (acquiredResources.empty()) { - m_lastRenderError = "No wrapped interop resources were prepared for UI composition."; - return false; - } - - d3d11On12Device->AcquireWrappedResources( - acquiredResources.data(), - static_cast(acquiredResources.size())); - - d2dDeviceContext->SetTarget(m_windowInterop.GetBackBufferTargetBitmap(backBufferIndex)); - const bool rendered = RenderToTarget(*d2dDeviceContext, *interopBrush, drawData); - const HRESULT hr = d2dDeviceContext->EndDraw(); - - d3d11On12Device->ReleaseWrappedResources( - acquiredResources.data(), - static_cast(acquiredResources.size())); - d3d11DeviceContext->Flush(); - d2dDeviceContext->SetTarget(nullptr); - m_windowInterop.ClearSourceTextures(); - - if (!rendered || FAILED(hr)) { - m_lastRenderError = FAILED(hr) - ? HrToString("ID2D1DeviceContext::EndDraw", hr) - : "RenderToTarget failed during D3D11On12 composition."; - const bool signaled = m_windowRenderer->SignalFrameCompletion(); - ReleaseWindowRendererInterop(); - if (!signaled) { - m_lastRenderError = - "Failed to signal D3D12 frame completion after UI composition failed."; - } - return false; - } - - if (!m_windowRenderer->SignalFrameCompletion()) { - m_lastRenderError = "Failed to signal D3D12 frame completion after UI composition."; - ReleaseWindowRendererInterop(); - return false; - } - if (!m_windowRenderer->PresentFrame()) { - m_lastRenderError = "Failed to present the D3D12 swap chain."; - ReleaseWindowRendererInterop(); - return false; - } - - m_lastRenderError.clear(); - return true; -} - -const std::string& NativeRenderer::GetLastRenderError() const { - return m_lastRenderError; -} - -bool NativeRenderer::LoadTextureFromFile( - const std::filesystem::path& path, - ::XCEngine::UI::UITextureHandle& outTexture, - std::string& outError) { - outError.clear(); - ReleaseTexture(outTexture); - - auto texture = std::make_unique(); - if (!DecodeTextureFile(path, *texture, outError)) { - outTexture = {}; - return false; - } - - outTexture.nativeHandle = reinterpret_cast(texture.get()); - outTexture.width = texture->width; - outTexture.height = texture->height; - outTexture.kind = ::XCEngine::UI::UITextureHandleKind::DescriptorHandle; - m_liveTextures.insert(texture.get()); - texture.release(); - return true; -} - -bool NativeRenderer::LoadTextureFromMemory( - const std::uint8_t* data, - std::size_t size, - ::XCEngine::UI::UITextureHandle& outTexture, - std::string& outError) { - outError.clear(); - ReleaseTexture(outTexture); - - auto texture = std::make_unique(); - if (!DecodeTextureMemory(data, size, *texture, outError)) { - outTexture = {}; - return false; - } - - outTexture.nativeHandle = reinterpret_cast(texture.get()); - outTexture.width = texture->width; - outTexture.height = texture->height; - outTexture.kind = ::XCEngine::UI::UITextureHandleKind::DescriptorHandle; - m_liveTextures.insert(texture.get()); - texture.release(); - return true; -} - -void NativeRenderer::ReleaseTexture(::XCEngine::UI::UITextureHandle& texture) { - if (!texture.IsValid()) { - texture = {}; - return; - } - - auto* resource = reinterpret_cast(texture.nativeHandle); - if (resource != nullptr) { - const auto found = m_liveTextures.find(resource); - if (found != m_liveTextures.end()) { - m_liveTextures.erase(found); - delete resource; - } - } - - texture = {}; -} - -float NativeRenderer::MeasureTextWidth( - const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const { - if (!m_dwriteFactory || request.text.empty()) { - return 0.0f; - } - - const std::wstring text = Utf8ToWide(request.text); - if (text.empty()) { - return 0.0f; - } - - const float dpiScale = ClampDpiScale(m_dpiScale); - const float scaledFontSize = ResolveFontSize(request.fontSize) * dpiScale; - IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize); - if (textFormat == nullptr) { - return 0.0f; - } - - Microsoft::WRL::ComPtr textLayout; - HRESULT hr = m_dwriteFactory->CreateTextLayout( - text.c_str(), - static_cast(text.size()), - textFormat, - 4096.0f, - scaledFontSize * 2.0f, - textLayout.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !textLayout) { - return 0.0f; - } - - DWRITE_TEXT_METRICS textMetrics = {}; - hr = textLayout->GetMetrics(&textMetrics); - if (FAILED(hr)) { - return 0.0f; - } - - DWRITE_OVERHANG_METRICS overhangMetrics = {}; - float width = textMetrics.widthIncludingTrailingWhitespace; - if (SUCCEEDED(textLayout->GetOverhangMetrics(&overhangMetrics))) { - width += (std::max)(overhangMetrics.left, 0.0f); - width += (std::max)(overhangMetrics.right, 0.0f); - } - - return std::ceil(width) / dpiScale; -} - -bool NativeRenderer::CaptureToPng( - const ::XCEngine::UI::UIDrawData& drawData, - UINT width, - UINT height, - const std::filesystem::path& outputPath, - std::string& outError) { - outError.clear(); - if (width == 0 || height == 0) { - outError = "CaptureToPng rejected an empty render size."; - return false; - } - - if (!m_d2dFactory || !m_dwriteFactory) { - outError = "CaptureToPng requires an initialized NativeRenderer."; - return false; - } - - if (!EnsureWicFactory(outError)) { - return false; - } - - std::error_code errorCode = {}; - std::filesystem::create_directories(outputPath.parent_path(), errorCode); - if (errorCode) { - outError = "Failed to create screenshot directory: " + outputPath.parent_path().string(); - return false; - } - - Microsoft::WRL::ComPtr bitmap; - HRESULT hr = m_wicFactory->CreateBitmap( - width, - height, - GUID_WICPixelFormat32bppPBGRA, - WICBitmapCacheOnLoad, - bitmap.ReleaseAndGetAddressOf()); - if (FAILED(hr)) { - outError = HrToString("IWICImagingFactory::CreateBitmap", hr); - return false; - } - - const D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties = D2D1::RenderTargetProperties( - D2D1_RENDER_TARGET_TYPE_DEFAULT, - D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), - kBaseDpi, - kBaseDpi); - - Microsoft::WRL::ComPtr offscreenRenderTarget; - hr = m_d2dFactory->CreateWicBitmapRenderTarget( - bitmap.Get(), - renderTargetProperties, - offscreenRenderTarget.ReleaseAndGetAddressOf()); - if (FAILED(hr)) { - outError = HrToString("ID2D1Factory::CreateWicBitmapRenderTarget", hr); - return false; - } - - Microsoft::WRL::ComPtr offscreenBrush; - hr = offscreenRenderTarget->CreateSolidColorBrush( - D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), - offscreenBrush.ReleaseAndGetAddressOf()); - if (FAILED(hr)) { - outError = HrToString("ID2D1RenderTarget::CreateSolidColorBrush", hr); - return false; - } - - const bool rendered = RenderToTarget(*offscreenRenderTarget.Get(), *offscreenBrush.Get(), drawData); - hr = offscreenRenderTarget->EndDraw(); - if (!rendered || FAILED(hr)) { - outError = HrToString("ID2D1RenderTarget::EndDraw", hr); - return false; - } - - const std::wstring wideOutputPath = outputPath.wstring(); - Microsoft::WRL::ComPtr stream; - hr = m_wicFactory->CreateStream(stream.ReleaseAndGetAddressOf()); - if (FAILED(hr)) { - outError = HrToString("IWICImagingFactory::CreateStream", hr); - return false; - } - - hr = stream->InitializeFromFilename(wideOutputPath.c_str(), GENERIC_WRITE); - if (FAILED(hr)) { - outError = HrToString("IWICStream::InitializeFromFilename", hr); - return false; - } - - Microsoft::WRL::ComPtr encoder; - hr = m_wicFactory->CreateEncoder(GUID_ContainerFormatPng, nullptr, encoder.ReleaseAndGetAddressOf()); - if (FAILED(hr)) { - outError = HrToString("IWICImagingFactory::CreateEncoder", hr); - return false; - } - - hr = encoder->Initialize(stream.Get(), WICBitmapEncoderNoCache); - if (FAILED(hr)) { - outError = HrToString("IWICBitmapEncoder::Initialize", hr); - return false; - } - - Microsoft::WRL::ComPtr frame; - Microsoft::WRL::ComPtr propertyBag; - hr = encoder->CreateNewFrame(frame.ReleaseAndGetAddressOf(), propertyBag.ReleaseAndGetAddressOf()); - if (FAILED(hr)) { - outError = HrToString("IWICBitmapEncoder::CreateNewFrame", hr); - return false; - } - - hr = frame->Initialize(propertyBag.Get()); - if (FAILED(hr)) { - outError = HrToString("IWICBitmapFrameEncode::Initialize", hr); - return false; - } - - hr = frame->SetSize(width, height); - if (FAILED(hr)) { - outError = HrToString("IWICBitmapFrameEncode::SetSize", hr); - return false; - } - - WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppPBGRA; - hr = frame->SetPixelFormat(&pixelFormat); - if (FAILED(hr)) { - outError = HrToString("IWICBitmapFrameEncode::SetPixelFormat", hr); - return false; - } - - hr = frame->WriteSource(bitmap.Get(), nullptr); - if (FAILED(hr)) { - outError = HrToString("IWICBitmapFrameEncode::WriteSource", hr); - return false; - } - - hr = frame->Commit(); - if (FAILED(hr)) { - outError = HrToString("IWICBitmapFrameEncode::Commit", hr); - return false; - } - - hr = encoder->Commit(); - if (FAILED(hr)) { - outError = HrToString("IWICBitmapEncoder::Commit", hr); - return false; - } - - return true; -} - -bool NativeRenderer::EnsureWindowRendererInterop() { - if (m_windowRenderer == nullptr) { - m_lastRenderError = "EnsureWindowRendererInterop requires an attached D3D12 window renderer."; - return false; - } - if (m_d2dFactory == nullptr || m_dwriteFactory == nullptr) { - m_lastRenderError = "EnsureWindowRendererInterop requires initialized D2D and DWrite factories."; - return false; - } - - const bool attached = m_windowInterop.Attach(*m_windowRenderer, *m_d2dFactory.Get()); - if (!attached) { - m_lastRenderError = m_windowInterop.GetLastError(); - } else { - m_lastRenderError.clear(); - } - return attached; -} - -void NativeRenderer::ReleaseWindowRendererInterop() { - m_windowInterop.Detach(); -} - -bool NativeRenderer::EnsureRenderTarget() { - if (!m_hwnd || !m_d2dFactory || !m_dwriteFactory) { - m_lastRenderError = "EnsureRenderTarget requires hwnd, D2D factory, and DWrite factory."; - return false; - } - - return CreateDeviceResources(); -} - -bool NativeRenderer::EnsureWicFactory(std::string& outError) { - outError.clear(); - if (m_wicFactory) { - return true; - } - - const HRESULT initHr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if (FAILED(initHr) && initHr != RPC_E_CHANGED_MODE) { - outError = HrToString("CoInitializeEx", initHr); - return false; - } - if (SUCCEEDED(initHr)) { - m_wicComInitialized = true; - } - - const HRESULT factoryHr = CoCreateInstance( - CLSID_WICImagingFactory, - nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(m_wicFactory.ReleaseAndGetAddressOf())); - if (FAILED(factoryHr)) { - outError = HrToString("CoCreateInstance(CLSID_WICImagingFactory)", factoryHr); - return false; - } - - return true; -} - -void NativeRenderer::DiscardRenderTarget() { - InvalidateCachedTextureBitmaps(m_renderTarget.Get()); - m_solidBrush.Reset(); - m_renderTarget.Reset(); -} - -bool NativeRenderer::CreateDeviceResources() { - if (m_renderTarget) { - return true; - } - - RECT clientRect = {}; - GetClientRect(m_hwnd, &clientRect); - const UINT width = static_cast((std::max)(clientRect.right - clientRect.left, 1L)); - const UINT height = static_cast((std::max)(clientRect.bottom - clientRect.top, 1L)); - - const D2D1_RENDER_TARGET_PROPERTIES renderTargetProps = D2D1::RenderTargetProperties( - D2D1_RENDER_TARGET_TYPE_DEFAULT, - D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), - kBaseDpi, - kBaseDpi); - const D2D1_HWND_RENDER_TARGET_PROPERTIES hwndProps = D2D1::HwndRenderTargetProperties( - m_hwnd, - D2D1::SizeU(width, height)); - - const HRESULT renderTargetHr = m_d2dFactory->CreateHwndRenderTarget( - renderTargetProps, - hwndProps, - m_renderTarget.ReleaseAndGetAddressOf()); - if (FAILED(renderTargetHr)) { - m_lastRenderError = HrToString("ID2D1Factory::CreateHwndRenderTarget", renderTargetHr); - return false; - } - - const HRESULT brushHr = m_renderTarget->CreateSolidColorBrush( - D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), - m_solidBrush.ReleaseAndGetAddressOf()); - if (FAILED(brushHr)) { - m_lastRenderError = HrToString("ID2D1HwndRenderTarget::CreateSolidColorBrush", brushHr); - DiscardRenderTarget(); - return false; - } - - m_renderTarget->SetDpi(kBaseDpi, kBaseDpi); - m_renderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); - m_lastRenderError.clear(); - return true; -} - -void NativeRenderer::InvalidateCachedTextureBitmaps(const ID2D1RenderTarget* renderTarget) { - for (NativeTextureResource* texture : m_liveTextures) { - if (texture == nullptr) { - continue; - } - - if (renderTarget == nullptr || texture->cachedTarget == renderTarget) { - texture->cachedBitmap.Reset(); - texture->cachedTarget = nullptr; - } - } -} - -bool NativeRenderer::DecodeTextureFile( - const std::filesystem::path& path, - NativeTextureResource& outTexture, - std::string& outError) { - outError.clear(); - if (!EnsureWicFactory(outError)) { - return false; - } - - const std::wstring widePath = path.wstring(); - Microsoft::WRL::ComPtr decoder; - HRESULT hr = m_wicFactory->CreateDecoderFromFilename( - widePath.c_str(), - nullptr, - GENERIC_READ, - WICDecodeMetadataCacheOnLoad, - decoder.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !decoder) { - outError = HrToString("IWICImagingFactory::CreateDecoderFromFilename", hr); - return false; - } - - Microsoft::WRL::ComPtr frame; - hr = decoder->GetFrame(0u, frame.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !frame) { - outError = HrToString("IWICBitmapDecoder::GetFrame", hr); - return false; - } - - return DecodeTextureFrame(*frame.Get(), outTexture, outError); -} - -bool NativeRenderer::DecodeTextureMemory( - const std::uint8_t* data, - std::size_t size, - NativeTextureResource& outTexture, - std::string& outError) { - outError.clear(); - if (data == nullptr || size == 0u) { - outError = "DecodeTextureMemory rejected an empty image payload."; - return false; - } - - if (size > static_cast((std::numeric_limits::max)())) { - outError = "DecodeTextureMemory payload exceeds WIC stream limits."; - return false; - } - - if (!EnsureWicFactory(outError)) { - return false; - } - - Microsoft::WRL::ComPtr stream; - HRESULT hr = m_wicFactory->CreateStream(stream.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !stream) { - outError = HrToString("IWICImagingFactory::CreateStream", hr); - return false; - } - - hr = stream->InitializeFromMemory( - const_cast(reinterpret_cast(data)), - static_cast(size)); - if (FAILED(hr)) { - outError = HrToString("IWICStream::InitializeFromMemory", hr); - return false; - } - - Microsoft::WRL::ComPtr decoder; - hr = m_wicFactory->CreateDecoderFromStream( - stream.Get(), - nullptr, - WICDecodeMetadataCacheOnLoad, - decoder.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !decoder) { - outError = HrToString("IWICImagingFactory::CreateDecoderFromStream", hr); - return false; - } - - Microsoft::WRL::ComPtr frame; - hr = decoder->GetFrame(0u, frame.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !frame) { - outError = HrToString("IWICBitmapDecoder::GetFrame", hr); - return false; - } - - return DecodeTextureFrame(*frame.Get(), outTexture, outError); -} - -bool NativeRenderer::DecodeTextureFrame( - IWICBitmapSource& source, - NativeTextureResource& outTexture, - std::string& outError) { - outError.clear(); - - Microsoft::WRL::ComPtr converter; - HRESULT hr = m_wicFactory->CreateFormatConverter(converter.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !converter) { - outError = HrToString("IWICImagingFactory::CreateFormatConverter", hr); - return false; - } - - hr = converter->Initialize( - &source, - GUID_WICPixelFormat32bppPBGRA, - WICBitmapDitherTypeNone, - nullptr, - 0.0f, - WICBitmapPaletteTypeCustom); - if (FAILED(hr)) { - outError = HrToString("IWICFormatConverter::Initialize", hr); - return false; - } - - UINT width = 0u; - UINT height = 0u; - hr = converter->GetSize(&width, &height); - if (FAILED(hr) || width == 0u || height == 0u) { - outError = HrToString("IWICBitmapSource::GetSize", hr); - return false; - } - - std::vector pixels( - static_cast(width) * static_cast(height) * 4u); - hr = converter->CopyPixels( - nullptr, - width * 4u, - static_cast(pixels.size()), - pixels.data()); - if (FAILED(hr)) { - outError = HrToString("IWICBitmapSource::CopyPixels", hr); - return false; - } - - outTexture.pixels = std::move(pixels); - outTexture.width = width; - outTexture.height = height; - outTexture.cachedBitmap.Reset(); - outTexture.cachedTarget = nullptr; - return true; -} - -bool NativeRenderer::ResolveTextureBitmap( - ID2D1RenderTarget& renderTarget, - NativeTextureResource& texture, - Microsoft::WRL::ComPtr& outBitmap) { - outBitmap.Reset(); - if (texture.width == 0u || texture.height == 0u || texture.pixels.empty()) { - return false; - } - - if (texture.cachedBitmap && texture.cachedTarget == &renderTarget) { - outBitmap = texture.cachedBitmap; - return true; - } - - Microsoft::WRL::ComPtr bitmap; - const D2D1_BITMAP_PROPERTIES properties = D2D1::BitmapProperties( - D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), - kBaseDpi, - kBaseDpi); - const HRESULT hr = renderTarget.CreateBitmap( - D2D1::SizeU(texture.width, texture.height), - texture.pixels.data(), - texture.width * 4u, - &properties, - bitmap.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !bitmap) { - return false; - } - - if (&renderTarget == m_renderTarget.Get()) { - texture.cachedBitmap = bitmap; - texture.cachedTarget = &renderTarget; - } - - outBitmap = std::move(bitmap); - return true; -} - -bool NativeRenderer::ResolveInteropBitmap( - const ::XCEngine::UI::UITextureHandle& texture, - Microsoft::WRL::ComPtr& outBitmap) const { - return m_windowInterop.ResolveInteropBitmap(texture, outBitmap); -} - -bool NativeRenderer::RenderToTarget( - ID2D1RenderTarget& renderTarget, - ID2D1SolidColorBrush& solidBrush, - const ::XCEngine::UI::UIDrawData& drawData) { - renderTarget.SetDpi(kBaseDpi, kBaseDpi); - renderTarget.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); - renderTarget.BeginDraw(); - renderTarget.Clear(D2D1::ColorF(0.04f, 0.05f, 0.06f, 1.0f)); - - std::vector clipStack = {}; - for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) { - for (const ::XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) { - RenderCommand(renderTarget, solidBrush, command, clipStack); - } - } - - while (!clipStack.empty()) { - renderTarget.PopAxisAlignedClip(); - clipStack.pop_back(); - } - - return true; -} - -void NativeRenderer::RenderCommand( - ID2D1RenderTarget& renderTarget, - ID2D1SolidColorBrush& solidBrush, - const ::XCEngine::UI::UIDrawCommand& command, - std::vector& clipStack) { - solidBrush.SetColor(ToD2DColor(command.color)); - const float dpiScale = ClampDpiScale(m_dpiScale); - - switch (command.type) { - case ::XCEngine::UI::UIDrawCommandType::FilledRect: { - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; - if (command.rounding > 0.0f) { - renderTarget.FillRoundedRectangle( - D2D1::RoundedRect(rect, rounding, rounding), - &solidBrush); - } else { - renderTarget.FillRectangle(rect, &solidBrush); - } - break; - } - case ::XCEngine::UI::UIDrawCommandType::FilledRectLinearGradient: { - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; - - const D2D1_GRADIENT_STOP stops[2] = { - D2D1::GradientStop(0.0f, ToD2DColor(command.color)), - D2D1::GradientStop(1.0f, ToD2DColor(command.secondaryColor)) - }; - - Microsoft::WRL::ComPtr stopCollection; - HRESULT hr = renderTarget.CreateGradientStopCollection( - stops, - 2u, - stopCollection.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !stopCollection) { - break; - } - - const D2D1_POINT_2F startPoint = - command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical - ? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.top) - : D2D1::Point2F(rect.left, (rect.top + rect.bottom) * 0.5f); - const D2D1_POINT_2F endPoint = - command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical - ? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.bottom) - : D2D1::Point2F(rect.right, (rect.top + rect.bottom) * 0.5f); - - Microsoft::WRL::ComPtr gradientBrush; - hr = renderTarget.CreateLinearGradientBrush( - D2D1::LinearGradientBrushProperties(startPoint, endPoint), - stopCollection.Get(), - gradientBrush.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !gradientBrush) { - break; - } - - if (command.rounding > 0.0f) { - renderTarget.FillRoundedRectangle( - D2D1::RoundedRect(rect, rounding, rounding), - gradientBrush.Get()); - } else { - renderTarget.FillRectangle(rect, gradientBrush.Get()); - } - break; - } - case ::XCEngine::UI::UIDrawCommandType::RectOutline: { - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; - const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; - if (command.rounding > 0.0f) { - renderTarget.DrawRoundedRectangle( - D2D1::RoundedRect(rect, rounding, rounding), - &solidBrush, - thickness); - } else { - renderTarget.DrawRectangle(rect, &solidBrush, thickness); - } - break; - } - case ::XCEngine::UI::UIDrawCommandType::Line: { - const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; - const float pixelOffset = ResolveStrokePixelOffset(thickness); - const D2D1_POINT_2F start = ToD2DPoint(command.position, dpiScale, pixelOffset); - const D2D1_POINT_2F end = ToD2DPoint(command.uvMin, dpiScale, pixelOffset); - renderTarget.DrawLine(start, end, &solidBrush, thickness); - break; - } - case ::XCEngine::UI::UIDrawCommandType::FilledTriangle: { - Microsoft::WRL::ComPtr geometry; - HRESULT hr = m_d2dFactory->CreatePathGeometry(geometry.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !geometry) { - break; - } - - Microsoft::WRL::ComPtr sink; - hr = geometry->Open(sink.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !sink) { - break; - } - - const D2D1_POINT_2F a = ToD2DPoint(command.position, dpiScale); - const D2D1_POINT_2F b = ToD2DPoint(command.uvMin, dpiScale); - const D2D1_POINT_2F c = ToD2DPoint(command.uvMax, dpiScale); - const D2D1_POINT_2F points[2] = { b, c }; - sink->BeginFigure(a, D2D1_FIGURE_BEGIN_FILLED); - sink->AddLines(points, 2u); - sink->EndFigure(D2D1_FIGURE_END_CLOSED); - hr = sink->Close(); - if (FAILED(hr)) { - break; - } - - renderTarget.FillGeometry(geometry.Get(), &solidBrush); - break; - } - case ::XCEngine::UI::UIDrawCommandType::FilledCircle: { - const float radius = command.radius * dpiScale; - renderTarget.FillEllipse( - D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius), - &solidBrush); - break; - } - case ::XCEngine::UI::UIDrawCommandType::CircleOutline: { - const float radius = command.radius * dpiScale; - const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; - renderTarget.DrawEllipse( - D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius), - &solidBrush, - thickness); - break; - } - case ::XCEngine::UI::UIDrawCommandType::Text: { - if (command.text.empty()) { - break; - } - - const float fontSize = ResolveFontSize(command.fontSize); - const float scaledFontSize = fontSize * dpiScale; - IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize); - if (textFormat == nullptr) { - break; - } - - const std::wstring text = Utf8ToWide(command.text); - if (text.empty()) { - break; - } - - const D2D1_SIZE_F targetSize = renderTarget.GetSize(); - const float originX = SnapToPixel(command.position.x, dpiScale); - const float originY = SnapToPixel(command.position.y, dpiScale); - const float lineHeight = std::ceil(scaledFontSize * 1.6f); - const D2D1_RECT_F layoutRect = D2D1::RectF( - originX, - originY, - targetSize.width, - originY + lineHeight); - renderTarget.DrawTextW( - text.c_str(), - static_cast(text.size()), - textFormat, - layoutRect, - &solidBrush, - D2D1_DRAW_TEXT_OPTIONS_CLIP, - DWRITE_MEASURING_MODE_GDI_NATURAL); - break; - } - case ::XCEngine::UI::UIDrawCommandType::Image: { - if (!command.texture.IsValid()) { - break; - } - - Microsoft::WRL::ComPtr bitmap; - if (command.texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView) { - if (!ResolveInteropBitmap(command.texture, bitmap) || !bitmap) { - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); - break; - } - } else { - auto* texture = reinterpret_cast(command.texture.nativeHandle); - if (texture == nullptr || m_liveTextures.find(texture) == m_liveTextures.end()) { - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); - break; - } - - if (!ResolveTextureBitmap(renderTarget, *texture, bitmap) || !bitmap) { - break; - } - } - - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - const float sourceWidth = static_cast(command.texture.width); - const float sourceHeight = static_cast(command.texture.height); - const float sourceLeft = sourceWidth * std::clamp(command.uvMin.x, 0.0f, 1.0f); - const float sourceTop = sourceHeight * std::clamp(command.uvMin.y, 0.0f, 1.0f); - const float sourceRight = sourceWidth * std::clamp(command.uvMax.x, 0.0f, 1.0f); - const float sourceBottom = sourceHeight * std::clamp(command.uvMax.y, 0.0f, 1.0f); - renderTarget.DrawBitmap( - bitmap.Get(), - rect, - std::clamp(command.color.a, 0.0f, 1.0f), - D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, - D2D1::RectF(sourceLeft, sourceTop, sourceRight, sourceBottom)); - break; - } - case ::XCEngine::UI::UIDrawCommandType::PushClipRect: { - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - renderTarget.PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); - clipStack.push_back(rect); - break; - } - case ::XCEngine::UI::UIDrawCommandType::PopClipRect: { - if (!clipStack.empty()) { - renderTarget.PopAxisAlignedClip(); - clipStack.pop_back(); - } - break; - } - default: - break; - } -} - -IDWriteTextFormat* NativeRenderer::GetTextFormat(float fontSize) const { - if (!m_dwriteFactory) { - return nullptr; - } - - const float resolvedFontSize = ResolveFontSize(fontSize); - const int key = static_cast(std::lround(resolvedFontSize * 10.0f)); - const auto found = m_textFormats.find(key); - if (found != m_textFormats.end()) { - return found->second.Get(); - } - - Microsoft::WRL::ComPtr textFormat; - const HRESULT hr = m_dwriteFactory->CreateTextFormat( - L"Segoe UI", - nullptr, - DWRITE_FONT_WEIGHT_REGULAR, - DWRITE_FONT_STYLE_NORMAL, - DWRITE_FONT_STRETCH_NORMAL, - resolvedFontSize, - L"", - textFormat.ReleaseAndGetAddressOf()); - if (FAILED(hr)) { - return nullptr; - } - - textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); - textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR); - textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); - - IDWriteTextFormat* result = textFormat.Get(); - m_textFormats.emplace(key, std::move(textFormat)); - return result; -} - -D2D1_COLOR_F NativeRenderer::ToD2DColor(const ::XCEngine::UI::UIColor& color) { - return D2D1::ColorF(color.r, color.g, color.b, color.a); -} - -std::wstring NativeRenderer::Utf8ToWide(std::string_view text) { - if (text.empty()) { - return {}; - } - - const int sizeNeeded = MultiByteToWideChar( - CP_UTF8, - 0, - text.data(), - static_cast(text.size()), - nullptr, - 0); - if (sizeNeeded <= 0) { - return {}; - } - - std::wstring wideText(static_cast(sizeNeeded), L'\0'); - MultiByteToWideChar( - CP_UTF8, - 0, - text.data(), - static_cast(text.size()), - wideText.data(), - sizeNeeded); - return wideText; -} - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/WindowMessageDispatcher.cpp b/new_editor/app/Host/WindowMessageDispatcher.cpp deleted file mode 100644 index 068f3b02..00000000 --- a/new_editor/app/Host/WindowMessageDispatcher.cpp +++ /dev/null @@ -1,155 +0,0 @@ -#include "WindowMessageDispatcher.h" - -#include "../Application.h" - -namespace XCEngine::UI::Editor::Host { - -namespace { - -constexpr UINT kMessageNcUaDrawCaption = 0x00AEu; -constexpr UINT kMessageNcUaDrawFrame = 0x00AFu; - -} // namespace - -void TryEnableNonClientDpiScaling(HWND hwnd) { - if (hwnd == nullptr) { - return; - } - - const HMODULE user32 = GetModuleHandleW(L"user32.dll"); - if (user32 == nullptr) { - return; - } - - using EnableNonClientDpiScalingFn = BOOL(WINAPI*)(HWND); - const auto enableNonClientDpiScaling = - reinterpret_cast( - GetProcAddress(user32, "EnableNonClientDpiScaling")); - if (enableNonClientDpiScaling != nullptr) { - enableNonClientDpiScaling(hwnd); - } -} - -Application* WindowMessageDispatcher::GetApplicationFromWindow(HWND hwnd) { - return reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); -} - -bool WindowMessageDispatcher::TryHandleNonClientCreate( - HWND hwnd, - UINT message, - LPARAM lParam, - LRESULT& outResult) { - if (message != WM_NCCREATE) { - return false; - } - - TryEnableNonClientDpiScaling(hwnd); - const auto* createStruct = reinterpret_cast(lParam); - auto* application = reinterpret_cast(createStruct->lpCreateParams); - SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(application)); - outResult = TRUE; - return true; -} - -bool WindowMessageDispatcher::TryDispatch( - HWND hwnd, - Application& application, - UINT message, - WPARAM wParam, - LPARAM lParam, - LRESULT& outResult) { - const auto renderAndValidateWindow = [&application, hwnd]() { - if (!application.m_renderReady) { - return; - } - - application.RenderFrame(); - if (hwnd != nullptr && IsWindow(hwnd)) { - ValidateRect(hwnd, nullptr); - } - }; - - switch (message) { - case WM_GETMINMAXINFO: - if (application.IsBorderlessWindowEnabled() && - application.HandleBorderlessWindowGetMinMaxInfo(lParam)) { - outResult = 0; - return true; - } - return false; - case WM_NCCALCSIZE: - if (application.IsBorderlessWindowEnabled()) { - outResult = application.HandleBorderlessWindowNcCalcSize(wParam, lParam); - return true; - } - return false; - case WM_NCACTIVATE: - if (application.IsBorderlessWindowEnabled()) { - outResult = TRUE; - return true; - } - return false; - case WM_NCHITTEST: - if (application.IsBorderlessWindowEnabled()) { - outResult = HTCLIENT; - return true; - } - return false; - case WM_NCPAINT: - case kMessageNcUaDrawCaption: - case kMessageNcUaDrawFrame: - if (application.IsBorderlessWindowEnabled()) { - outResult = 0; - return true; - } - return false; - case WM_SYSCOMMAND: - if (application.HandleBorderlessWindowSystemCommand(wParam)) { - outResult = 0; - return true; - } - return false; - case WM_SETCURSOR: - if (LOWORD(lParam) == HTCLIENT && application.ApplyCurrentCursor()) { - outResult = TRUE; - return true; - } - return false; - case WM_DPICHANGED: - if (lParam == 0) { - return false; - } - application.OnDpiChanged( - static_cast(LOWORD(wParam)), - *reinterpret_cast(lParam)); - renderAndValidateWindow(); - outResult = 0; - return true; - case WM_ENTERSIZEMOVE: - application.OnEnterSizeMove(); - outResult = 0; - return true; - case WM_EXITSIZEMOVE: - application.OnExitSizeMove(); - renderAndValidateWindow(); - outResult = 0; - return true; - case WM_SIZE: - if (wParam != SIZE_MINIMIZED) { - application.OnResize( - static_cast(LOWORD(lParam)), - static_cast(HIWORD(lParam))); - renderAndValidateWindow(); - } - outResult = 0; - return true; - case WM_PAINT: - application.OnPaintMessage(); - outResult = 0; - return true; - default: - return false; - } -} - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/BorderlessWindowChrome.cpp b/new_editor/app/Platform/Win32/BorderlessWindowChrome.cpp new file mode 100644 index 00000000..711f5500 --- /dev/null +++ b/new_editor/app/Platform/Win32/BorderlessWindowChrome.cpp @@ -0,0 +1,72 @@ +#include "BorderlessWindowChrome.h" + +#include + +namespace XCEngine::UI::Editor::Host { + +namespace { + +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) { + return rect.width > 0.0f && + rect.height > 0.0f && + point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +} // namespace + +BorderlessWindowChromeLayout BuildBorderlessWindowChromeLayout( + const UIRect& titleBarRect, + float leadingOccupiedRight, + const BorderlessWindowChromeMetrics& metrics) { + BorderlessWindowChromeLayout layout = {}; + layout.titleBarRect = titleBarRect; + if (titleBarRect.width <= 0.0f || titleBarRect.height <= 0.0f) { + return layout; + } + + const float buttonWidth = (std::max)(metrics.buttonWidth, 0.0f); + const float buttonX3 = titleBarRect.x + titleBarRect.width - metrics.buttonInsetX - buttonWidth; + const float buttonX2 = buttonX3 - buttonWidth; + const float buttonX1 = buttonX2 - buttonWidth; + + layout.minimizeButtonRect = UIRect(buttonX1, titleBarRect.y, buttonWidth, titleBarRect.height); + layout.maximizeRestoreButtonRect = UIRect(buttonX2, titleBarRect.y, buttonWidth, titleBarRect.height); + layout.closeButtonRect = UIRect(buttonX3, titleBarRect.y, buttonWidth, titleBarRect.height); + + const float dragLeft = + (std::max)(titleBarRect.x, leadingOccupiedRight + metrics.dragPaddingLeft); + const float dragRight = + (std::max)(dragLeft, layout.minimizeButtonRect.x - metrics.dragPaddingRight); + layout.dragRect = UIRect( + dragLeft, + titleBarRect.y, + (std::max)(0.0f, dragRight - dragLeft), + titleBarRect.height); + return layout; +} + +BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome( + const BorderlessWindowChromeLayout& layout, + const UIPoint& point) { + if (IsPointInsideRect(layout.closeButtonRect, point)) { + return BorderlessWindowChromeHitTarget::CloseButton; + } + if (IsPointInsideRect(layout.maximizeRestoreButtonRect, point)) { + return BorderlessWindowChromeHitTarget::MaximizeRestoreButton; + } + if (IsPointInsideRect(layout.minimizeButtonRect, point)) { + return BorderlessWindowChromeHitTarget::MinimizeButton; + } + if (IsPointInsideRect(layout.dragRect, point)) { + return BorderlessWindowChromeHitTarget::DragRegion; + } + return BorderlessWindowChromeHitTarget::None; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/BorderlessWindowChrome.h b/new_editor/app/Platform/Win32/BorderlessWindowChrome.h similarity index 95% rename from new_editor/app/Host/BorderlessWindowChrome.h rename to new_editor/app/Platform/Win32/BorderlessWindowChrome.h index 59404c93..b4c7a6f6 100644 --- a/new_editor/app/Host/BorderlessWindowChrome.h +++ b/new_editor/app/Platform/Win32/BorderlessWindowChrome.h @@ -32,9 +32,9 @@ struct BorderlessWindowChromePalette { ::XCEngine::UI::UIColor buttonPressedColor = ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor closeButtonHoverColor = - ::XCEngine::UI::UIColor(0.91f, 0.31f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor(0.84f, 0.28f, 0.22f, 1.0f); ::XCEngine::UI::UIColor closeButtonPressedColor = - ::XCEngine::UI::UIColor(0.78f, 0.22f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.20f, 0.16f, 1.0f); ::XCEngine::UI::UIColor iconColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor closeIconHoverColor = diff --git a/new_editor/app/Platform/Win32/BorderlessWindowChromeDwm.cpp b/new_editor/app/Platform/Win32/BorderlessWindowChromeDwm.cpp new file mode 100644 index 00000000..19142810 --- /dev/null +++ b/new_editor/app/Platform/Win32/BorderlessWindowChromeDwm.cpp @@ -0,0 +1,195 @@ +#include "BorderlessWindowChrome.h" + +#include + +namespace XCEngine::UI::Editor::Host { + +namespace { + +bool IsWindowAlignedToMonitorWorkArea(HWND hwnd) { + if (hwnd == nullptr) { + return false; + } + + RECT windowRect = {}; + if (!GetWindowRect(hwnd, &windowRect)) { + return false; + } + + const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + if (monitor == nullptr) { + return false; + } + + MONITORINFO monitorInfo = {}; + monitorInfo.cbSize = sizeof(monitorInfo); + if (!GetMonitorInfoW(monitor, &monitorInfo)) { + return false; + } + + const RECT& workArea = monitorInfo.rcWork; + return windowRect.left == workArea.left && + windowRect.top == workArea.top && + windowRect.right == workArea.right && + windowRect.bottom == workArea.bottom; +} + +void ApplyDwmBoolWindowAttribute(HWND hwnd, DWORD attribute, BOOL value) { + if (hwnd == nullptr) { + return; + } + + using DwmSetWindowAttributeFn = HRESULT(WINAPI*)(HWND, DWORD, LPCVOID, DWORD); + static const auto setWindowAttribute = []() -> DwmSetWindowAttributeFn { + HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll"); + if (dwmapi == nullptr) { + dwmapi = LoadLibraryW(L"dwmapi.dll"); + } + if (dwmapi == nullptr) { + return nullptr; + } + + return reinterpret_cast( + GetProcAddress(dwmapi, "DwmSetWindowAttribute")); + }(); + + if (setWindowAttribute != nullptr) { + setWindowAttribute(hwnd, attribute, &value, sizeof(value)); + } +} + +void ApplyDwmColorWindowAttribute(HWND hwnd, DWORD attribute, COLORREF value) { + if (hwnd == nullptr) { + return; + } + + using DwmSetWindowAttributeFn = HRESULT(WINAPI*)(HWND, DWORD, LPCVOID, DWORD); + static const auto setWindowAttribute = []() -> DwmSetWindowAttributeFn { + HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll"); + if (dwmapi == nullptr) { + dwmapi = LoadLibraryW(L"dwmapi.dll"); + } + if (dwmapi == nullptr) { + return nullptr; + } + + return reinterpret_cast( + GetProcAddress(dwmapi, "DwmSetWindowAttribute")); + }(); + + if (setWindowAttribute != nullptr) { + setWindowAttribute(hwnd, attribute, &value, sizeof(value)); + } +} + +void ApplyDwmIntWindowAttribute(HWND hwnd, DWORD attribute, int value) { + if (hwnd == nullptr) { + return; + } + + using DwmSetWindowAttributeFn = HRESULT(WINAPI*)(HWND, DWORD, LPCVOID, DWORD); + static const auto setWindowAttribute = []() -> DwmSetWindowAttributeFn { + HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll"); + if (dwmapi == nullptr) { + dwmapi = LoadLibraryW(L"dwmapi.dll"); + } + if (dwmapi == nullptr) { + return nullptr; + } + + return reinterpret_cast( + GetProcAddress(dwmapi, "DwmSetWindowAttribute")); + }(); + + if (setWindowAttribute != nullptr) { + setWindowAttribute(hwnd, attribute, &value, sizeof(value)); + } +} + +} // namespace + +void EnableBorderlessWindowShadow(HWND hwnd) { + if (hwnd == nullptr) { + return; + } + + using DwmExtendFrameIntoClientAreaFn = HRESULT(WINAPI*)(HWND, const MARGINS*); + static const auto extendFrameIntoClientArea = []() -> DwmExtendFrameIntoClientAreaFn { + HMODULE dwmapi = GetModuleHandleW(L"dwmapi.dll"); + if (dwmapi == nullptr) { + dwmapi = LoadLibraryW(L"dwmapi.dll"); + } + if (dwmapi == nullptr) { + return nullptr; + } + return reinterpret_cast( + GetProcAddress(dwmapi, "DwmExtendFrameIntoClientArea")); + }(); + if (extendFrameIntoClientArea != nullptr) { + const bool maximized = IsZoomed(hwnd) || IsWindowAlignedToMonitorWorkArea(hwnd); + const MARGINS margins = maximized + ? MARGINS{ 0, 0, 0, 0 } + : MARGINS{ 1, 1, 1, 1 }; + extendFrameIntoClientArea(hwnd, &margins); + } +} + +void RefreshBorderlessWindowDwmDecorations(HWND hwnd) { + if (hwnd == nullptr) { + return; + } + + ApplyDwmIntWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, DWMNCRP_DISABLED); + ApplyDwmBoolWindowAttribute(hwnd, DWMWA_ALLOW_NCPAINT, FALSE); + ApplyDwmBoolWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED, TRUE); + ApplyDwmBoolWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, TRUE); + ApplyDwmColorWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, RGB(26, 26, 26)); + ApplyDwmColorWindowAttribute(hwnd, DWMWA_TEXT_COLOR, RGB(235, 235, 235)); + ApplyDwmColorWindowAttribute(hwnd, DWMWA_BORDER_COLOR, RGB(26, 26, 26)); + EnableBorderlessWindowShadow(hwnd); +} + +bool HandleBorderlessWindowGetMinMaxInfo(HWND hwnd, LPARAM lParam) { + if (hwnd == nullptr || lParam == 0) { + return false; + } + + auto* minMaxInfo = reinterpret_cast(lParam); + if (minMaxInfo == nullptr) { + return false; + } + + const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + if (monitor == nullptr) { + return false; + } + + MONITORINFO monitorInfo = {}; + monitorInfo.cbSize = sizeof(monitorInfo); + if (!GetMonitorInfoW(monitor, &monitorInfo)) { + return false; + } + + const RECT& workArea = monitorInfo.rcWork; + const RECT& monitorArea = monitorInfo.rcMonitor; + minMaxInfo->ptMaxPosition.x = workArea.left - monitorArea.left; + minMaxInfo->ptMaxPosition.y = workArea.top - monitorArea.top; + minMaxInfo->ptMaxSize.x = workArea.right - workArea.left; + minMaxInfo->ptMaxSize.y = workArea.bottom - workArea.top; + minMaxInfo->ptMaxTrackSize = minMaxInfo->ptMaxSize; + return true; +} + +LRESULT HandleBorderlessWindowNcCalcSize( + HWND hwnd, + WPARAM wParam, + LPARAM lParam, + UINT dpi) { + (void)hwnd; + (void)wParam; + (void)lParam; + (void)dpi; + return 0; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/BorderlessWindowChromeRendering.cpp b/new_editor/app/Platform/Win32/BorderlessWindowChromeRendering.cpp new file mode 100644 index 00000000..c6d7599f --- /dev/null +++ b/new_editor/app/Platform/Win32/BorderlessWindowChromeRendering.cpp @@ -0,0 +1,193 @@ +#include "BorderlessWindowChrome.h" + +#include +#include + +namespace XCEngine::UI::Editor::Host { + +namespace { + +using ::XCEngine::UI::UIColor; +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +constexpr UIColor kTransparentColor(0.0f, 0.0f, 0.0f, 0.0f); + +float ResolveGlyphBoxSize(const UIRect& rect, float ratio, float minSize) { + return (std::max)(minSize, static_cast(std::round(rect.height * ratio))); +} + +void AppendMinimizeGlyph( + UIDrawList& drawList, + const UIRect& rect, + const UIColor& color, + float thickness) { + const float centerX = rect.x + rect.width * 0.5f; + const float centerY = rect.y + rect.height * 0.5f; + const float halfWidth = ResolveGlyphBoxSize(rect, 0.38f, 10.0f) * 0.5f; + const float y = centerY; + drawList.AddLine( + UIPoint(centerX - halfWidth, y), + UIPoint(centerX + halfWidth, y), + color, + thickness); +} + +void AppendMaximizeGlyph( + UIDrawList& drawList, + const UIRect& rect, + const UIColor& color, + float thickness) { + const float centerX = rect.x + rect.width * 0.5f; + const float centerY = rect.y + rect.height * 0.5f; + const float boxSize = ResolveGlyphBoxSize(rect, 0.32f, 9.0f); + const float halfExtent = boxSize * 0.5f; + const float left = centerX - halfExtent; + const float top = centerY - halfExtent; + const float right = left + boxSize; + const float bottom = top + boxSize; + drawList.AddLine(UIPoint(left, top), UIPoint(right, top), color, thickness); + drawList.AddLine(UIPoint(left, top), UIPoint(left, bottom), color, thickness); + drawList.AddLine(UIPoint(right, top), UIPoint(right, bottom), color, thickness); + drawList.AddLine(UIPoint(left, bottom), UIPoint(right, bottom), color, thickness); +} + +void AppendRestoreGlyph( + UIDrawList& drawList, + const UIRect& rect, + const UIColor& color, + float thickness) { + const float centerX = rect.x + rect.width * 0.5f; + const float centerY = rect.y + rect.height * 0.5f; + const float boxSize = ResolveGlyphBoxSize(rect, 0.29f, 8.0f); + const float halfExtent = boxSize * 0.5f; + const float offset = 1.0f; + + const float backLeft = centerX - halfExtent + offset; + const float backTop = centerY - halfExtent - offset; + const float backRight = backLeft + boxSize; + const float backBottom = backTop + boxSize; + + const float frontLeft = centerX - halfExtent - offset; + const float frontTop = centerY - halfExtent + offset; + const float frontRight = frontLeft + boxSize; + const float frontBottom = frontTop + boxSize; + + drawList.AddLine(UIPoint(backLeft, backTop), UIPoint(backRight, backTop), color, thickness); + drawList.AddLine(UIPoint(backLeft, backTop), UIPoint(backLeft, frontTop), color, thickness); + drawList.AddLine(UIPoint(backRight, backTop), UIPoint(backRight, backBottom), color, thickness); + drawList.AddLine(UIPoint(frontRight, backBottom), UIPoint(backRight, backBottom), color, thickness); + + drawList.AddLine(UIPoint(frontLeft, frontTop), UIPoint(frontRight, frontTop), color, thickness); + drawList.AddLine(UIPoint(frontLeft, frontTop), UIPoint(frontLeft, frontBottom), color, thickness); + drawList.AddLine(UIPoint(frontRight, frontTop), UIPoint(frontRight, frontBottom), color, thickness); + drawList.AddLine(UIPoint(frontLeft, frontBottom), UIPoint(frontRight, frontBottom), color, thickness); +} + +void AppendCloseGlyph( + UIDrawList& drawList, + const UIRect& rect, + const UIColor& color, + float thickness) { + const float centerX = rect.x + rect.width * 0.5f; + const float centerY = rect.y + rect.height * 0.5f; + const float halfWidth = ResolveGlyphBoxSize(rect, 0.29f, 8.0f) * 0.5f; + const float halfHeight = halfWidth; + drawList.AddLine( + UIPoint(centerX - halfWidth, centerY - halfHeight), + UIPoint(centerX + halfWidth, centerY + halfHeight), + color, + thickness); + drawList.AddLine( + UIPoint(centerX + halfWidth, centerY - halfHeight), + UIPoint(centerX - halfWidth, centerY + halfHeight), + color, + thickness); +} + +UIColor ResolveButtonFillColor( + BorderlessWindowChromeHitTarget target, + const BorderlessWindowChromeState& state, + const BorderlessWindowChromePalette& palette) { + const bool hovered = state.hoveredTarget == target; + const bool pressed = state.pressedTarget == target; + if (target == BorderlessWindowChromeHitTarget::CloseButton) { + if (pressed) { + return palette.closeButtonPressedColor; + } + if (hovered) { + return palette.closeButtonHoverColor; + } + return kTransparentColor; + } + + if (pressed) { + return palette.buttonPressedColor; + } + if (hovered) { + return palette.buttonHoverColor; + } + return kTransparentColor; +} + +UIColor ResolveIconColor( + BorderlessWindowChromeHitTarget target, + const BorderlessWindowChromeState& state, + const BorderlessWindowChromePalette& palette) { + if (target == BorderlessWindowChromeHitTarget::CloseButton && + (state.hoveredTarget == target || state.pressedTarget == target)) { + return palette.closeIconHoverColor; + } + + return palette.iconColor; +} + +} // namespace + +void AppendBorderlessWindowChrome( + UIDrawList& drawList, + const BorderlessWindowChromeLayout& layout, + const BorderlessWindowChromeState& state, + bool maximized, + const BorderlessWindowChromePalette& palette, + const BorderlessWindowChromeMetrics& metrics) { + const struct ButtonEntry { + BorderlessWindowChromeHitTarget target; + UIRect rect; + } buttons[] = { + { BorderlessWindowChromeHitTarget::MinimizeButton, layout.minimizeButtonRect }, + { BorderlessWindowChromeHitTarget::MaximizeRestoreButton, layout.maximizeRestoreButtonRect }, + { BorderlessWindowChromeHitTarget::CloseButton, layout.closeButtonRect } + }; + + for (const ButtonEntry& button : buttons) { + const UIColor fill = ResolveButtonFillColor(button.target, state, palette); + if (fill.a > 0.0f) { + drawList.AddFilledRect(button.rect, fill); + } + + const UIColor iconColor = ResolveIconColor(button.target, state, palette); + switch (button.target) { + case BorderlessWindowChromeHitTarget::MinimizeButton: + AppendMinimizeGlyph(drawList, button.rect, iconColor, metrics.iconThickness); + break; + case BorderlessWindowChromeHitTarget::MaximizeRestoreButton: + if (maximized) { + AppendRestoreGlyph(drawList, button.rect, iconColor, metrics.iconThickness); + } else { + AppendMaximizeGlyph(drawList, button.rect, iconColor, metrics.iconThickness); + } + break; + case BorderlessWindowChromeHitTarget::CloseButton: + AppendCloseGlyph(drawList, button.rect, iconColor, metrics.iconThickness); + break; + case BorderlessWindowChromeHitTarget::DragRegion: + case BorderlessWindowChromeHitTarget::None: + default: + break; + } + } +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/BorderlessWindowFrame.cpp b/new_editor/app/Platform/Win32/BorderlessWindowFrame.cpp similarity index 100% rename from new_editor/app/Host/BorderlessWindowFrame.cpp rename to new_editor/app/Platform/Win32/BorderlessWindowFrame.cpp diff --git a/new_editor/app/Host/BorderlessWindowFrame.h b/new_editor/app/Platform/Win32/BorderlessWindowFrame.h similarity index 100% rename from new_editor/app/Host/BorderlessWindowFrame.h rename to new_editor/app/Platform/Win32/BorderlessWindowFrame.h diff --git a/new_editor/app/Platform/Win32/EditorWindow.h b/new_editor/app/Platform/Win32/EditorWindow.h new file mode 100644 index 00000000..3d2c7fb6 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindow.h @@ -0,0 +1,191 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "State/EditorContext.h" +#include "Platform/Win32/EditorWindowState.h" + +#include + +#include +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::App { + +struct EditorWindowPendingTabDragStart { + std::string nodeId = {}; + std::string panelId = {}; + POINT screenPoint = {}; +}; + +struct EditorWindowPendingDetachRequest { + std::string nodeId = {}; + std::string panelId = {}; + POINT screenPoint = {}; +}; + +class EditorWindow { +public: + EditorWindow( + std::string windowId, + std::wstring title, + bool primary, + UIEditorWorkspaceController workspaceController); + + EditorWindow(const EditorWindow&) = delete; + EditorWindow& operator=(const EditorWindow&) = delete; + EditorWindow(EditorWindow&&) = delete; + EditorWindow& operator=(EditorWindow&&) = delete; + + std::string_view GetWindowId() const; + HWND GetHwnd() const; + bool HasHwnd() const; + bool IsPrimary() const; + bool IsRenderReady() const; + bool IsTrackingMouseLeave() const; + bool HasHoveredBorderlessResizeEdge() const; + const std::wstring& GetTitle() const; + const UIEditorWorkspaceController& GetWorkspaceController() const; + UIEditorWorkspaceController& GetWorkspaceController(); + const EditorShellRuntime& GetShellRuntime() const; + EditorShellRuntime& GetShellRuntime(); + const UIEditorShellInteractionFrame& GetShellFrame() const; + const UIEditorShellInteractionState& GetShellInteractionState() const; + ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips(const POINT& screenPoint) const; + void InvalidateHostWindow() const; + + void AttachHwnd(HWND hwnd); + void MarkDestroyed(); + void SetTrackingMouseLeave(bool trackingMouseLeave); + void SetTitle(std::wstring title); + void ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController); + + bool Initialize( + const std::filesystem::path& repoRoot, + EditorContext& editorContext, + const std::filesystem::path& captureRoot, + bool autoCaptureOnStartup); + void Shutdown(); + void ResetInteractionState(); + + std::optional ConsumePendingTabDragStart(); + std::optional ConsumeDetachRequest(); + + void RenderFrame( + EditorContext& editorContext, + bool globalTabDragActive); + void OnPaintMessage( + EditorContext& editorContext, + bool globalTabDragActive); + void OnResize(UINT width, UINT height); + void OnEnterSizeMove(); + void OnExitSizeMove(); + void OnDpiChanged(UINT dpi, const RECT& suggestedRect); + + bool IsBorderlessWindowEnabled() const; + bool IsBorderlessWindowMaximized() const; + bool HandleBorderlessWindowSystemCommand( + EditorContext& editorContext, + bool globalTabDragActive, + WPARAM wParam); + bool HandleBorderlessWindowGetMinMaxInfo(LPARAM lParam) const; + LRESULT HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const; + bool ApplyCurrentCursor() const; + bool UpdateBorderlessWindowResizeHover(LPARAM lParam); + bool HandleBorderlessWindowResizeButtonDown(LPARAM lParam); + bool HandleBorderlessWindowResizeButtonUp(); + bool HandleBorderlessWindowResizePointerMove( + EditorContext& editorContext, + bool globalTabDragActive); + void ClearBorderlessWindowResizeState(); + void ForceClearBorderlessWindowResizeState(); + Host::BorderlessWindowChromeHitTarget HitTestBorderlessWindowChrome(LPARAM lParam) const; + bool UpdateBorderlessWindowChromeHover(LPARAM lParam); + bool HandleBorderlessWindowChromeButtonDown(LPARAM lParam); + bool HandleBorderlessWindowChromeButtonUp( + EditorContext& editorContext, + bool globalTabDragActive, + LPARAM lParam); + bool HandleBorderlessWindowChromeDoubleClick( + EditorContext& editorContext, + bool globalTabDragActive, + LPARAM lParam); + bool HandleBorderlessWindowChromeDragRestorePointerMove( + EditorContext& editorContext, + bool globalTabDragActive); + void ClearBorderlessWindowChromeDragRestoreState(); + void ClearBorderlessWindowChromeState(); + bool HasInteractiveCaptureState() const; + + void QueuePointerEvent( + ::XCEngine::UI::UIInputEventType type, + ::XCEngine::UI::UIPointerButton button, + WPARAM wParam, + LPARAM lParam); + void QueuePointerLeaveEvent(); + void QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam); + void QueueKeyEvent(::XCEngine::UI::UIInputEventType type, WPARAM wParam, LPARAM lParam); + void QueueCharacterEvent(WPARAM wParam, LPARAM lParam); + void QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType type); + void SyncInputModifiersFromSystemState(); + void ResetInputModifiers(); + void RequestManualScreenshot(); + +private: + bool ApplyWindowResize(UINT width, UINT height); + bool QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const; + bool ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const; + ::XCEngine::UI::UIRect ResolveWorkspaceBounds( + float clientWidthDips, + float clientHeightDips) const; + bool IsPointerInsideClientArea() const; + LPCWSTR ResolveCurrentCursorResource() const; + float GetDpiScale() const; + float PixelsToDips(float pixels) const; + ::XCEngine::UI::UIPoint ConvertClientPixelsToDips(LONG x, LONG y) const; + std::string BuildCaptureStatusText() const; + void ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result); + void ApplyHostedContentCaptureRequests(); + std::string DescribeInputEvents( + const std::vector<::XCEngine::UI::UIInputEvent>& events) const; + Host::BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(LPARAM lParam) const; + void ApplyBorderlessWindowResizeCursorHoverPriority(); + Host::BorderlessWindowChromeLayout ResolveBorderlessWindowChromeLayout( + float clientWidthDips) const; + bool QueryCurrentWindowRect(RECT& outRect) const; + bool QueryBorderlessWindowWorkAreaRect(RECT& outRect) const; + bool ApplyPredictedWindowRectTransition( + EditorContext& editorContext, + bool globalTabDragActive, + const RECT& targetRect); + void ToggleBorderlessWindowMaximizeRestore( + EditorContext& editorContext, + bool globalTabDragActive); + void AppendBorderlessWindowChrome( + ::XCEngine::UI::UIDrawList& drawList, + float clientWidthDips) const; + void ExecuteBorderlessWindowChromeAction( + EditorContext& editorContext, + bool globalTabDragActive, + Host::BorderlessWindowChromeHitTarget target); + void QueuePendingTabDragStart(std::string_view nodeId, std::string_view panelId); + void QueuePendingDetachRequest(std::string_view nodeId, std::string_view panelId); + void UpdateCachedTitleText(); + static bool IsVerboseRuntimeTraceEnabled(); + + EditorWindowWindowState m_window = {}; + EditorWindowRenderState m_render = {}; + EditorWindowInputState m_input = {}; + EditorWindowCompositionState m_composition = {}; + EditorWindowChromeRuntimeState m_chrome = {}; + EditorWindowPanelTransferState m_pendingDetachRequest = {}; + EditorWindowPanelTransferState m_pendingTabDragStart = {}; +}; + +} // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp b/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp new file mode 100644 index 00000000..4f3e4b67 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp @@ -0,0 +1,140 @@ +#include "Platform/Win32/EditorWindow.h" +namespace XCEngine::UI::Editor::App { + +bool EditorWindow::IsBorderlessWindowEnabled() const { + return true; +} + +bool EditorWindow::IsBorderlessWindowMaximized() const { + return m_chrome.runtime.IsBorderlessWindowMaximized(); +} + +bool EditorWindow::HandleBorderlessWindowSystemCommand( + EditorContext& editorContext, + bool globalTabDragActive, + WPARAM wParam) { + if (!IsBorderlessWindowEnabled()) { + return false; + } + + switch (wParam & 0xFFF0u) { + case SC_MAXIMIZE: + ToggleBorderlessWindowMaximizeRestore(editorContext, globalTabDragActive); + return true; + case SC_RESTORE: + if (!IsIconic(m_window.hwnd)) { + ToggleBorderlessWindowMaximizeRestore(editorContext, globalTabDragActive); + return true; + } + return false; + default: + return false; + } +} + +bool EditorWindow::HandleBorderlessWindowGetMinMaxInfo(LPARAM lParam) const { + return Host::HandleBorderlessWindowGetMinMaxInfo(m_window.hwnd, lParam); +} + +LRESULT EditorWindow::HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const { + return Host::HandleBorderlessWindowNcCalcSize( + m_window.hwnd, + wParam, + lParam, + m_chrome.runtime.GetWindowDpi()); +} + +bool EditorWindow::QueryCurrentWindowRect(RECT& outRect) const { + outRect = {}; + return m_window.hwnd != nullptr && GetWindowRect(m_window.hwnd, &outRect) != FALSE; +} + +bool EditorWindow::QueryBorderlessWindowWorkAreaRect(RECT& outRect) const { + outRect = {}; + if (m_window.hwnd == nullptr) { + return false; + } + + const HMONITOR monitor = MonitorFromWindow(m_window.hwnd, MONITOR_DEFAULTTONEAREST); + if (monitor == nullptr) { + return false; + } + + MONITORINFO monitorInfo = {}; + monitorInfo.cbSize = sizeof(monitorInfo); + if (!GetMonitorInfoW(monitor, &monitorInfo)) { + return false; + } + + outRect = monitorInfo.rcWork; + return true; +} + +bool EditorWindow::ApplyPredictedWindowRectTransition( + EditorContext& editorContext, + bool globalTabDragActive, + const RECT& targetRect) { + if (m_window.hwnd == nullptr) { + return false; + } + + const int width = targetRect.right - targetRect.left; + const int height = targetRect.bottom - targetRect.top; + if (width <= 0 || height <= 0) { + return false; + } + + m_chrome.runtime.SetPredictedClientPixelSize( + static_cast(width), + static_cast(height)); + ApplyWindowResize(static_cast(width), static_cast(height)); + RenderFrame(editorContext, globalTabDragActive); + SetWindowPos( + m_window.hwnd, + nullptr, + targetRect.left, + targetRect.top, + width, + height, + SWP_NOZORDER | SWP_NOACTIVATE); + InvalidateHostWindow(); + return true; +} + +void EditorWindow::ToggleBorderlessWindowMaximizeRestore( + EditorContext& editorContext, + bool globalTabDragActive) { + if (m_window.hwnd == nullptr) { + return; + } + + if (!IsBorderlessWindowMaximized()) { + RECT currentRect = {}; + RECT workAreaRect = {}; + if (!QueryCurrentWindowRect(currentRect) || + !QueryBorderlessWindowWorkAreaRect(workAreaRect)) { + return; + } + + m_chrome.runtime.SetBorderlessWindowRestoreRect(currentRect); + m_chrome.runtime.SetBorderlessWindowMaximized(true); + ApplyPredictedWindowRectTransition( + editorContext, + globalTabDragActive, + workAreaRect); + return; + } + + RECT restoreRect = {}; + if (!m_chrome.runtime.TryGetBorderlessWindowRestoreRect(restoreRect)) { + return; + } + + m_chrome.runtime.SetBorderlessWindowMaximized(false); + ApplyPredictedWindowRectTransition( + editorContext, + globalTabDragActive, + restoreRect); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp b/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp new file mode 100644 index 00000000..ed44f041 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp @@ -0,0 +1,161 @@ +#include "Platform/Win32/EditorWindow.h" + +#include + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +using ::XCEngine::UI::UIRect; + +bool EditorWindow::UpdateBorderlessWindowResizeHover(LPARAM lParam) { + const Host::BorderlessWindowResizeEdge hoveredEdge = + HitTestBorderlessWindowResizeEdge(lParam); + if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() == hoveredEdge) { + return false; + } + + m_chrome.runtime.SetHoveredBorderlessResizeEdge(hoveredEdge); + ApplyBorderlessWindowResizeCursorHoverPriority(); + return true; +} + +bool EditorWindow::HandleBorderlessWindowResizeButtonDown(LPARAM lParam) { + const Host::BorderlessWindowResizeEdge edge = + HitTestBorderlessWindowResizeEdge(lParam); + if (edge == Host::BorderlessWindowResizeEdge::None || m_window.hwnd == nullptr) { + return false; + } + + POINT screenPoint = {}; + if (!GetCursorPos(&screenPoint)) { + return false; + } + + RECT windowRect = {}; + if (!GetWindowRect(m_window.hwnd, &windowRect)) { + return false; + } + + m_chrome.runtime.BeginBorderlessResize(edge, screenPoint, windowRect); + SetCapture(m_window.hwnd); + InvalidateHostWindow(); + return true; +} + +bool EditorWindow::HandleBorderlessWindowResizeButtonUp() { + if (!m_chrome.runtime.IsBorderlessResizeActive()) { + return false; + } + + m_chrome.runtime.EndBorderlessResize(); + if (GetCapture() == m_window.hwnd) { + ReleaseCapture(); + } + InvalidateHostWindow(); + return true; +} + +bool EditorWindow::HandleBorderlessWindowResizePointerMove( + EditorContext& editorContext, + bool globalTabDragActive) { + if (!m_chrome.runtime.IsBorderlessResizeActive() || m_window.hwnd == nullptr) { + return false; + } + + POINT currentScreenPoint = {}; + if (!GetCursorPos(¤tScreenPoint)) { + return false; + } + + RECT targetRect = Host::ComputeBorderlessWindowResizeRect( + m_chrome.runtime.GetBorderlessResizeInitialWindowRect(), + m_chrome.runtime.GetBorderlessResizeInitialScreenPoint(), + currentScreenPoint, + m_chrome.runtime.GetBorderlessResizeEdge(), + 640, + 360); + const int width = targetRect.right - targetRect.left; + const int height = targetRect.bottom - targetRect.top; + if (width <= 0 || height <= 0) { + return true; + } + + m_chrome.runtime.SetPredictedClientPixelSize( + static_cast(width), + static_cast(height)); + ApplyWindowResize(static_cast(width), static_cast(height)); + RenderFrame(editorContext, globalTabDragActive); + + SetWindowPos( + m_window.hwnd, + nullptr, + targetRect.left, + targetRect.top, + width, + height, + SWP_NOZORDER | SWP_NOACTIVATE); + return true; +} + +void EditorWindow::ClearBorderlessWindowResizeState() { + if (m_chrome.runtime.IsBorderlessResizeActive()) { + return; + } + + if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() == + Host::BorderlessWindowResizeEdge::None) { + return; + } + + m_chrome.runtime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); + InvalidateHostWindow(); +} + +void EditorWindow::ForceClearBorderlessWindowResizeState() { + if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() == + Host::BorderlessWindowResizeEdge::None && + !m_chrome.runtime.IsBorderlessResizeActive()) { + return; + } + + m_chrome.runtime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); + m_chrome.runtime.EndBorderlessResize(); + if (GetCapture() == m_window.hwnd) { + ReleaseCapture(); + } + InvalidateHostWindow(); +} + +Host::BorderlessWindowResizeEdge EditorWindow::HitTestBorderlessWindowResizeEdge( + LPARAM lParam) const { + if (!IsBorderlessWindowEnabled() || m_window.hwnd == nullptr || IsBorderlessWindowMaximized()) { + return Host::BorderlessWindowResizeEdge::None; + } + + RECT clientRect = {}; + if (!GetClientRect(m_window.hwnd, &clientRect)) { + return Host::BorderlessWindowResizeEdge::None; + } + + const float clientWidthDips = + PixelsToDips(static_cast((std::max)(clientRect.right - clientRect.left, 1L))); + const float clientHeightDips = + PixelsToDips(static_cast((std::max)(clientRect.bottom - clientRect.top, 1L))); + return Host::HitTestBorderlessWindowResizeEdge( + UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips), + ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); +} + +void EditorWindow::ApplyBorderlessWindowResizeCursorHoverPriority() { + if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() != + Host::BorderlessWindowResizeEdge::None || + m_chrome.runtime.IsBorderlessResizeActive()) { + m_chrome.chromeState.hoveredTarget = + Host::BorderlessWindowChromeHitTarget::None; + } +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowConstants.h b/new_editor/app/Platform/Win32/EditorWindowConstants.h new file mode 100644 index 00000000..e8d01a60 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowConstants.h @@ -0,0 +1,10 @@ +#pragma once + +namespace XCEngine::UI::Editor::App::EditorWindowSupport { + +inline constexpr unsigned int kDefaultDpi = 96u; +inline constexpr float kBaseDpiScale = 96.0f; +inline constexpr float kBorderlessTitleBarHeightDips = 28.0f; +inline constexpr float kBorderlessTitleBarFontSize = 12.0f; + +} // namespace XCEngine::UI::Editor::App::EditorWindowSupport diff --git a/new_editor/app/Platform/Win32/EditorWindowFrame.cpp b/new_editor/app/Platform/Win32/EditorWindowFrame.cpp new file mode 100644 index 00000000..842b64bf --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowFrame.cpp @@ -0,0 +1,235 @@ +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowInputSupport.h" +#include "Platform/Win32/EditorWindowRuntimeSupport.h" +#include "Platform/Win32/EditorWindowStyle.h" + +#include + +#include +#include + +namespace XCEngine::UI::Editor::App { + +using namespace EditorWindowSupport; +using ::XCEngine::UI::UIDrawData; +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +void EditorWindow::RenderFrame( + EditorContext& editorContext, + bool globalTabDragActive) { + if (!m_render.ready || m_window.hwnd == nullptr) { + return; + } + + UINT pixelWidth = 0u; + UINT pixelHeight = 0u; + if (!ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) { + return; + } + + const float width = PixelsToDips(static_cast(pixelWidth)); + const float height = PixelsToDips(static_cast(pixelHeight)); + const UIRect workspaceBounds = ResolveWorkspaceBounds(width, height); + + UIDrawData drawData = {}; + UIDrawList& drawList = drawData.EmplaceDrawList("XCEditorShell"); + drawList.AddFilledRect( + UIRect(0.0f, 0.0f, width, height), + kShellSurfaceColor); + + if (editorContext.IsValid()) { + std::vector frameEvents = std::move(m_input.pendingEvents); + m_input.pendingEvents.clear(); + if (!frameEvents.empty() && IsVerboseRuntimeTraceEnabled()) { + LogRuntimeTrace( + "input", + DescribeInputEvents(frameEvents) + " | " + + editorContext.DescribeWorkspaceState( + m_composition.workspaceController, + m_composition.shellRuntime.GetShellInteractionState())); + } + + const Host::D3D12WindowRenderLoopFrameContext frameContext = + m_render.windowRenderLoop.BeginFrame(); + if (!frameContext.warning.empty()) { + LogRuntimeTrace("viewport", frameContext.warning); + } + + editorContext.AttachTextMeasurer(m_render.renderer); + m_composition.shellRuntime.Update( + editorContext, + m_composition.workspaceController, + workspaceBounds, + frameEvents, + BuildCaptureStatusText(), + m_window.primary + ? EditorShellVariant::Primary + : EditorShellVariant::DetachedWindow); + const UIEditorShellInteractionFrame& shellFrame = + m_composition.shellRuntime.GetShellFrame(); + const UIEditorDockHostInteractionState& dockHostInteractionState = + m_composition.shellRuntime + .GetShellInteractionState() + .workspaceInteractionState + .dockHostInteractionState; + if (IsVerboseRuntimeTraceEnabled() && + (!frameEvents.empty() || + shellFrame.result.workspaceResult.dockHostResult.layoutChanged || + shellFrame.result.workspaceResult.dockHostResult.commandExecuted)) { + std::ostringstream frameTrace = {}; + frameTrace << "result consumed=" + << (shellFrame.result.consumed ? "true" : "false") + << " layoutChanged=" + << (shellFrame.result.workspaceResult.dockHostResult.layoutChanged ? "true" : "false") + << " commandExecuted=" + << (shellFrame.result.workspaceResult.dockHostResult.commandExecuted ? "true" : "false") + << " active=" + << m_composition.workspaceController.GetWorkspace().activePanelId + << " message=" + << shellFrame.result.workspaceResult.dockHostResult.layoutResult.message; + LogRuntimeTrace("frame", frameTrace.str()); + } + + if (!globalTabDragActive && + !dockHostInteractionState.activeTabDragNodeId.empty() && + !dockHostInteractionState.activeTabDragPanelId.empty()) { + QueuePendingTabDragStart( + dockHostInteractionState.activeTabDragNodeId, + dockHostInteractionState.activeTabDragPanelId); + } + + if (shellFrame.result.workspaceResult.dockHostResult.detachRequested) { + QueuePendingDetachRequest( + shellFrame.result.workspaceResult.dockHostResult.detachedNodeId, + shellFrame.result.workspaceResult.dockHostResult.detachedPanelId); + } + + ApplyHostCaptureRequests(shellFrame.result); + for (const WorkspaceTraceEntry& entry : m_composition.shellRuntime.GetTraceEntries()) { + LogRuntimeTrace(entry.channel, entry.message); + } + ApplyHostedContentCaptureRequests(); + ApplyCurrentCursor(); + m_composition.shellRuntime.Append(drawList); + if (frameContext.canRenderViewports) { + m_composition.shellRuntime.RenderRequestedViewports(frameContext.renderContext); + } + } else { + drawList.AddText( + UIPoint(28.0f, 28.0f), + "Editor shell asset invalid.", + kShellTextColor, + 16.0f); + drawList.AddText( + UIPoint(28.0f, 54.0f), + editorContext.GetValidationMessage().empty() + ? std::string("Unknown validation error.") + : editorContext.GetValidationMessage(), + kShellMutedTextColor, + 12.0f); + } + + AppendBorderlessWindowChrome(drawList, width); + + const Host::D3D12WindowRenderLoopPresentResult presentResult = + m_render.windowRenderLoop.Present(drawData); + if (!presentResult.warning.empty()) { + LogRuntimeTrace("present", presentResult.warning); + } + + m_render.autoScreenshot.CaptureIfRequested( + m_render.renderer, + drawData, + pixelWidth, + pixelHeight, + presentResult.framePresented); +} + +void EditorWindow::OnPaintMessage( + EditorContext& editorContext, + bool globalTabDragActive) { + if (!m_render.ready || m_window.hwnd == nullptr) { + return; + } + + PAINTSTRUCT paintStruct = {}; + BeginPaint(m_window.hwnd, &paintStruct); + RenderFrame(editorContext, globalTabDragActive); + EndPaint(m_window.hwnd, &paintStruct); +} + +UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const { + if (!IsBorderlessWindowEnabled()) { + return UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips); + } + + const float titleBarHeight = (std::min)(kBorderlessTitleBarHeightDips, clientHeightDips); + return UIRect( + 0.0f, + titleBarHeight, + clientWidthDips, + (std::max)(0.0f, clientHeightDips - titleBarHeight)); +} + +std::string EditorWindow::BuildCaptureStatusText() const { + if (m_render.autoScreenshot.HasPendingCapture()) { + return "Shot pending..."; + } + + if (!m_render.autoScreenshot.GetLastCaptureError().empty()) { + return TruncateText(m_render.autoScreenshot.GetLastCaptureError(), 38u); + } + + if (!m_render.autoScreenshot.GetLastCaptureSummary().empty()) { + return TruncateText(m_render.autoScreenshot.GetLastCaptureSummary(), 38u); + } + + return {}; +} + +void EditorWindow::ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result) { + if (result.requestPointerCapture && GetCapture() != m_window.hwnd) { + SetCapture(m_window.hwnd); + } + if (result.releasePointerCapture && GetCapture() == m_window.hwnd) { + ReleaseCapture(); + } +} + +void EditorWindow::ApplyHostedContentCaptureRequests() { + if (m_composition.shellRuntime.WantsHostPointerCapture() && + GetCapture() != m_window.hwnd) { + SetCapture(m_window.hwnd); + } + + if (m_composition.shellRuntime.WantsHostPointerRelease() && + GetCapture() == m_window.hwnd && + !m_composition.shellRuntime.HasShellInteractiveCapture()) { + ReleaseCapture(); + } +} + +std::string EditorWindow::DescribeInputEvents( + const std::vector& events) const { + std::ostringstream stream = {}; + stream << "events=["; + for (std::size_t index = 0; index < events.size(); ++index) { + if (index > 0u) { + stream << " | "; + } + const UIInputEvent& event = events[index]; + stream << DescribeInputEventType(event) + << '@' + << static_cast(event.position.x) + << ',' + << static_cast(event.position.y); + } + stream << ']'; + return stream.str(); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowInitialization.cpp b/new_editor/app/Platform/Win32/EditorWindowInitialization.cpp new file mode 100644 index 00000000..0a0aeef4 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowInitialization.cpp @@ -0,0 +1,128 @@ +#include "Platform/Win32/EditorWindow.h" + +#include "Bootstrap/EditorResources.h" +#include "Platform/Win32/EditorWindowPlatformSupport.h" +#include "Platform/Win32/EditorWindowRuntimeSupport.h" + +#include +#include + +namespace XCEngine::UI::Editor::App { + +using namespace EditorWindowSupport; + +bool EditorWindow::Initialize( + const std::filesystem::path& repoRoot, + EditorContext& editorContext, + const std::filesystem::path& captureRoot, + bool autoCaptureOnStartup) { + if (m_window.hwnd == nullptr) { + LogRuntimeTrace("app", "window initialize skipped: hwnd is null"); + return false; + } + + Host::RefreshBorderlessWindowDwmDecorations(m_window.hwnd); + m_chrome.runtime.Reset(); + m_chrome.runtime.SetWindowDpi(QueryWindowDpi(m_window.hwnd)); + m_render.renderer.SetDpiScale(GetDpiScale()); + + std::ostringstream dpiTrace = {}; + dpiTrace << "initial dpi=" << m_chrome.runtime.GetWindowDpi() + << " scale=" << GetDpiScale(); + LogRuntimeTrace("window", dpiTrace.str()); + + if (!m_render.renderer.Initialize(m_window.hwnd)) { + LogRuntimeTrace("app", "renderer initialization failed"); + return false; + } + + RECT clientRect = {}; + GetClientRect(m_window.hwnd, &clientRect); + const int clientWidth = (std::max)(clientRect.right - clientRect.left, 1L); + const int clientHeight = (std::max)(clientRect.bottom - clientRect.top, 1L); + if (!m_render.windowRenderer.Initialize(m_window.hwnd, clientWidth, clientHeight)) { + LogRuntimeTrace("app", "d3d12 window renderer initialization failed"); + m_render.renderer.Shutdown(); + return false; + } + + const Host::D3D12WindowRenderLoopAttachResult attachResult = + m_render.windowRenderLoop.Attach(m_render.renderer, m_render.windowRenderer); + if (!attachResult.interopWarning.empty()) { + LogRuntimeTrace("app", attachResult.interopWarning); + } + + editorContext.AttachTextMeasurer(m_render.renderer); + m_composition.shellRuntime.Initialize(repoRoot, m_render.renderer); + m_composition.shellRuntime.AttachViewportWindowRenderer(m_render.windowRenderer); + m_composition.shellRuntime.SetViewportSurfacePresentationEnabled( + attachResult.hasViewportSurfacePresentation); + + std::string titleBarLogoError = {}; + if (!LoadEmbeddedPngTexture( + m_render.renderer, + IDR_PNG_LOGO_ICON, + m_render.titleBarLogoIcon, + titleBarLogoError)) { + LogRuntimeTrace("icons", "titlebar logo_icon.png: " + titleBarLogoError); + } + if (!m_composition.shellRuntime.GetBuiltInIconError().empty()) { + LogRuntimeTrace("icons", m_composition.shellRuntime.GetBuiltInIconError()); + } + + LogRuntimeTrace( + "app", + "shell runtime initialized: " + + editorContext.DescribeWorkspaceState( + m_composition.workspaceController, + m_composition.shellRuntime.GetShellInteractionState())); + m_render.ready = true; + + m_render.autoScreenshot.Initialize(captureRoot); + if (autoCaptureOnStartup && IsAutoCaptureOnStartupEnabled()) { + m_render.autoScreenshot.RequestCapture("startup"); + editorContext.SetStatus("Capture", "Startup capture requested."); + } + + return true; +} + +void EditorWindow::Shutdown() { + if (GetCapture() == m_window.hwnd) { + ReleaseCapture(); + } + + m_render.ready = false; + m_render.autoScreenshot.Shutdown(); + m_composition.shellRuntime.Shutdown(); + m_render.renderer.ReleaseTexture(m_render.titleBarLogoIcon); + m_render.windowRenderLoop.Detach(); + m_render.windowRenderer.Shutdown(); + m_render.renderer.Shutdown(); + m_input.pendingEvents.clear(); + m_chrome.chromeState = {}; + m_chrome.runtime.Reset(); + m_pendingDetachRequest = {}; + m_pendingTabDragStart = {}; +} + +void EditorWindow::ResetInteractionState() { + if (GetCapture() == m_window.hwnd) { + ReleaseCapture(); + } + + m_input.pendingEvents.clear(); + m_input.trackingMouseLeave = false; + m_input.modifierTracker.Reset(); + m_composition.shellRuntime.ResetInteractionState(); + m_chrome.chromeState = {}; + m_chrome.runtime.EndBorderlessResize(); + m_chrome.runtime.EndBorderlessWindowDragRestore(); + m_chrome.runtime.EndInteractiveResize(); + m_chrome.runtime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); + m_chrome.runtime.ClearPredictedClientPixelSize(); + m_pendingDetachRequest = {}; + m_pendingTabDragStart = {}; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowInput.cpp b/new_editor/app/Platform/Win32/EditorWindowInput.cpp new file mode 100644 index 00000000..6de16440 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowInput.cpp @@ -0,0 +1,163 @@ +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowInputSupport.h" + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +using namespace EditorWindowSupport; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPointerButton; + +bool EditorWindow::ApplyCurrentCursor() const { + if (!HasInteractiveCaptureState() && !IsPointerInsideClientArea()) { + return false; + } + + const HCURSOR cursor = LoadCursorW(nullptr, ResolveCurrentCursorResource()); + if (cursor == nullptr) { + return false; + } + + SetCursor(cursor); + return true; +} + + +bool EditorWindow::HasInteractiveCaptureState() const { + return m_composition.shellRuntime.HasInteractiveCapture() || + m_chrome.runtime.IsBorderlessWindowDragRestoreArmed(); +} + +void EditorWindow::QueuePointerEvent( + UIInputEventType type, + UIPointerButton button, + WPARAM wParam, + LPARAM lParam) { + UIInputEvent event = {}; + event.type = type; + event.pointerButton = button; + event.position = ConvertClientPixelsToDips( + GET_X_LPARAM(lParam), + GET_Y_LPARAM(lParam)); + event.modifiers = + m_input.modifierTracker.BuildPointerModifiers(static_cast(wParam)); + m_input.pendingEvents.push_back(event); +} + +void EditorWindow::QueuePointerLeaveEvent() { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerLeave; + if (m_window.hwnd != nullptr) { + POINT clientPoint = {}; + GetCursorPos(&clientPoint); + ScreenToClient(m_window.hwnd, &clientPoint); + event.position = ConvertClientPixelsToDips(clientPoint.x, clientPoint.y); + } + m_input.pendingEvents.push_back(event); +} + +void EditorWindow::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) { + if (m_window.hwnd == nullptr) { + return; + } + + POINT screenPoint = { + GET_X_LPARAM(lParam), + GET_Y_LPARAM(lParam) + }; + ScreenToClient(m_window.hwnd, &screenPoint); + + UIInputEvent event = {}; + event.type = UIInputEventType::PointerWheel; + event.position = ConvertClientPixelsToDips(screenPoint.x, screenPoint.y); + event.wheelDelta = static_cast(wheelDelta); + event.modifiers = + m_input.modifierTracker.BuildPointerModifiers(static_cast(wParam)); + m_input.pendingEvents.push_back(event); +} + +void EditorWindow::QueueKeyEvent(UIInputEventType type, WPARAM wParam, LPARAM lParam) { + UIInputEvent event = {}; + event.type = type; + event.keyCode = MapVirtualKeyToUIKeyCode(wParam); + event.modifiers = m_input.modifierTracker.ApplyKeyMessage(type, wParam, lParam); + event.repeat = IsRepeatKeyMessage(lParam); + m_input.pendingEvents.push_back(event); +} + +void EditorWindow::QueueCharacterEvent(WPARAM wParam, LPARAM) { + UIInputEvent event = {}; + event.type = UIInputEventType::Character; + event.character = static_cast(wParam); + event.modifiers = m_input.modifierTracker.GetCurrentModifiers(); + m_input.pendingEvents.push_back(event); +} + +void EditorWindow::QueueWindowFocusEvent(UIInputEventType type) { + UIInputEvent event = {}; + event.type = type; + m_input.pendingEvents.push_back(event); +} + +void EditorWindow::SyncInputModifiersFromSystemState() { + m_input.modifierTracker.SyncFromSystemState(); +} + +void EditorWindow::ResetInputModifiers() { + m_input.modifierTracker.Reset(); +} + +void EditorWindow::RequestManualScreenshot() { + m_render.autoScreenshot.RequestCapture("manual_f12"); +} + +bool EditorWindow::IsPointerInsideClientArea() const { + if (m_window.hwnd == nullptr || !IsWindow(m_window.hwnd)) { + return false; + } + + POINT screenPoint = {}; + if (!GetCursorPos(&screenPoint)) { + return false; + } + + const LPARAM pointParam = MAKELPARAM( + static_cast(screenPoint.x), + static_cast(screenPoint.y)); + return SendMessageW(m_window.hwnd, WM_NCHITTEST, 0, pointParam) == HTCLIENT; +} + +LPCWSTR EditorWindow::ResolveCurrentCursorResource() const { + const Host::BorderlessWindowResizeEdge borderlessResizeEdge = + m_chrome.runtime.IsBorderlessResizeActive() + ? m_chrome.runtime.GetBorderlessResizeEdge() + : m_chrome.runtime.GetHoveredBorderlessResizeEdge(); + if (borderlessResizeEdge != Host::BorderlessWindowResizeEdge::None) { + return Host::ResolveBorderlessWindowResizeCursor(borderlessResizeEdge); + } + + switch (m_composition.shellRuntime.GetHostedContentCursorKind()) { + case ProjectPanel::CursorKind::ResizeEW: + return IDC_SIZEWE; + case ProjectPanel::CursorKind::Arrow: + default: + break; + } + + switch (m_composition.shellRuntime.GetDockCursorKind()) { + case Widgets::UIEditorDockHostCursorKind::ResizeEW: + return IDC_SIZEWE; + case Widgets::UIEditorDockHostCursorKind::ResizeNS: + return IDC_SIZENS; + case Widgets::UIEditorDockHostCursorKind::Arrow: + default: + return IDC_ARROW; + } +} + +} // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Platform/Win32/EditorWindowInputSupport.h b/new_editor/app/Platform/Win32/EditorWindowInputSupport.h new file mode 100644 index 00000000..9af08853 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowInputSupport.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +namespace XCEngine::UI::Editor::App::EditorWindowSupport { + +inline std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) { + using ::XCEngine::Input::KeyCode; + + switch (wParam) { + case 'A': return static_cast(KeyCode::A); + case 'B': return static_cast(KeyCode::B); + case 'C': return static_cast(KeyCode::C); + case 'D': return static_cast(KeyCode::D); + case 'E': return static_cast(KeyCode::E); + case 'F': return static_cast(KeyCode::F); + case 'G': return static_cast(KeyCode::G); + case 'H': return static_cast(KeyCode::H); + case 'I': return static_cast(KeyCode::I); + case 'J': return static_cast(KeyCode::J); + case 'K': return static_cast(KeyCode::K); + case 'L': return static_cast(KeyCode::L); + case 'M': return static_cast(KeyCode::M); + case 'N': return static_cast(KeyCode::N); + case 'O': return static_cast(KeyCode::O); + case 'P': return static_cast(KeyCode::P); + case 'Q': return static_cast(KeyCode::Q); + case 'R': return static_cast(KeyCode::R); + case 'S': return static_cast(KeyCode::S); + case 'T': return static_cast(KeyCode::T); + case 'U': return static_cast(KeyCode::U); + case 'V': return static_cast(KeyCode::V); + case 'W': return static_cast(KeyCode::W); + case 'X': return static_cast(KeyCode::X); + case 'Y': return static_cast(KeyCode::Y); + case 'Z': return static_cast(KeyCode::Z); + case '0': return static_cast(KeyCode::Zero); + case '1': return static_cast(KeyCode::One); + case '2': return static_cast(KeyCode::Two); + case '3': return static_cast(KeyCode::Three); + case '4': return static_cast(KeyCode::Four); + case '5': return static_cast(KeyCode::Five); + case '6': return static_cast(KeyCode::Six); + case '7': return static_cast(KeyCode::Seven); + case '8': return static_cast(KeyCode::Eight); + case '9': return static_cast(KeyCode::Nine); + case VK_SPACE: return static_cast(KeyCode::Space); + case VK_TAB: return static_cast(KeyCode::Tab); + case VK_RETURN: return static_cast(KeyCode::Enter); + case VK_ESCAPE: return static_cast(KeyCode::Escape); + case VK_SHIFT: return static_cast(KeyCode::LeftShift); + case VK_CONTROL: return static_cast(KeyCode::LeftCtrl); + case VK_MENU: return static_cast(KeyCode::LeftAlt); + case VK_UP: return static_cast(KeyCode::Up); + case VK_DOWN: return static_cast(KeyCode::Down); + case VK_LEFT: return static_cast(KeyCode::Left); + case VK_RIGHT: return static_cast(KeyCode::Right); + case VK_HOME: return static_cast(KeyCode::Home); + case VK_END: return static_cast(KeyCode::End); + case VK_PRIOR: return static_cast(KeyCode::PageUp); + case VK_NEXT: return static_cast(KeyCode::PageDown); + case VK_DELETE: return static_cast(KeyCode::Delete); + case VK_BACK: return static_cast(KeyCode::Backspace); + case VK_F1: return static_cast(KeyCode::F1); + case VK_F2: return static_cast(KeyCode::F2); + case VK_F3: return static_cast(KeyCode::F3); + case VK_F4: return static_cast(KeyCode::F4); + case VK_F5: return static_cast(KeyCode::F5); + case VK_F6: return static_cast(KeyCode::F6); + case VK_F7: return static_cast(KeyCode::F7); + case VK_F8: return static_cast(KeyCode::F8); + case VK_F9: return static_cast(KeyCode::F9); + case VK_F10: return static_cast(KeyCode::F10); + case VK_F11: return static_cast(KeyCode::F11); + case VK_F12: return static_cast(KeyCode::F12); + default: return static_cast(KeyCode::None); + } +} + +inline bool IsRepeatKeyMessage(LPARAM lParam) { + return (static_cast(lParam) & (1ul << 30)) != 0ul; +} + +inline std::string DescribeInputEventType(const ::XCEngine::UI::UIInputEvent& event) { + using ::XCEngine::UI::UIInputEventType; + + switch (event.type) { + case UIInputEventType::PointerMove: return "PointerMove"; + case UIInputEventType::PointerEnter: return "PointerEnter"; + case UIInputEventType::PointerLeave: return "PointerLeave"; + case UIInputEventType::PointerButtonDown: return "PointerDown"; + case UIInputEventType::PointerButtonUp: return "PointerUp"; + case UIInputEventType::PointerWheel: return "PointerWheel"; + case UIInputEventType::KeyDown: return "KeyDown"; + case UIInputEventType::KeyUp: return "KeyUp"; + case UIInputEventType::Character: return "Character"; + case UIInputEventType::FocusGained: return "FocusGained"; + case UIInputEventType::FocusLost: return "FocusLost"; + default: return "Unknown"; + } +} + +} // namespace XCEngine::UI::Editor::App::EditorWindowSupport diff --git a/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp b/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp new file mode 100644 index 00000000..cd0e744f --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp @@ -0,0 +1,173 @@ +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowRuntimeSupport.h" + +namespace XCEngine::UI::Editor::App { + +using namespace EditorWindowSupport; + +EditorWindow::EditorWindow( + std::string windowId, + std::wstring title, + bool primary, + UIEditorWorkspaceController workspaceController) + : m_window{ + nullptr, + std::move(windowId), + std::move(title), + {}, + primary } + , m_composition{ std::move(workspaceController), {} } { + UpdateCachedTitleText(); +} + +std::string_view EditorWindow::GetWindowId() const { + return m_window.windowId; +} + +HWND EditorWindow::GetHwnd() const { + return m_window.hwnd; +} + +bool EditorWindow::HasHwnd() const { + return m_window.hwnd != nullptr; +} + +bool EditorWindow::IsPrimary() const { + return m_window.primary; +} + +bool EditorWindow::IsRenderReady() const { + return m_render.ready; +} + +bool EditorWindow::IsTrackingMouseLeave() const { + return m_input.trackingMouseLeave; +} + +bool EditorWindow::HasHoveredBorderlessResizeEdge() const { + return m_chrome.runtime.GetHoveredBorderlessResizeEdge() != + Host::BorderlessWindowResizeEdge::None; +} + +const std::wstring& EditorWindow::GetTitle() const { + return m_window.title; +} + +const UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() const { + return m_composition.workspaceController; +} + +UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() { + return m_composition.workspaceController; +} + +const EditorShellRuntime& EditorWindow::GetShellRuntime() const { + return m_composition.shellRuntime; +} + +EditorShellRuntime& EditorWindow::GetShellRuntime() { + return m_composition.shellRuntime; +} + +const UIEditorShellInteractionFrame& EditorWindow::GetShellFrame() const { + return m_composition.shellRuntime.GetShellFrame(); +} + +const UIEditorShellInteractionState& EditorWindow::GetShellInteractionState() const { + return m_composition.shellRuntime.GetShellInteractionState(); +} + +void EditorWindow::AttachHwnd(HWND hwnd) { + m_window.hwnd = hwnd; +} + +void EditorWindow::MarkDestroyed() { + m_window.hwnd = nullptr; + m_input.trackingMouseLeave = false; +} + +void EditorWindow::SetTrackingMouseLeave(bool trackingMouseLeave) { + m_input.trackingMouseLeave = trackingMouseLeave; +} + +void EditorWindow::SetTitle(std::wstring title) { + m_window.title = std::move(title); + UpdateCachedTitleText(); +} + +void EditorWindow::ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) { + m_composition.workspaceController = std::move(workspaceController); +} + +std::optional EditorWindow::ConsumePendingTabDragStart() { + if (!m_pendingTabDragStart.pending || + m_pendingTabDragStart.nodeId.empty() || + m_pendingTabDragStart.panelId.empty()) { + return std::nullopt; + } + + EditorWindowPendingTabDragStart pending = {}; + pending.nodeId = std::move(m_pendingTabDragStart.nodeId); + pending.panelId = std::move(m_pendingTabDragStart.panelId); + pending.screenPoint = m_pendingTabDragStart.screenPoint; + + m_pendingTabDragStart = {}; + return pending; +} + +std::optional EditorWindow::ConsumeDetachRequest() { + if (!m_pendingDetachRequest.pending || + m_pendingDetachRequest.nodeId.empty() || + m_pendingDetachRequest.panelId.empty()) { + return std::nullopt; + } + + EditorWindowPendingDetachRequest pending = {}; + pending.nodeId = std::move(m_pendingDetachRequest.nodeId); + pending.panelId = std::move(m_pendingDetachRequest.panelId); + pending.screenPoint = m_pendingDetachRequest.screenPoint; + + m_pendingDetachRequest = {}; + return pending; +} + +void EditorWindow::QueuePendingTabDragStart(std::string_view nodeId, std::string_view panelId) { + POINT screenPoint = {}; + if (!GetCursorPos(&screenPoint)) { + return; + } + + m_pendingTabDragStart.pending = true; + m_pendingTabDragStart.nodeId = std::string(nodeId); + m_pendingTabDragStart.panelId = std::string(panelId); + m_pendingTabDragStart.screenPoint = screenPoint; +} + +void EditorWindow::QueuePendingDetachRequest(std::string_view nodeId, std::string_view panelId) { + POINT screenPoint = {}; + if (!GetCursorPos(&screenPoint)) { + return; + } + + m_pendingDetachRequest.pending = true; + m_pendingDetachRequest.nodeId = std::string(nodeId); + m_pendingDetachRequest.panelId = std::string(panelId); + m_pendingDetachRequest.screenPoint = screenPoint; +} + +void EditorWindow::InvalidateHostWindow() const { + if (m_window.hwnd != nullptr && IsWindow(m_window.hwnd)) { + InvalidateRect(m_window.hwnd, nullptr, FALSE); + } +} + +bool EditorWindow::IsVerboseRuntimeTraceEnabled() { + static const bool s_enabled = ResolveVerboseRuntimeTraceEnabled(); + return s_enabled; +} + +void EditorWindow::UpdateCachedTitleText() { + m_window.titleText = WideToUtf8(m_window.title); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowManager.h b/new_editor/app/Platform/Win32/EditorWindowManager.h new file mode 100644 index 00000000..5e29ece7 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowManager.h @@ -0,0 +1,135 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::App { + +class EditorContext; +class EditorWindow; + +struct EditorWindowHostConfig { + HINSTANCE hInstance = nullptr; + const wchar_t* windowClassName = L""; + DWORD windowStyle = 0; + const wchar_t* primaryWindowTitle = L""; + void* windowUserData = nullptr; +}; + +class EditorWindowManager final { +public: + struct CreateParams { + std::string windowId = {}; + std::wstring title = {}; + int initialX = CW_USEDEFAULT; + int initialY = CW_USEDEFAULT; + int initialWidth = 1540; + int initialHeight = 940; + int showCommand = SW_SHOW; + bool primary = false; + bool autoCaptureOnStartup = false; + }; + + EditorWindowManager( + EditorWindowHostConfig hostConfig, + std::filesystem::path repoRoot, + EditorContext& editorContext); + ~EditorWindowManager(); + + EditorWindowManager(const EditorWindowManager&) = delete; + EditorWindowManager& operator=(const EditorWindowManager&) = delete; + EditorWindowManager(EditorWindowManager&&) = delete; + EditorWindowManager& operator=(EditorWindowManager&&) = delete; + + EditorWindow* CreateEditorWindow( + UIEditorWorkspaceController workspaceController, + const CreateParams& params); + void HandlePendingNativeWindowCreated(HWND hwnd); + void Shutdown(); + + EditorWindow* FindWindow(HWND hwnd); + const EditorWindow* FindWindow(HWND hwnd) const; + EditorWindow* FindWindow(std::string_view windowId); + const EditorWindow* FindWindow(std::string_view windowId) const; + EditorWindow* FindPrimaryWindow(); + const EditorWindow* FindPrimaryWindow() const; + + bool HasWindows() const; + void DestroyClosedWindows(); + void RenderAllWindows(); + + bool IsGlobalTabDragActive() const; + bool OwnsActiveGlobalTabDrag(std::string_view windowId) const; + void EndGlobalTabDragSession(); + void HandleDestroyedWindow(HWND hwnd); + bool HandleGlobalTabDragPointerMove(HWND hwnd); + bool HandleGlobalTabDragPointerButtonUp(HWND hwnd); + + void ProcessPendingGlobalTabDragStarts(); + void ProcessPendingDetachRequests(); + +private: + struct GlobalTabDragSession { + bool active = false; + std::string panelWindowId = {}; + std::string sourceNodeId = {}; + std::string panelId = {}; + POINT screenPoint = {}; + }; + + void DestroyEditorWindow(EditorWindow& window); + UIEditorWindowWorkspaceSet BuildWindowWorkspaceSet( + std::string_view activeWindowId = {}) const; + UIEditorWindowWorkspaceController BuildLiveWindowWorkspaceController( + std::string_view activeWindowId) const; + bool SynchronizeWindowsFromWindowSet( + const UIEditorWindowWorkspaceSet& windowSet, + std::string_view preferredNewWindowId, + const POINT& preferredScreenPoint); + bool SynchronizeWindowsFromController( + const UIEditorWindowWorkspaceController& windowWorkspaceController, + std::string_view preferredNewWindowId, + const POINT& preferredScreenPoint); + UIEditorWorkspaceController BuildWorkspaceControllerForWindow( + const UIEditorWindowWorkspaceState& windowState) const; + std::wstring BuildWindowTitle( + const UIEditorWorkspaceController& workspaceController) const; + RECT BuildDetachedWindowRect(const POINT& screenPoint) const; + void BeginGlobalTabDragSession( + std::string_view panelWindowId, + std::string_view sourceNodeId, + std::string_view panelId, + const POINT& screenPoint); + EditorWindow* FindTopmostWindowAtScreenPoint( + const POINT& screenPoint, + std::string_view excludedWindowId = {}); + const EditorWindow* FindTopmostWindowAtScreenPoint( + const POINT& screenPoint, + std::string_view excludedWindowId = {}) const; + bool TryStartGlobalTabDrag(EditorWindow& sourceWindow); + bool TryProcessDetachRequest(EditorWindow& sourceWindow); + void LogRuntimeTrace(std::string_view channel, std::string_view message) const; + + EditorWindowHostConfig m_hostConfig = {}; + std::filesystem::path m_repoRoot = {}; + EditorContext& m_editorContext; + std::vector> m_windows = {}; + GlobalTabDragSession m_globalTabDragSession = {}; + EditorWindow* m_pendingCreateWindow = nullptr; +}; + +} // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerCrossWindowDrop.cpp b/new_editor/app/Platform/Win32/EditorWindowManagerCrossWindowDrop.cpp new file mode 100644 index 00000000..db260f42 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowManagerCrossWindowDrop.cpp @@ -0,0 +1,241 @@ +#include "EditorWindowManager.h" + +#include "State/EditorContext.h" +#include "EditorWindow.h" + +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +struct CrossWindowDockDropTarget { + bool valid = false; + std::string nodeId = {}; + UIEditorWorkspaceDockPlacement placement = UIEditorWorkspaceDockPlacement::Center; + std::size_t insertionIndex = Widgets::UIEditorTabStripInvalidIndex; +}; + +bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) { + return point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +std::size_t ResolveCrossWindowDropInsertionIndex( + const Widgets::UIEditorDockHostTabStackLayout& tabStack, + const UIPoint& point) { + if (!IsPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { + return Widgets::UIEditorTabStripInvalidIndex; + } + + std::size_t insertionIndex = 0u; + for (const UIRect& rect : tabStack.tabStripLayout.tabHeaderRects) { + const float midpoint = rect.x + rect.width * 0.5f; + if (point.x > midpoint) { + ++insertionIndex; + } + } + return insertionIndex; +} + +UIEditorWorkspaceDockPlacement ResolveCrossWindowDockPlacement( + const Widgets::UIEditorDockHostTabStackLayout& tabStack, + const UIPoint& point) { + if (IsPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { + return UIEditorWorkspaceDockPlacement::Center; + } + + const float leftDistance = point.x - tabStack.bounds.x; + const float rightDistance = tabStack.bounds.x + tabStack.bounds.width - point.x; + const float topDistance = point.y - tabStack.bounds.y; + const float bottomDistance = tabStack.bounds.y + tabStack.bounds.height - point.y; + const float minHorizontalThreshold = tabStack.bounds.width * 0.25f; + const float minVerticalThreshold = tabStack.bounds.height * 0.25f; + const float nearestEdge = + (std::min)((std::min)(leftDistance, rightDistance), (std::min)(topDistance, bottomDistance)); + + if (nearestEdge == leftDistance && leftDistance <= minHorizontalThreshold) { + return UIEditorWorkspaceDockPlacement::Left; + } + if (nearestEdge == rightDistance && rightDistance <= minHorizontalThreshold) { + return UIEditorWorkspaceDockPlacement::Right; + } + if (nearestEdge == topDistance && topDistance <= minVerticalThreshold) { + return UIEditorWorkspaceDockPlacement::Top; + } + if (nearestEdge == bottomDistance && bottomDistance <= minVerticalThreshold) { + return UIEditorWorkspaceDockPlacement::Bottom; + } + + return UIEditorWorkspaceDockPlacement::Center; +} + +bool TryResolveCrossWindowDockDropTarget( + const Widgets::UIEditorDockHostLayout& layout, + const UIPoint& point, + CrossWindowDockDropTarget& outTarget) { + outTarget = {}; + if (!IsPointInsideRect(layout.bounds, point)) { + return false; + } + + const Widgets::UIEditorDockHostHitTarget hitTarget = + Widgets::HitTestUIEditorDockHost(layout, point); + for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + if ((!hitTarget.nodeId.empty() && tabStack.nodeId != hitTarget.nodeId) || + !IsPointInsideRect(tabStack.bounds, point)) { + continue; + } + + outTarget.valid = true; + outTarget.nodeId = tabStack.nodeId; + outTarget.placement = ResolveCrossWindowDockPlacement(tabStack, point); + if (outTarget.placement == UIEditorWorkspaceDockPlacement::Center) { + outTarget.insertionIndex = ResolveCrossWindowDropInsertionIndex(tabStack, point); + if (outTarget.insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { + outTarget.insertionIndex = tabStack.items.size(); + } + } + return true; + } + + for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + if (IsPointInsideRect(tabStack.bounds, point)) { + outTarget.valid = true; + outTarget.nodeId = tabStack.nodeId; + outTarget.placement = ResolveCrossWindowDockPlacement(tabStack, point); + if (outTarget.placement == UIEditorWorkspaceDockPlacement::Center) { + outTarget.insertionIndex = tabStack.items.size(); + } + return true; + } + } + + return false; +} + +} // namespace + +EditorWindow* EditorWindowManager::FindTopmostWindowAtScreenPoint( + const POINT& screenPoint, + std::string_view excludedWindowId) { + if (const HWND hitWindow = WindowFromPoint(screenPoint); hitWindow != nullptr) { + const HWND rootWindow = GetAncestor(hitWindow, GA_ROOT); + if (EditorWindow* window = FindWindow(rootWindow); + window != nullptr && + window->GetWindowId() != excludedWindowId) { + return window; + } + } + + for (auto it = m_windows.rbegin(); it != m_windows.rend(); ++it) { + EditorWindow* const window = it->get(); + if (window == nullptr || + window->GetHwnd() == nullptr || + window->GetWindowId() == excludedWindowId) { + continue; + } + + RECT windowRect = {}; + if (GetWindowRect(window->GetHwnd(), &windowRect) && + screenPoint.x >= windowRect.left && + screenPoint.x < windowRect.right && + screenPoint.y >= windowRect.top && + screenPoint.y < windowRect.bottom) { + return window; + } + } + + return nullptr; +} + +const EditorWindow* EditorWindowManager::FindTopmostWindowAtScreenPoint( + const POINT& screenPoint, + std::string_view excludedWindowId) const { + return const_cast(this)->FindTopmostWindowAtScreenPoint( + screenPoint, + excludedWindowId); +} + +bool EditorWindowManager::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { + if (!m_globalTabDragSession.active) { + return false; + } + + const EditorWindow* ownerWindow = FindWindow(m_globalTabDragSession.panelWindowId); + if (ownerWindow == nullptr || ownerWindow->GetHwnd() != hwnd) { + return false; + } + + POINT screenPoint = m_globalTabDragSession.screenPoint; + GetCursorPos(&screenPoint); + + const std::string panelWindowId = m_globalTabDragSession.panelWindowId; + const std::string sourceNodeId = m_globalTabDragSession.sourceNodeId; + const std::string panelId = m_globalTabDragSession.panelId; + EndGlobalTabDragSession(); + + EditorWindow* targetWindow = FindTopmostWindowAtScreenPoint(screenPoint, panelWindowId); + if (targetWindow == nullptr || targetWindow->GetHwnd() == nullptr) { + return true; + } + + const UIPoint targetPoint = + targetWindow->ConvertScreenPixelsToClientDips(screenPoint); + const Widgets::UIEditorDockHostLayout& targetLayout = + targetWindow->GetShellFrame() + .workspaceInteractionFrame + .dockHostFrame + .layout; + CrossWindowDockDropTarget dropTarget = {}; + if (!TryResolveCrossWindowDockDropTarget(targetLayout, targetPoint, dropTarget)) { + return true; + } + + UIEditorWindowWorkspaceController windowWorkspaceController = + BuildLiveWindowWorkspaceController(targetWindow->GetWindowId()); + const UIEditorWindowWorkspaceOperationResult result = + dropTarget.placement == UIEditorWorkspaceDockPlacement::Center + ? windowWorkspaceController.MovePanelToStack( + panelWindowId, + sourceNodeId, + panelId, + targetWindow->GetWindowId(), + dropTarget.nodeId, + dropTarget.insertionIndex) + : windowWorkspaceController.DockPanelRelative( + panelWindowId, + sourceNodeId, + panelId, + targetWindow->GetWindowId(), + dropTarget.nodeId, + dropTarget.placement); + if (result.status != UIEditorWindowWorkspaceOperationStatus::Changed) { + LogRuntimeTrace("drag", "cross-window drop rejected: " + result.message); + return true; + } + + if (!SynchronizeWindowsFromController( + windowWorkspaceController, + {}, + screenPoint)) { + LogRuntimeTrace("drag", "failed to synchronize windows after cross-window drop"); + return true; + } + + if (targetWindow->GetHwnd() != nullptr) { + SetForegroundWindow(targetWindow->GetHwnd()); + } + LogRuntimeTrace( + "drag", + "committed cross-window drop panel '" + panelId + + "' into window '" + std::string(targetWindow->GetWindowId()) + "'"); + return true; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerDetach.cpp b/new_editor/app/Platform/Win32/EditorWindowManagerDetach.cpp new file mode 100644 index 00000000..8d2a8e6a --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowManagerDetach.cpp @@ -0,0 +1,68 @@ +#include "EditorWindowManager.h" + +#include "State/EditorContext.h" +#include "EditorWindow.h" + +namespace XCEngine::UI::Editor::App { + +void EditorWindowManager::ProcessPendingDetachRequests() { + if (m_globalTabDragSession.active) { + return; + } + + std::vector windowsToProcess = {}; + for (const std::unique_ptr& window : m_windows) { + if (window != nullptr && window->GetHwnd() != nullptr) { + windowsToProcess.push_back(window.get()); + } + } + + for (EditorWindow* window : windowsToProcess) { + if (window != nullptr) { + TryProcessDetachRequest(*window); + } + } +} + +bool EditorWindowManager::TryProcessDetachRequest(EditorWindow& sourceWindow) { + const std::optional pending = + sourceWindow.ConsumeDetachRequest(); + if (!pending.has_value()) { + return false; + } + + const std::string sourceWindowId(sourceWindow.GetWindowId()); + UIEditorWindowWorkspaceController windowWorkspaceController = + BuildLiveWindowWorkspaceController(sourceWindowId); + const UIEditorWindowWorkspaceOperationResult result = + windowWorkspaceController.DetachPanelToNewWindow( + sourceWindowId, + pending->nodeId, + pending->panelId); + if (result.status != UIEditorWindowWorkspaceOperationStatus::Changed) { + LogRuntimeTrace("detach", "detach request rejected: " + result.message); + return false; + } + + if (!SynchronizeWindowsFromController( + windowWorkspaceController, + result.targetWindowId, + pending->screenPoint)) { + LogRuntimeTrace("detach", "failed to synchronize detached window state"); + return false; + } + + if (EditorWindow* detachedWindow = FindWindow(result.targetWindowId); + detachedWindow != nullptr && + detachedWindow->GetHwnd() != nullptr) { + SetForegroundWindow(detachedWindow->GetHwnd()); + } + + LogRuntimeTrace( + "detach", + "detached panel '" + pending->panelId + "' from window '" + sourceWindowId + + "' to window '" + result.targetWindowId + "'"); + return true; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerLifecycle.cpp b/new_editor/app/Platform/Win32/EditorWindowManagerLifecycle.cpp new file mode 100644 index 00000000..a8ffdb28 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowManagerLifecycle.cpp @@ -0,0 +1,245 @@ +#include "EditorWindowManager.h" + +#include "State/EditorContext.h" +#include "Bootstrap/EditorResources.h" +#include "EditorWindow.h" + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +EditorWindowManager::EditorWindowManager( + EditorWindowHostConfig hostConfig, + std::filesystem::path repoRoot, + EditorContext& editorContext) + : m_hostConfig(hostConfig), + m_repoRoot(std::move(repoRoot)), + m_editorContext(editorContext) {} + +EditorWindowManager::~EditorWindowManager() = default; + +EditorWindow* EditorWindowManager::CreateEditorWindow( + UIEditorWorkspaceController workspaceController, + const CreateParams& params) { + auto windowPtr = std::make_unique( + params.windowId, + params.title.empty() ? std::wstring(L"XCEngine Editor") : params.title, + params.primary, + std::move(workspaceController)); + EditorWindow* const rawWindow = windowPtr.get(); + m_windows.push_back(std::move(windowPtr)); + + const auto eraseRawWindow = [this, rawWindow]() { + const auto it = std::find_if( + m_windows.begin(), + m_windows.end(), + [rawWindow](const std::unique_ptr& candidate) { + return candidate.get() == rawWindow; + }); + if (it != m_windows.end()) { + m_windows.erase(it); + } + }; + + m_pendingCreateWindow = rawWindow; + const HWND hwnd = CreateWindowExW( + WS_EX_APPWINDOW, + m_hostConfig.windowClassName, + rawWindow->GetTitle().c_str(), + m_hostConfig.windowStyle, + params.initialX, + params.initialY, + params.initialWidth, + params.initialHeight, + nullptr, + nullptr, + m_hostConfig.hInstance, + m_hostConfig.windowUserData); + m_pendingCreateWindow = nullptr; + if (hwnd == nullptr) { + eraseRawWindow(); + return nullptr; + } + + if (!rawWindow->HasHwnd()) { + rawWindow->AttachHwnd(hwnd); + } + + auto failWindowInitialization = [&](std::string_view message) { + LogRuntimeTrace("window", std::string(message)); + DestroyEditorWindow(*rawWindow); + eraseRawWindow(); + return static_cast(nullptr); + }; + + const HICON bigIcon = static_cast( + LoadImageW( + m_hostConfig.hInstance, + MAKEINTRESOURCEW(IDI_APP_ICON), + IMAGE_ICON, + 0, + 0, + LR_DEFAULTSIZE)); + const HICON smallIcon = static_cast( + LoadImageW( + m_hostConfig.hInstance, + MAKEINTRESOURCEW(IDI_APP_ICON_SMALL), + IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + LR_DEFAULTCOLOR)); + if (bigIcon != nullptr) { + SendMessageW(hwnd, WM_SETICON, ICON_BIG, reinterpret_cast(bigIcon)); + } + if (smallIcon != nullptr) { + SendMessageW(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(smallIcon)); + } + + if (!rawWindow->Initialize( + m_repoRoot, + m_editorContext, + m_editorContext.GetShellAsset().captureRootPath, + params.autoCaptureOnStartup)) { + return failWindowInitialization("managed window initialization failed"); + } + + ShowWindow(hwnd, params.showCommand); + UpdateWindow(hwnd); + return rawWindow; +} + +void EditorWindowManager::HandlePendingNativeWindowCreated(HWND hwnd) { + if (m_pendingCreateWindow != nullptr && !m_pendingCreateWindow->HasHwnd()) { + m_pendingCreateWindow->AttachHwnd(hwnd); + } +} + +void EditorWindowManager::Shutdown() { + for (const std::unique_ptr& window : m_windows) { + if (window != nullptr) { + DestroyEditorWindow(*window); + } + } + m_windows.clear(); + m_pendingCreateWindow = nullptr; + m_globalTabDragSession = {}; +} + +EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) { + if (hwnd == nullptr) { + return nullptr; + } + + for (const std::unique_ptr& window : m_windows) { + if (window != nullptr && window->GetHwnd() == hwnd) { + return window.get(); + } + } + + return nullptr; +} + +const EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) const { + return const_cast(this)->FindWindow(hwnd); +} + +EditorWindow* EditorWindowManager::FindWindow(std::string_view windowId) { + if (windowId.empty()) { + return nullptr; + } + + for (const std::unique_ptr& window : m_windows) { + if (window != nullptr && window->GetWindowId() == windowId) { + return window.get(); + } + } + + return nullptr; +} + +const EditorWindow* EditorWindowManager::FindWindow(std::string_view windowId) const { + return const_cast(this)->FindWindow(windowId); +} + +EditorWindow* EditorWindowManager::FindPrimaryWindow() { + for (const std::unique_ptr& window : m_windows) { + if (window != nullptr && window->IsPrimary()) { + return window.get(); + } + } + + return nullptr; +} + +const EditorWindow* EditorWindowManager::FindPrimaryWindow() const { + return const_cast(this)->FindPrimaryWindow(); +} + +bool EditorWindowManager::HasWindows() const { + return !m_windows.empty(); +} + +void EditorWindowManager::DestroyEditorWindow(EditorWindow& window) { + const HWND hwnd = window.GetHwnd(); + if (GetCapture() == hwnd) { + ReleaseCapture(); + } + + window.Shutdown(); + if (hwnd != nullptr && IsWindow(hwnd)) { + DestroyWindow(hwnd); + } + window.MarkDestroyed(); +} + +void EditorWindowManager::DestroyClosedWindows() { + for (auto it = m_windows.begin(); it != m_windows.end();) { + EditorWindow* const window = it->get(); + if (window == nullptr || window->GetHwnd() != nullptr) { + ++it; + continue; + } + + if (m_pendingCreateWindow == window) { + m_pendingCreateWindow = nullptr; + } + + window->Shutdown(); + it = m_windows.erase(it); + } +} + +void EditorWindowManager::RenderAllWindows() { + for (const std::unique_ptr& window : m_windows) { + if (window == nullptr || window->GetHwnd() == nullptr) { + continue; + } + + window->RenderFrame(m_editorContext, IsGlobalTabDragActive()); + } +} + +void EditorWindowManager::HandleDestroyedWindow(HWND hwnd) { + if (EditorWindow* window = FindWindow(hwnd); window != nullptr) { + window->MarkDestroyed(); + if (window->IsPrimary()) { + for (const std::unique_ptr& otherWindow : m_windows) { + if (otherWindow != nullptr && + otherWindow.get() != window && + otherWindow->GetHwnd() != nullptr) { + PostMessageW(otherWindow->GetHwnd(), WM_CLOSE, 0, 0); + } + } + } + } +} + +void EditorWindowManager::LogRuntimeTrace( + std::string_view channel, + std::string_view message) const { + AppendUIEditorRuntimeTrace(channel, message); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerTabDrag.cpp b/new_editor/app/Platform/Win32/EditorWindowManagerTabDrag.cpp new file mode 100644 index 00000000..bd8f9b42 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowManagerTabDrag.cpp @@ -0,0 +1,145 @@ +#include "EditorWindowManager.h" + +#include "State/EditorContext.h" +#include "EditorWindow.h" + +namespace XCEngine::UI::Editor::App { + +bool EditorWindowManager::IsGlobalTabDragActive() const { + return m_globalTabDragSession.active; +} + +bool EditorWindowManager::OwnsActiveGlobalTabDrag(std::string_view windowId) const { + return m_globalTabDragSession.active && + m_globalTabDragSession.panelWindowId == windowId; +} + +void EditorWindowManager::BeginGlobalTabDragSession( + std::string_view panelWindowId, + std::string_view sourceNodeId, + std::string_view panelId, + const POINT& screenPoint) { + m_globalTabDragSession.active = true; + m_globalTabDragSession.panelWindowId = std::string(panelWindowId); + m_globalTabDragSession.sourceNodeId = std::string(sourceNodeId); + m_globalTabDragSession.panelId = std::string(panelId); + m_globalTabDragSession.screenPoint = screenPoint; +} + +void EditorWindowManager::EndGlobalTabDragSession() { + if (!m_globalTabDragSession.active) { + return; + } + + if (EditorWindow* ownerWindow = FindWindow(m_globalTabDragSession.panelWindowId); + ownerWindow != nullptr) { + if (GetCapture() == ownerWindow->GetHwnd()) { + ReleaseCapture(); + } + ownerWindow->ResetInteractionState(); + } + + m_globalTabDragSession = {}; +} + +bool EditorWindowManager::HandleGlobalTabDragPointerMove(HWND hwnd) { + if (!m_globalTabDragSession.active) { + return false; + } + + const EditorWindow* ownerWindow = FindWindow(m_globalTabDragSession.panelWindowId); + if (ownerWindow == nullptr || ownerWindow->GetHwnd() != hwnd) { + return false; + } + + POINT screenPoint = {}; + if (GetCursorPos(&screenPoint)) { + m_globalTabDragSession.screenPoint = screenPoint; + } + return true; +} + +void EditorWindowManager::ProcessPendingGlobalTabDragStarts() { + if (m_globalTabDragSession.active) { + return; + } + + for (const std::unique_ptr& window : m_windows) { + if (window == nullptr || window->GetHwnd() == nullptr) { + continue; + } + + if (TryStartGlobalTabDrag(*window)) { + return; + } + } +} + +bool EditorWindowManager::TryStartGlobalTabDrag(EditorWindow& sourceWindow) { + const std::optional pending = + sourceWindow.ConsumePendingTabDragStart(); + if (!pending.has_value()) { + return false; + } + + if (sourceWindow.IsPrimary()) { + UIEditorWindowWorkspaceController windowWorkspaceController = + BuildLiveWindowWorkspaceController(sourceWindow.GetWindowId()); + const UIEditorWindowWorkspaceOperationResult result = + windowWorkspaceController.DetachPanelToNewWindow( + sourceWindow.GetWindowId(), + pending->nodeId, + pending->panelId); + if (result.status != UIEditorWindowWorkspaceOperationStatus::Changed) { + LogRuntimeTrace( + "drag", + "failed to start global tab drag from primary window: " + result.message); + return false; + } + + if (!SynchronizeWindowsFromController( + windowWorkspaceController, + result.targetWindowId, + pending->screenPoint)) { + LogRuntimeTrace("drag", "failed to synchronize detached drag window state"); + return false; + } + + EditorWindow* detachedWindow = FindWindow(result.targetWindowId); + if (detachedWindow == nullptr || detachedWindow->GetHwnd() == nullptr) { + LogRuntimeTrace("drag", "detached drag window was not created."); + return false; + } + + BeginGlobalTabDragSession( + detachedWindow->GetWindowId(), + detachedWindow->GetWorkspaceController().GetWorkspace().root.nodeId, + pending->panelId, + pending->screenPoint); + SetCapture(detachedWindow->GetHwnd()); + SetForegroundWindow(detachedWindow->GetHwnd()); + LogRuntimeTrace( + "drag", + "started global tab drag by detaching panel '" + pending->panelId + + "' into window '" + std::string(detachedWindow->GetWindowId()) + "'"); + return true; + } + + sourceWindow.ResetInteractionState(); + BeginGlobalTabDragSession( + sourceWindow.GetWindowId(), + pending->nodeId, + pending->panelId, + pending->screenPoint); + if (sourceWindow.GetHwnd() != nullptr) { + SetCapture(sourceWindow.GetHwnd()); + } + LogRuntimeTrace( + "drag", + "started global tab drag from detached window '" + + std::string(sourceWindow.GetWindowId()) + + "' panel '" + pending->panelId + "'"); + return true; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerWindowSet.cpp b/new_editor/app/Platform/Win32/EditorWindowManagerWindowSet.cpp new file mode 100644 index 00000000..adb2da26 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowManagerWindowSet.cpp @@ -0,0 +1,171 @@ +#include "EditorWindowManager.h" + +#include "State/EditorContext.h" +#include "EditorWindow.h" + +#include + +namespace XCEngine::UI::Editor::App { + +UIEditorWindowWorkspaceSet EditorWindowManager::BuildWindowWorkspaceSet( + std::string_view activeWindowId) const { + UIEditorWindowWorkspaceSet windowSet = {}; + if (const EditorWindow* primaryWindow = FindPrimaryWindow(); + primaryWindow != nullptr) { + windowSet.primaryWindowId = std::string(primaryWindow->GetWindowId()); + } + + for (const std::unique_ptr& window : m_windows) { + if (window == nullptr || window->GetHwnd() == nullptr) { + continue; + } + + UIEditorWindowWorkspaceState entry = {}; + entry.windowId = std::string(window->GetWindowId()); + entry.workspace = window->GetWorkspaceController().GetWorkspace(); + entry.session = window->GetWorkspaceController().GetSession(); + windowSet.windows.push_back(std::move(entry)); + } + + windowSet.activeWindowId = + !activeWindowId.empty() && FindWindow(activeWindowId) != nullptr + ? std::string(activeWindowId) + : windowSet.primaryWindowId; + + return windowSet; +} + +UIEditorWindowWorkspaceController +EditorWindowManager::BuildLiveWindowWorkspaceController( + std::string_view activeWindowId) const { + return UIEditorWindowWorkspaceController( + m_editorContext.GetShellAsset().panelRegistry, + BuildWindowWorkspaceSet(activeWindowId)); +} + +UIEditorWorkspaceController EditorWindowManager::BuildWorkspaceControllerForWindow( + const UIEditorWindowWorkspaceState& windowState) const { + return UIEditorWorkspaceController( + m_editorContext.GetShellAsset().panelRegistry, + windowState.workspace, + windowState.session); +} + +std::wstring EditorWindowManager::BuildWindowTitle( + const UIEditorWorkspaceController& workspaceController) const { + const std::string& activePanelId = workspaceController.GetWorkspace().activePanelId; + if (const UIEditorPanelDescriptor* descriptor = + FindUIEditorPanelDescriptor( + workspaceController.GetPanelRegistry(), + activePanelId); + descriptor != nullptr && + !descriptor->defaultTitle.empty()) { + const std::string titleText = descriptor->defaultTitle + " - XCEngine Editor"; + return std::wstring(titleText.begin(), titleText.end()); + } + + return std::wstring(L"XCEngine Editor"); +} + +RECT EditorWindowManager::BuildDetachedWindowRect(const POINT& screenPoint) const { + RECT rect = { + screenPoint.x - 420, + screenPoint.y - 24, + screenPoint.x - 420 + 960, + screenPoint.y - 24 + 720 + }; + + const HMONITOR monitor = MonitorFromPoint(screenPoint, MONITOR_DEFAULTTONEAREST); + MONITORINFO monitorInfo = {}; + monitorInfo.cbSize = sizeof(monitorInfo); + if (monitor != nullptr && GetMonitorInfoW(monitor, &monitorInfo)) { + const RECT& workArea = monitorInfo.rcWork; + const LONG width = rect.right - rect.left; + const LONG height = rect.bottom - rect.top; + rect.left = (std::max)(workArea.left, (std::min)(rect.left, workArea.right - width)); + rect.top = (std::max)(workArea.top, (std::min)(rect.top, workArea.bottom - height)); + rect.right = rect.left + width; + rect.bottom = rect.top + height; + } + + return rect; +} + +bool EditorWindowManager::SynchronizeWindowsFromWindowSet( + const UIEditorWindowWorkspaceSet& windowSet, + std::string_view preferredNewWindowId, + const POINT& preferredScreenPoint) { + std::vector windowIdsInSet = {}; + windowIdsInSet.reserve(windowSet.windows.size()); + + for (const UIEditorWindowWorkspaceState& entry : windowSet.windows) { + windowIdsInSet.push_back(entry.windowId); + if (EditorWindow* existingWindow = FindWindow(entry.windowId); + existingWindow != nullptr) { + existingWindow->ReplaceWorkspaceController(BuildWorkspaceControllerForWindow(entry)); + existingWindow->ResetInteractionState(); + if (!existingWindow->IsPrimary()) { + existingWindow->SetTitle( + BuildWindowTitle(existingWindow->GetWorkspaceController())); + if (existingWindow->GetHwnd() != nullptr) { + SetWindowTextW(existingWindow->GetHwnd(), existingWindow->GetTitle().c_str()); + } + } + continue; + } + + CreateParams createParams = {}; + createParams.windowId = entry.windowId; + createParams.primary = entry.windowId == windowSet.primaryWindowId; + createParams.title = + createParams.primary + ? std::wstring( + m_hostConfig.primaryWindowTitle != nullptr && + m_hostConfig.primaryWindowTitle[0] != L'\0' + ? m_hostConfig.primaryWindowTitle + : L"XCEngine Editor") + : BuildWindowTitle(BuildWorkspaceControllerForWindow(entry)); + if (entry.windowId == preferredNewWindowId) { + const RECT detachedRect = BuildDetachedWindowRect(preferredScreenPoint); + createParams.initialX = detachedRect.left; + createParams.initialY = detachedRect.top; + createParams.initialWidth = detachedRect.right - detachedRect.left; + createParams.initialHeight = detachedRect.bottom - detachedRect.top; + } + + if (CreateEditorWindow(BuildWorkspaceControllerForWindow(entry), createParams) == nullptr) { + return false; + } + } + + for (const std::unique_ptr& window : m_windows) { + if (window == nullptr || + window->GetHwnd() == nullptr || + window->IsPrimary()) { + continue; + } + + const bool existsInWindowSet = + std::find( + windowIdsInSet.begin(), + windowIdsInSet.end(), + window->GetWindowId()) != windowIdsInSet.end(); + if (!existsInWindowSet) { + PostMessageW(window->GetHwnd(), WM_CLOSE, 0, 0); + } + } + + return true; +} + +bool EditorWindowManager::SynchronizeWindowsFromController( + const UIEditorWindowWorkspaceController& windowWorkspaceController, + std::string_view preferredNewWindowId, + const POINT& preferredScreenPoint) { + return SynchronizeWindowsFromWindowSet( + windowWorkspaceController.GetWindowSet(), + preferredNewWindowId, + preferredScreenPoint); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowMetrics.cpp b/new_editor/app/Platform/Win32/EditorWindowMetrics.cpp new file mode 100644 index 00000000..b391a57c --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowMetrics.cpp @@ -0,0 +1,95 @@ +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowRuntimeSupport.h" + +#include + +namespace XCEngine::UI::Editor::App { + +using namespace EditorWindowSupport; +using ::XCEngine::UI::UIPoint; + +bool EditorWindow::ApplyWindowResize(UINT width, UINT height) { + if (!m_render.ready || width == 0u || height == 0u) { + return false; + } + + const Host::D3D12WindowRenderLoopResizeResult resizeResult = + m_render.windowRenderLoop.ApplyResize(width, height); + m_composition.shellRuntime.SetViewportSurfacePresentationEnabled( + resizeResult.hasViewportSurfacePresentation); + + if (!resizeResult.windowRendererWarning.empty()) { + LogRuntimeTrace("present", resizeResult.windowRendererWarning); + } + + if (!resizeResult.interopWarning.empty()) { + LogRuntimeTrace("present", resizeResult.interopWarning); + } + + return resizeResult.hasViewportSurfacePresentation; +} + +bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const { + outWidth = 0u; + outHeight = 0u; + if (m_window.hwnd == nullptr || !IsWindow(m_window.hwnd)) { + return false; + } + + RECT clientRect = {}; + if (!GetClientRect(m_window.hwnd, &clientRect)) { + return false; + } + + const LONG width = clientRect.right - clientRect.left; + const LONG height = clientRect.bottom - clientRect.top; + if (width <= 0 || height <= 0) { + return false; + } + + outWidth = static_cast(width); + outHeight = static_cast(height); + return true; +} + +bool EditorWindow::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const { + if (m_chrome.runtime.TryGetPredictedClientPixelSize(outWidth, outHeight)) { + return true; + } + + return QueryCurrentClientPixelSize(outWidth, outHeight); +} + +float EditorWindow::GetDpiScale() const { + return m_chrome.runtime.GetDpiScale(kBaseDpiScale); +} + +float EditorWindow::PixelsToDips(float pixels) const { + const float dpiScale = GetDpiScale(); + return dpiScale > 0.0f ? pixels / dpiScale : pixels; +} + +UIPoint EditorWindow::ConvertClientPixelsToDips(LONG x, LONG y) const { + return UIPoint( + PixelsToDips(static_cast(x)), + PixelsToDips(static_cast(y))); +} + +UIPoint EditorWindow::ConvertScreenPixelsToClientDips(const POINT& screenPoint) const { + POINT clientPoint = screenPoint; + if (m_window.hwnd != nullptr) { + ScreenToClient(m_window.hwnd, &clientPoint); + } + + const float dpiScale = m_chrome.runtime.GetDpiScale(kBaseDpiScale); + return UIPoint( + dpiScale > 0.0f + ? static_cast(clientPoint.x) / dpiScale + : static_cast(clientPoint.x), + dpiScale > 0.0f + ? static_cast(clientPoint.y) / dpiScale + : static_cast(clientPoint.y)); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowPlatformSupport.h b/new_editor/app/Platform/Win32/EditorWindowPlatformSupport.h new file mode 100644 index 00000000..9db61934 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowPlatformSupport.h @@ -0,0 +1,42 @@ +#pragma once + +#include "Platform/Win32/EditorWindowConstants.h" +#include "Support/EmbeddedPngLoader.h" + +#include + +namespace XCEngine::UI::Editor::App::EditorWindowSupport { + +using Support::LoadEmbeddedPngTexture; + +inline UINT QuerySystemDpi() { + HDC screenDc = GetDC(nullptr); + if (screenDc == nullptr) { + return kDefaultDpi; + } + + const int dpiX = GetDeviceCaps(screenDc, LOGPIXELSX); + ReleaseDC(nullptr, screenDc); + return dpiX > 0 ? static_cast(dpiX) : kDefaultDpi; +} + +inline UINT QueryWindowDpi(HWND hwnd) { + if (hwnd != nullptr) { + const HMODULE user32 = GetModuleHandleW(L"user32.dll"); + if (user32 != nullptr) { + using GetDpiForWindowFn = UINT(WINAPI*)(HWND); + const auto getDpiForWindow = + reinterpret_cast(GetProcAddress(user32, "GetDpiForWindow")); + if (getDpiForWindow != nullptr) { + const UINT dpi = getDpiForWindow(hwnd); + if (dpi != 0u) { + return dpi; + } + } + } + } + + return QuerySystemDpi(); +} + +} // namespace XCEngine::UI::Editor::App::EditorWindowSupport diff --git a/new_editor/app/Platform/Win32/EditorWindowResizeLifecycle.cpp b/new_editor/app/Platform/Win32/EditorWindowResizeLifecycle.cpp new file mode 100644 index 00000000..448af12f --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowResizeLifecycle.cpp @@ -0,0 +1,73 @@ +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowRuntimeSupport.h" + +#include + +namespace XCEngine::UI::Editor::App { + +using namespace EditorWindowSupport; + +void EditorWindow::OnResize(UINT width, UINT height) { + bool matchesPredictedClientSize = false; + UINT predictedWidth = 0u; + UINT predictedHeight = 0u; + if (m_chrome.runtime.TryGetPredictedClientPixelSize(predictedWidth, predictedHeight)) { + matchesPredictedClientSize = + predictedWidth == width && + predictedHeight == height; + } + + m_chrome.runtime.ClearPredictedClientPixelSize(); + if (IsBorderlessWindowEnabled() && m_window.hwnd != nullptr) { + Host::RefreshBorderlessWindowDwmDecorations(m_window.hwnd); + } + + if (!matchesPredictedClientSize) { + ApplyWindowResize(width, height); + } +} + +void EditorWindow::OnEnterSizeMove() { + m_chrome.runtime.BeginInteractiveResize(); +} + +void EditorWindow::OnExitSizeMove() { + m_chrome.runtime.EndInteractiveResize(); + m_chrome.runtime.ClearPredictedClientPixelSize(); + UINT width = 0u; + UINT height = 0u; + if (QueryCurrentClientPixelSize(width, height)) { + ApplyWindowResize(width, height); + } +} + +void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { + m_chrome.runtime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); + m_render.renderer.SetDpiScale(GetDpiScale()); + if (m_window.hwnd != nullptr) { + const LONG windowWidth = suggestedRect.right - suggestedRect.left; + const LONG windowHeight = suggestedRect.bottom - suggestedRect.top; + SetWindowPos( + m_window.hwnd, + nullptr, + suggestedRect.left, + suggestedRect.top, + windowWidth, + windowHeight, + SWP_NOZORDER | SWP_NOACTIVATE); + UINT clientWidth = 0u; + UINT clientHeight = 0u; + if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) { + ApplyWindowResize(clientWidth, clientHeight); + } + Host::RefreshBorderlessWindowDwmDecorations(m_window.hwnd); + } + + std::ostringstream trace = {}; + trace << "dpi changed to " << m_chrome.runtime.GetWindowDpi() + << " scale=" << GetDpiScale(); + LogRuntimeTrace("window", trace.str()); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeSupport.h b/new_editor/app/Platform/Win32/EditorWindowRuntimeSupport.h new file mode 100644 index 00000000..d79e8eb1 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeSupport.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Support/EnvironmentFlags.h" +#include "Support/StringEncoding.h" +#include "Support/TextFormat.h" + +#include + +#include +#include + +#include + +namespace XCEngine::UI::Editor::App::EditorWindowSupport { + +inline bool ResolveVerboseRuntimeTraceEnabled() { + wchar_t buffer[8] = {}; + const DWORD length = GetEnvironmentVariableW( + L"XCUIEDITOR_VERBOSE_TRACE", + buffer, + static_cast(std::size(buffer))); + return length > 0u && buffer[0] != L'0'; +} + +inline void LogRuntimeTrace(std::string_view channel, std::string_view message) { + AppendUIEditorRuntimeTrace(channel, message); +} + +inline bool IsAutoCaptureOnStartupEnabled() { + return Support::IsEnvironmentFlagEnabled("XCUI_AUTO_CAPTURE_ON_STARTUP"); +} + +using Support::TruncateText; +using Support::WideToUtf8; + +} // namespace XCEngine::UI::Editor::App::EditorWindowSupport diff --git a/new_editor/app/Platform/Win32/EditorWindowState.h b/new_editor/app/Platform/Win32/EditorWindowState.h new file mode 100644 index 00000000..91478f34 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowState.h @@ -0,0 +1,68 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "Composition/EditorShellRuntime.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +namespace XCEngine::UI::Editor::App { + +struct EditorWindowWindowState { + HWND hwnd = nullptr; + std::string windowId = {}; + std::wstring title = {}; + std::string titleText = {}; + bool primary = false; +}; + +struct EditorWindowRenderState { + Host::NativeRenderer renderer = {}; + Host::D3D12WindowRenderer windowRenderer = {}; + Host::D3D12WindowRenderLoop windowRenderLoop = {}; + Host::AutoScreenshotController autoScreenshot = {}; + ::XCEngine::UI::UITextureHandle titleBarLogoIcon = {}; + bool ready = false; +}; + +struct EditorWindowInputState { + Host::InputModifierTracker modifierTracker = {}; + std::vector<::XCEngine::UI::UIInputEvent> pendingEvents = {}; + bool trackingMouseLeave = false; +}; + +struct EditorWindowCompositionState { + UIEditorWorkspaceController workspaceController = {}; + EditorShellRuntime shellRuntime = {}; +}; + +struct EditorWindowChromeRuntimeState { + Host::BorderlessWindowChromeState chromeState = {}; + Host::HostRuntimeState runtime = {}; +}; + +struct EditorWindowPanelTransferState { + bool pending = false; + std::string nodeId = {}; + std::string panelId = {}; + POINT screenPoint = {}; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowStyle.h b/new_editor/app/Platform/Win32/EditorWindowStyle.h new file mode 100644 index 00000000..34a35601 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowStyle.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace XCEngine::UI::Editor::App::EditorWindowSupport { + +inline const ::XCEngine::UI::UIColor kShellSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); +inline const ::XCEngine::UI::UIColor kShellBorderColor(0.15f, 0.15f, 0.15f, 1.0f); +inline const ::XCEngine::UI::UIColor kShellTextColor(0.92f, 0.92f, 0.92f, 1.0f); +inline const ::XCEngine::UI::UIColor kShellMutedTextColor(0.70f, 0.70f, 0.70f, 1.0f); + +} // namespace XCEngine::UI::Editor::App::EditorWindowSupport diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBar.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBar.cpp new file mode 100644 index 00000000..c0e1fa46 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowTitleBar.cpp @@ -0,0 +1,8 @@ +#include "Platform/Win32/EditorWindow.h" + +namespace XCEngine::UI::Editor::App { + +// Intentionally kept as the translation unit that matches the public title-bar +// entrypoint name; implementation now lives in focused interaction/rendering files. + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp new file mode 100644 index 00000000..fcfe6b8f --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp @@ -0,0 +1,261 @@ +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowConstants.h" + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +using namespace EditorWindowSupport; + +bool EditorWindow::UpdateBorderlessWindowChromeHover(LPARAM lParam) { + if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() != + Host::BorderlessWindowResizeEdge::None || + m_chrome.runtime.IsBorderlessResizeActive()) { + const bool changed = + m_chrome.chromeState.hoveredTarget != + Host::BorderlessWindowChromeHitTarget::None; + m_chrome.chromeState.hoveredTarget = + Host::BorderlessWindowChromeHitTarget::None; + return changed; + } + + const Host::BorderlessWindowChromeHitTarget hitTarget = + HitTestBorderlessWindowChrome(lParam); + const Host::BorderlessWindowChromeHitTarget buttonTarget = + hitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || + hitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || + hitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton + ? hitTarget + : Host::BorderlessWindowChromeHitTarget::None; + if (m_chrome.chromeState.hoveredTarget == buttonTarget) { + return false; + } + + m_chrome.chromeState.hoveredTarget = buttonTarget; + return true; +} + +bool EditorWindow::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) { + if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() != + Host::BorderlessWindowResizeEdge::None || + m_chrome.runtime.IsBorderlessResizeActive()) { + return false; + } + + const Host::BorderlessWindowChromeHitTarget hitTarget = + HitTestBorderlessWindowChrome(lParam); + switch (hitTarget) { + case Host::BorderlessWindowChromeHitTarget::MinimizeButton: + case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: + case Host::BorderlessWindowChromeHitTarget::CloseButton: + m_chrome.chromeState.pressedTarget = hitTarget; + if (m_window.hwnd != nullptr) { + SetCapture(m_window.hwnd); + } + InvalidateHostWindow(); + return true; + case Host::BorderlessWindowChromeHitTarget::DragRegion: + if (m_window.hwnd != nullptr) { + if (IsBorderlessWindowMaximized()) { + POINT screenPoint = {}; + if (GetCursorPos(&screenPoint)) { + m_chrome.runtime.BeginBorderlessWindowDragRestore(screenPoint); + SetCapture(m_window.hwnd); + return true; + } + } + + ReleaseCapture(); + SendMessageW(m_window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + return true; + case Host::BorderlessWindowChromeHitTarget::None: + default: + return false; + } +} + +bool EditorWindow::HandleBorderlessWindowChromeButtonUp( + EditorContext& editorContext, + bool globalTabDragActive, + LPARAM lParam) { + if (m_chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { + ClearBorderlessWindowChromeDragRestoreState(); + return true; + } + + const Host::BorderlessWindowChromeHitTarget pressedTarget = + m_chrome.chromeState.pressedTarget; + if (pressedTarget != Host::BorderlessWindowChromeHitTarget::MinimizeButton && + pressedTarget != Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton && + pressedTarget != Host::BorderlessWindowChromeHitTarget::CloseButton) { + return false; + } + + const Host::BorderlessWindowChromeHitTarget releasedTarget = + HitTestBorderlessWindowChrome(lParam); + m_chrome.chromeState.pressedTarget = + Host::BorderlessWindowChromeHitTarget::None; + if (GetCapture() == m_window.hwnd) { + ReleaseCapture(); + } + InvalidateHostWindow(); + + if (pressedTarget == releasedTarget) { + ExecuteBorderlessWindowChromeAction( + editorContext, + globalTabDragActive, + pressedTarget); + } + return true; +} + +bool EditorWindow::HandleBorderlessWindowChromeDoubleClick( + EditorContext& editorContext, + bool globalTabDragActive, + LPARAM lParam) { + if (m_chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { + ClearBorderlessWindowChromeDragRestoreState(); + } + + if (HitTestBorderlessWindowChrome(lParam) != + Host::BorderlessWindowChromeHitTarget::DragRegion) { + return false; + } + + ExecuteBorderlessWindowChromeAction( + editorContext, + globalTabDragActive, + Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton); + return true; +} + +bool EditorWindow::HandleBorderlessWindowChromeDragRestorePointerMove( + EditorContext& editorContext, + bool globalTabDragActive) { + if (!m_chrome.runtime.IsBorderlessWindowDragRestoreArmed() || m_window.hwnd == nullptr) { + return false; + } + + POINT currentScreenPoint = {}; + if (!GetCursorPos(¤tScreenPoint)) { + return true; + } + + const POINT initialScreenPoint = + m_chrome.runtime.GetBorderlessWindowDragRestoreInitialScreenPoint(); + const int dragThresholdX = (std::max)(GetSystemMetrics(SM_CXDRAG), 1); + const int dragThresholdY = (std::max)(GetSystemMetrics(SM_CYDRAG), 1); + const LONG deltaX = currentScreenPoint.x - initialScreenPoint.x; + const LONG deltaY = currentScreenPoint.y - initialScreenPoint.y; + if (std::abs(deltaX) < dragThresholdX && + std::abs(deltaY) < dragThresholdY) { + return true; + } + + RECT restoreRect = {}; + RECT currentRect = {}; + RECT workAreaRect = {}; + if (!m_chrome.runtime.TryGetBorderlessWindowRestoreRect(restoreRect) || + !QueryCurrentWindowRect(currentRect) || + !QueryBorderlessWindowWorkAreaRect(workAreaRect)) { + ClearBorderlessWindowChromeDragRestoreState(); + return true; + } + + const int restoreWidth = restoreRect.right - restoreRect.left; + const int restoreHeight = restoreRect.bottom - restoreRect.top; + const int currentWidth = currentRect.right - currentRect.left; + if (restoreWidth <= 0 || restoreHeight <= 0 || currentWidth <= 0) { + ClearBorderlessWindowChromeDragRestoreState(); + return true; + } + + const float pointerRatio = + static_cast(currentScreenPoint.x - currentRect.left) / + static_cast(currentWidth); + const float clampedPointerRatio = (std::clamp)(pointerRatio, 0.0f, 1.0f); + const int newLeft = + (std::clamp)( + currentScreenPoint.x - + static_cast(clampedPointerRatio * static_cast(restoreWidth)), + workAreaRect.left, + workAreaRect.right - restoreWidth); + const int titleBarHeightPixels = + static_cast(kBorderlessTitleBarHeightDips * GetDpiScale()); + const int newTop = + (std::clamp)( + currentScreenPoint.y - (std::max)(titleBarHeightPixels / 2, 1), + workAreaRect.top, + workAreaRect.bottom - restoreHeight); + const RECT targetRect = { + newLeft, + newTop, + newLeft + restoreWidth, + newTop + restoreHeight + }; + + m_chrome.runtime.SetBorderlessWindowMaximized(false); + ApplyPredictedWindowRectTransition( + editorContext, + globalTabDragActive, + targetRect); + ClearBorderlessWindowChromeDragRestoreState(); + ReleaseCapture(); + SendMessageW(m_window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); + return true; +} + +void EditorWindow::ClearBorderlessWindowChromeDragRestoreState() { + if (!m_chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { + return; + } + + m_chrome.runtime.EndBorderlessWindowDragRestore(); + if (GetCapture() == m_window.hwnd) { + ReleaseCapture(); + } +} + +void EditorWindow::ClearBorderlessWindowChromeState() { + if (m_chrome.chromeState.hoveredTarget == + Host::BorderlessWindowChromeHitTarget::None && + m_chrome.chromeState.pressedTarget == + Host::BorderlessWindowChromeHitTarget::None) { + return; + } + + m_chrome.chromeState = {}; + InvalidateHostWindow(); +} + +void EditorWindow::ExecuteBorderlessWindowChromeAction( + EditorContext& editorContext, + bool globalTabDragActive, + Host::BorderlessWindowChromeHitTarget target) { + if (m_window.hwnd == nullptr) { + return; + } + + switch (target) { + case Host::BorderlessWindowChromeHitTarget::MinimizeButton: + ShowWindow(m_window.hwnd, SW_MINIMIZE); + break; + case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: + ToggleBorderlessWindowMaximizeRestore(editorContext, globalTabDragActive); + break; + case Host::BorderlessWindowChromeHitTarget::CloseButton: + PostMessageW(m_window.hwnd, WM_CLOSE, 0, 0); + break; + case Host::BorderlessWindowChromeHitTarget::DragRegion: + case Host::BorderlessWindowChromeHitTarget::None: + default: + break; + } + + InvalidateHostWindow(); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp new file mode 100644 index 00000000..e8f3fb98 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp @@ -0,0 +1,96 @@ +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowStyle.h" + +#include + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +using namespace EditorWindowSupport; +using ::XCEngine::UI::UIColor; +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +Host::BorderlessWindowChromeHitTarget EditorWindow::HitTestBorderlessWindowChrome( + LPARAM lParam) const { + if (!IsBorderlessWindowEnabled() || m_window.hwnd == nullptr) { + return Host::BorderlessWindowChromeHitTarget::None; + } + + RECT clientRect = {}; + if (!GetClientRect(m_window.hwnd, &clientRect)) { + return Host::BorderlessWindowChromeHitTarget::None; + } + + const float clientWidthDips = + PixelsToDips(static_cast((std::max)(clientRect.right - clientRect.left, 1L))); + const Host::BorderlessWindowChromeLayout layout = + ResolveBorderlessWindowChromeLayout(clientWidthDips); + return Host::HitTestBorderlessWindowChrome( + layout, + ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); +} + +Host::BorderlessWindowChromeLayout EditorWindow::ResolveBorderlessWindowChromeLayout( + float clientWidthDips) const { + return Host::BuildBorderlessWindowChromeLayout( + UIRect(0.0f, 0.0f, clientWidthDips, kBorderlessTitleBarHeightDips), + 0.0f); +} + +void EditorWindow::AppendBorderlessWindowChrome( + UIDrawList& drawList, + float clientWidthDips) const { + if (!IsBorderlessWindowEnabled()) { + return; + } + + const Host::BorderlessWindowChromeLayout layout = + ResolveBorderlessWindowChromeLayout(clientWidthDips); + const float iconExtent = 16.0f; + const float iconInsetLeft = 8.0f; + const float iconTextGap = 8.0f; + const float iconX = layout.titleBarRect.x + iconInsetLeft; + const float iconY = + layout.titleBarRect.y + + (std::max)(0.0f, (layout.titleBarRect.height - iconExtent) * 0.5f); + drawList.AddFilledRect(layout.titleBarRect, kShellSurfaceColor); + drawList.AddLine( + UIPoint(layout.titleBarRect.x, layout.titleBarRect.y + layout.titleBarRect.height), + UIPoint( + layout.titleBarRect.x + layout.titleBarRect.width, + layout.titleBarRect.y + layout.titleBarRect.height), + kShellBorderColor, + 1.0f); + if (m_render.titleBarLogoIcon.IsValid()) { + drawList.AddImage( + UIRect(iconX, iconY, iconExtent, iconExtent), + m_render.titleBarLogoIcon, + UIColor(1.0f, 1.0f, 1.0f, 1.0f)); + } + + const std::string& titleText = + m_window.titleText.empty() ? std::string("XCEngine Editor") : m_window.titleText; + drawList.AddText( + UIPoint( + iconX + (m_render.titleBarLogoIcon.IsValid() ? (iconExtent + iconTextGap) : 4.0f), + layout.titleBarRect.y + + (std::max)( + 0.0f, + (layout.titleBarRect.height - kBorderlessTitleBarFontSize) * 0.5f - 1.0f)), + titleText, + kShellTextColor, + kBorderlessTitleBarFontSize); + Host::AppendBorderlessWindowChrome( + drawList, + layout, + m_chrome.chromeState, + IsBorderlessWindowMaximized()); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Host/HostRuntimeState.h b/new_editor/app/Platform/Win32/HostRuntimeState.h similarity index 100% rename from new_editor/app/Host/HostRuntimeState.h rename to new_editor/app/Platform/Win32/HostRuntimeState.h diff --git a/new_editor/app/Host/InputModifierTracker.h b/new_editor/app/Platform/Win32/InputModifierTracker.h similarity index 100% rename from new_editor/app/Host/InputModifierTracker.h rename to new_editor/app/Platform/Win32/InputModifierTracker.h diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatchHandlers.h b/new_editor/app/Platform/Win32/WindowMessageDispatchHandlers.h new file mode 100644 index 00000000..dd42ede1 --- /dev/null +++ b/new_editor/app/Platform/Win32/WindowMessageDispatchHandlers.h @@ -0,0 +1,55 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + +namespace XCEngine::UI::Editor { +namespace App { +class EditorWindow; +} +} + +namespace XCEngine::UI::Editor::Host { + +class WindowMessageHost; + +struct WindowMessageDispatchContext { + HWND hwnd = nullptr; + WindowMessageHost& windowHost; + App::EditorWindow& window; +}; + +void RenderAndValidateWindow(const WindowMessageDispatchContext& context); + +bool TryDispatchWindowChromeMessage( + const WindowMessageDispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult); + +bool TryDispatchWindowLifecycleMessage( + const WindowMessageDispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult); + +bool TryDispatchWindowInputMessage( + const WindowMessageDispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult); + +bool TryDispatchWindowPointerMessage( + const WindowMessageDispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult); + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp b/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp new file mode 100644 index 00000000..96580930 --- /dev/null +++ b/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp @@ -0,0 +1,26 @@ +#include "WindowMessageDispatcher.h" + +#include "WindowMessageDispatchHandlers.h" + +namespace XCEngine::UI::Editor::Host { + +bool WindowMessageDispatcher::TryDispatch( + HWND hwnd, + WindowMessageHost& windowHost, + App::EditorWindow& window, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult) { + const WindowMessageDispatchContext context = { + .hwnd = hwnd, + .windowHost = windowHost, + .window = window, + }; + + return TryDispatchWindowChromeMessage(context, message, wParam, lParam, outResult) || + TryDispatchWindowLifecycleMessage(context, message, wParam, lParam, outResult) || + TryDispatchWindowInputMessage(context, message, wParam, lParam, outResult); +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/WindowMessageDispatcher.h b/new_editor/app/Platform/Win32/WindowMessageDispatcher.h similarity index 60% rename from new_editor/app/Host/WindowMessageDispatcher.h rename to new_editor/app/Platform/Win32/WindowMessageDispatcher.h index 5b930606..a40dd0ec 100644 --- a/new_editor/app/Host/WindowMessageDispatcher.h +++ b/new_editor/app/Platform/Win32/WindowMessageDispatcher.h @@ -7,22 +7,21 @@ #include namespace XCEngine::UI::Editor { -class Application; +namespace App { +class EditorWindow; +} } namespace XCEngine::UI::Editor::Host { +class WindowMessageHost; + class WindowMessageDispatcher { public: - static Application* GetApplicationFromWindow(HWND hwnd); - static bool TryHandleNonClientCreate( - HWND hwnd, - UINT message, - LPARAM lParam, - LRESULT& outResult); static bool TryDispatch( HWND hwnd, - Application& application, + WindowMessageHost& windowHost, + App::EditorWindow& window, UINT message, WPARAM wParam, LPARAM lParam, diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcherChrome.cpp b/new_editor/app/Platform/Win32/WindowMessageDispatcherChrome.cpp new file mode 100644 index 00000000..1ac7a937 --- /dev/null +++ b/new_editor/app/Platform/Win32/WindowMessageDispatcherChrome.cpp @@ -0,0 +1,75 @@ +#include "WindowMessageDispatchHandlers.h" + +#include "Platform/Win32/EditorWindow.h" +#include "WindowMessageHost.h" + +namespace XCEngine::UI::Editor::Host { + +namespace { + +constexpr UINT kMessageNcUaDrawCaption = 0x00AEu; +constexpr UINT kMessageNcUaDrawFrame = 0x00AFu; + +} // namespace + +bool TryDispatchWindowChromeMessage( + const WindowMessageDispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult) { + switch (message) { + case WM_GETMINMAXINFO: + if (context.window.IsBorderlessWindowEnabled() && + context.window.HandleBorderlessWindowGetMinMaxInfo(lParam)) { + outResult = 0; + return true; + } + return false; + case WM_NCCALCSIZE: + if (context.window.IsBorderlessWindowEnabled()) { + outResult = context.window.HandleBorderlessWindowNcCalcSize(wParam, lParam); + return true; + } + return false; + case WM_NCACTIVATE: + if (context.window.IsBorderlessWindowEnabled()) { + outResult = TRUE; + return true; + } + return false; + case WM_NCHITTEST: + if (context.window.IsBorderlessWindowEnabled()) { + outResult = HTCLIENT; + return true; + } + return false; + case WM_NCPAINT: + case kMessageNcUaDrawCaption: + case kMessageNcUaDrawFrame: + if (context.window.IsBorderlessWindowEnabled()) { + outResult = 0; + return true; + } + return false; + case WM_SYSCOMMAND: + if (context.window.HandleBorderlessWindowSystemCommand( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive(), + wParam)) { + outResult = 0; + return true; + } + return false; + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT && context.window.ApplyCurrentCursor()) { + outResult = TRUE; + return true; + } + return false; + default: + return false; + } +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcherInput.cpp b/new_editor/app/Platform/Win32/WindowMessageDispatcherInput.cpp new file mode 100644 index 00000000..30a36887 --- /dev/null +++ b/new_editor/app/Platform/Win32/WindowMessageDispatcherInput.cpp @@ -0,0 +1,73 @@ +#include "WindowMessageDispatchHandlers.h" + +#include "Platform/Win32/EditorWindow.h" +#include "WindowMessageHost.h" + +namespace XCEngine::UI::Editor::Host { + +bool TryDispatchWindowInputMessage( + const WindowMessageDispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult) { + if (TryDispatchWindowPointerMessage(context, message, wParam, lParam, outResult)) { + return true; + } + + switch (message) { + case WM_SETFOCUS: + context.window.SyncInputModifiersFromSystemState(); + context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusGained); + outResult = 0; + return true; + case WM_KILLFOCUS: + context.window.ResetInputModifiers(); + context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusLost); + outResult = 0; + return true; + case WM_CAPTURECHANGED: + if (context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId()) && + reinterpret_cast(lParam) != context.hwnd) { + context.windowHost.EndGlobalTabDragSession(); + outResult = 0; + return true; + } + if (reinterpret_cast(lParam) != context.hwnd && + context.window.HasInteractiveCaptureState()) { + context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusLost); + context.window.ForceClearBorderlessWindowResizeState(); + context.window.ClearBorderlessWindowChromeDragRestoreState(); + context.window.ClearBorderlessWindowChromeState(); + outResult = 0; + return true; + } + if (reinterpret_cast(lParam) != context.hwnd) { + context.window.ForceClearBorderlessWindowResizeState(); + context.window.ClearBorderlessWindowChromeDragRestoreState(); + context.window.ClearBorderlessWindowChromeState(); + } + return false; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + if (wParam == VK_F12) { + context.window.RequestManualScreenshot(); + } + context.window.QueueKeyEvent(::XCEngine::UI::UIInputEventType::KeyDown, wParam, lParam); + outResult = 0; + return true; + case WM_KEYUP: + case WM_SYSKEYUP: + context.window.QueueKeyEvent(::XCEngine::UI::UIInputEventType::KeyUp, wParam, lParam); + outResult = 0; + return true; + case WM_CHAR: + context.window.QueueCharacterEvent(wParam, lParam); + outResult = 0; + return true; + default: + return false; + } +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcherLifecycle.cpp b/new_editor/app/Platform/Win32/WindowMessageDispatcherLifecycle.cpp new file mode 100644 index 00000000..43ed68f4 --- /dev/null +++ b/new_editor/app/Platform/Win32/WindowMessageDispatcherLifecycle.cpp @@ -0,0 +1,77 @@ +#include "WindowMessageDispatchHandlers.h" + +#include "Platform/Win32/EditorWindow.h" +#include "WindowMessageHost.h" + +namespace XCEngine::UI::Editor::Host { + +void RenderAndValidateWindow(const WindowMessageDispatchContext& context) { + if (!context.window.IsRenderReady()) { + return; + } + + context.window.RenderFrame( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive()); + if (context.hwnd != nullptr && IsWindow(context.hwnd)) { + ValidateRect(context.hwnd, nullptr); + } +} + +bool TryDispatchWindowLifecycleMessage( + const WindowMessageDispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult) { + switch (message) { + case WM_DPICHANGED: + if (lParam == 0) { + return false; + } + context.window.OnDpiChanged( + static_cast(LOWORD(wParam)), + *reinterpret_cast(lParam)); + RenderAndValidateWindow(context); + outResult = 0; + return true; + case WM_ENTERSIZEMOVE: + context.window.OnEnterSizeMove(); + outResult = 0; + return true; + case WM_EXITSIZEMOVE: + context.window.OnExitSizeMove(); + RenderAndValidateWindow(context); + outResult = 0; + return true; + case WM_SIZE: + if (wParam != SIZE_MINIMIZED) { + context.window.OnResize( + static_cast(LOWORD(lParam)), + static_cast(HIWORD(lParam))); + RenderAndValidateWindow(context); + } + outResult = 0; + return true; + case WM_PAINT: + context.window.OnPaintMessage( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive()); + outResult = 0; + return true; + case WM_ERASEBKGND: + outResult = 1; + return true; + case WM_DESTROY: + if (context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) { + context.windowHost.EndGlobalTabDragSession(); + } + context.windowHost.HandleDestroyedWindow(context.hwnd); + outResult = 0; + return true; + default: + return false; + } +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcherPointer.cpp b/new_editor/app/Platform/Win32/WindowMessageDispatcherPointer.cpp new file mode 100644 index 00000000..ca4a6be7 --- /dev/null +++ b/new_editor/app/Platform/Win32/WindowMessageDispatcherPointer.cpp @@ -0,0 +1,160 @@ +#include "WindowMessageDispatchHandlers.h" + +#include "Platform/Win32/EditorWindow.h" +#include "WindowMessageHost.h" + +namespace XCEngine::UI::Editor::Host { + +namespace { + +bool EnsureTrackingMouseLeave(const WindowMessageDispatchContext& context) { + if (context.window.IsTrackingMouseLeave()) { + return true; + } + + TRACKMOUSEEVENT trackMouseEvent = {}; + trackMouseEvent.cbSize = sizeof(trackMouseEvent); + trackMouseEvent.dwFlags = TME_LEAVE; + trackMouseEvent.hwndTrack = context.hwnd; + if (!TrackMouseEvent(&trackMouseEvent)) { + return false; + } + + context.window.SetTrackingMouseLeave(true); + return true; +} + +bool TryHandleChromeHoverConsumption( + const WindowMessageDispatchContext& context, + LPARAM lParam, + LRESULT& outResult) { + const bool resizeHoverChanged = context.window.UpdateBorderlessWindowResizeHover(lParam); + if (context.window.UpdateBorderlessWindowChromeHover(lParam)) { + context.window.InvalidateHostWindow(); + } + if (resizeHoverChanged) { + context.window.InvalidateHostWindow(); + } + + EnsureTrackingMouseLeave(context); + if (context.window.HasHoveredBorderlessResizeEdge()) { + outResult = 0; + return true; + } + + const BorderlessWindowChromeHitTarget chromeHitTarget = + context.window.HitTestBorderlessWindowChrome(lParam); + if (chromeHitTarget == BorderlessWindowChromeHitTarget::MinimizeButton || + chromeHitTarget == BorderlessWindowChromeHitTarget::MaximizeRestoreButton || + chromeHitTarget == BorderlessWindowChromeHitTarget::CloseButton) { + outResult = 0; + return true; + } + + return false; +} + +} // namespace + +bool TryDispatchWindowPointerMessage( + const WindowMessageDispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult) { + switch (message) { + case WM_MOUSEMOVE: + if (context.windowHost.HandleGlobalTabDragPointerMove(context.hwnd)) { + outResult = 0; + return true; + } + if (context.window.HandleBorderlessWindowResizePointerMove( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive())) { + outResult = 0; + return true; + } + if (context.window.HandleBorderlessWindowChromeDragRestorePointerMove( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive())) { + outResult = 0; + return true; + } + if (TryHandleChromeHoverConsumption(context, lParam, outResult)) { + return true; + } + + context.window.QueuePointerEvent( + ::XCEngine::UI::UIInputEventType::PointerMove, + ::XCEngine::UI::UIPointerButton::None, + wParam, + lParam); + outResult = 0; + return true; + case WM_MOUSELEAVE: + context.window.SetTrackingMouseLeave(false); + context.window.ClearBorderlessWindowResizeState(); + context.window.ClearBorderlessWindowChromeDragRestoreState(); + context.window.ClearBorderlessWindowChromeState(); + context.window.QueuePointerLeaveEvent(); + outResult = 0; + return true; + case WM_LBUTTONDOWN: + if (context.window.HandleBorderlessWindowResizeButtonDown(lParam)) { + outResult = 0; + return true; + } + if (context.window.HandleBorderlessWindowChromeButtonDown(lParam)) { + outResult = 0; + return true; + } + SetFocus(context.hwnd); + context.window.QueuePointerEvent( + ::XCEngine::UI::UIInputEventType::PointerButtonDown, + ::XCEngine::UI::UIPointerButton::Left, + wParam, + lParam); + outResult = 0; + return true; + case WM_LBUTTONUP: + if (context.windowHost.HandleGlobalTabDragPointerButtonUp(context.hwnd)) { + outResult = 0; + return true; + } + if (context.window.HandleBorderlessWindowResizeButtonUp()) { + outResult = 0; + return true; + } + if (context.window.HandleBorderlessWindowChromeButtonUp( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive(), + lParam)) { + outResult = 0; + return true; + } + context.window.QueuePointerEvent( + ::XCEngine::UI::UIInputEventType::PointerButtonUp, + ::XCEngine::UI::UIPointerButton::Left, + wParam, + lParam); + outResult = 0; + return true; + case WM_LBUTTONDBLCLK: + if (context.window.HandleBorderlessWindowChromeDoubleClick( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive(), + lParam)) { + outResult = 0; + return true; + } + return false; + case WM_MOUSEWHEEL: + context.window.QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam); + outResult = 0; + return true; + default: + return false; + } +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageHost.h b/new_editor/app/Platform/Win32/WindowMessageHost.h new file mode 100644 index 00000000..403c81a9 --- /dev/null +++ b/new_editor/app/Platform/Win32/WindowMessageHost.h @@ -0,0 +1,32 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + +#include + +namespace XCEngine::UI::Editor::App { +class EditorContext; +} + +namespace XCEngine::UI::Editor::Host { + +class WindowMessageHost { +public: + virtual ~WindowMessageHost() = default; + + virtual App::EditorContext& GetEditorContext() = 0; + virtual const App::EditorContext& GetEditorContext() const = 0; + virtual bool IsGlobalTabDragActive() const = 0; + virtual bool OwnsActiveGlobalTabDrag(std::string_view windowId) const = 0; + virtual void EndGlobalTabDragSession() = 0; + virtual void HandleDestroyedWindow(HWND hwnd) = 0; + virtual bool HandleGlobalTabDragPointerMove(HWND hwnd) = 0; + virtual bool HandleGlobalTabDragPointerButtonUp(HWND hwnd) = 0; +}; + +} // namespace XCEngine::UI::Editor::Host + diff --git a/new_editor/app/Rendering/Assets/BuiltInIcons.cpp b/new_editor/app/Rendering/Assets/BuiltInIcons.cpp new file mode 100644 index 00000000..c1bf08a8 --- /dev/null +++ b/new_editor/app/Rendering/Assets/BuiltInIcons.cpp @@ -0,0 +1,97 @@ +#include "BuiltInIcons.h" +#include "Bootstrap/EditorResources.h" +#include "Support/EmbeddedPngLoader.h" + +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +void AppendLoadError( + std::ostringstream& stream, + std::string_view label, + const std::string& error) { + if (error.empty()) { + return; + } + + if (stream.tellp() > 0) { + stream << '\n'; + } + stream << label << ": " << error; +} + +void LoadEmbeddedIconTexture( + Host::NativeRenderer& renderer, + UINT resourceId, + std::string_view label, + ::XCEngine::UI::UITextureHandle& outTexture, + std::ostringstream& errorStream) { + std::string error = {}; + if (!Support::LoadEmbeddedPngTexture(renderer, resourceId, outTexture, error)) { + AppendLoadError(errorStream, label, error); + } +} + +} // namespace + +void BuiltInIcons::Initialize(Host::NativeRenderer& renderer) { + Shutdown(); + + m_renderer = &renderer; + + std::ostringstream errorStream = {}; + LoadEmbeddedIconTexture( + renderer, + IDR_PNG_FOLDER_ICON, + "folder_icon.png", + m_folderIcon, + errorStream); + LoadEmbeddedIconTexture( + renderer, + IDR_PNG_GAMEOBJECT_ICON, + "gameobject_icon.png", + m_gameObjectIcon, + errorStream); + LoadEmbeddedIconTexture( + renderer, + IDR_PNG_SCENE_ICON, + "scene_icon.png", + m_sceneIcon, + errorStream); + + m_lastError = errorStream.str(); +} + +void BuiltInIcons::Shutdown() { + if (m_renderer != nullptr) { + m_renderer->ReleaseTexture(m_folderIcon); + m_renderer->ReleaseTexture(m_gameObjectIcon); + m_renderer->ReleaseTexture(m_sceneIcon); + } + + m_renderer = nullptr; + m_lastError.clear(); +} + +const ::XCEngine::UI::UITextureHandle& BuiltInIcons::Resolve( + BuiltInIconKind kind) const { + switch (kind) { + case BuiltInIconKind::Folder: + return m_folderIcon; + case BuiltInIconKind::GameObject: + return m_gameObjectIcon; + case BuiltInIconKind::Scene: + return m_sceneIcon; + default: + return m_folderIcon; + } +} + +const std::string& BuiltInIcons::GetLastError() const { + return m_lastError; +} + +} // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Features/Shared/BuiltInIcons.h b/new_editor/app/Rendering/Assets/BuiltInIcons.h similarity index 75% rename from new_editor/app/Features/Shared/BuiltInIcons.h rename to new_editor/app/Rendering/Assets/BuiltInIcons.h index 121b6caf..d48a63c4 100644 --- a/new_editor/app/Features/Shared/BuiltInIcons.h +++ b/new_editor/app/Rendering/Assets/BuiltInIcons.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include @@ -9,18 +9,18 @@ namespace XCEngine::UI::Editor::App { -enum class ProductBuiltInIconKind : std::uint8_t { +enum class BuiltInIconKind : std::uint8_t { Folder = 0, GameObject, Scene }; -class ProductBuiltInIcons { +class BuiltInIcons { public: void Initialize(Host::NativeRenderer& renderer); void Shutdown(); - const ::XCEngine::UI::UITextureHandle& Resolve(ProductBuiltInIconKind kind) const; + const ::XCEngine::UI::UITextureHandle& Resolve(BuiltInIconKind kind) const; const std::string& GetLastError() const; private: @@ -32,3 +32,4 @@ private: }; } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Rendering/D3D12/D3D12HostDevice.cpp b/new_editor/app/Rendering/D3D12/D3D12HostDevice.cpp new file mode 100644 index 00000000..6fd8d28e --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12HostDevice.cpp @@ -0,0 +1,8 @@ +#include "D3D12HostDevice.h" + +namespace XCEngine::UI::Editor::Host { + +// D3D12HostDevice implementations are split into lifecycle, frame, and fence +// translation units to keep device-host responsibilities isolated. + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/D3D12HostDevice.h b/new_editor/app/Rendering/D3D12/D3D12HostDevice.h similarity index 100% rename from new_editor/app/Host/D3D12HostDevice.h rename to new_editor/app/Rendering/D3D12/D3D12HostDevice.h diff --git a/new_editor/app/Rendering/D3D12/D3D12HostDeviceFence.cpp b/new_editor/app/Rendering/D3D12/D3D12HostDeviceFence.cpp new file mode 100644 index 00000000..bf36fd4e --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12HostDeviceFence.cpp @@ -0,0 +1,42 @@ +#include "D3D12HostDevice.h" + +namespace XCEngine::UI::Editor::Host { + +bool D3D12HostDevice::InitializeFrameCompletionFence() { + ReleaseFrameCompletionFence(); + + ID3D12Device* device = GetDevice(); + if (device == nullptr) { + return false; + } + + const HRESULT hr = device->CreateFence( + 0u, + D3D12_FENCE_FLAG_NONE, + IID_PPV_ARGS(m_frameCompletionFence.ReleaseAndGetAddressOf())); + if (FAILED(hr)) { + return false; + } + + m_frameCompletionEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if (m_frameCompletionEvent == nullptr) { + m_frameCompletionFence.Reset(); + return false; + } + + m_lastSubmittedFrameValue = 0u; + return true; +} + +void D3D12HostDevice::ReleaseFrameCompletionFence() { + if (m_frameCompletionEvent != nullptr) { + CloseHandle(m_frameCompletionEvent); + m_frameCompletionEvent = nullptr; + } + + m_frameCompletionFence.Reset(); + m_frameFenceValues.fill(0u); + m_lastSubmittedFrameValue = 0u; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/D3D12/D3D12HostDeviceFrame.cpp b/new_editor/app/Rendering/D3D12/D3D12HostDeviceFrame.cpp new file mode 100644 index 00000000..69b660d2 --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12HostDeviceFrame.cpp @@ -0,0 +1,113 @@ +#include "D3D12HostDevice.h" + +namespace XCEngine::UI::Editor::Host { + +using ::XCEngine::RHI::D3D12CommandList; +using ::XCEngine::RHI::RHICommandList; + +bool D3D12HostDevice::BeginFrame(std::uint32_t frameIndex) { + WaitForFrame(frameIndex); + + RHICommandList* commandList = GetCommandList(frameIndex); + D3D12CommandList* d3d12CommandList = GetD3D12CommandList(frameIndex); + if (commandList == nullptr || d3d12CommandList == nullptr) { + m_lastError = "BeginFrame could not resolve the active command list."; + return false; + } + + commandList->Reset(); + m_lastError.clear(); + return true; +} + +bool D3D12HostDevice::SubmitFrame(std::uint32_t frameIndex) { + RHICommandList* commandList = GetCommandList(frameIndex); + if (commandList == nullptr || m_commandQueue == nullptr) { + m_lastError = "SubmitFrame requires an initialized command list and queue."; + return false; + } + + commandList->Close(); + void* commandLists[] = { commandList }; + m_commandQueue->ExecuteCommandLists(1, commandLists); + m_lastError.clear(); + return true; +} + +bool D3D12HostDevice::SignalFrameCompletion(std::uint32_t frameIndex) { + ID3D12CommandQueue* commandQueue = GetCommandQueue(); + if (commandQueue == nullptr || + m_frameCompletionFence == nullptr || + frameIndex >= m_frameFenceValues.size()) { + return false; + } + + ++m_lastSubmittedFrameValue; + const HRESULT hr = commandQueue->Signal( + m_frameCompletionFence.Get(), + m_lastSubmittedFrameValue); + if (SUCCEEDED(hr)) { + m_frameFenceValues[frameIndex] = m_lastSubmittedFrameValue; + m_lastError.clear(); + } + return SUCCEEDED(hr); +} + +void D3D12HostDevice::WaitForFrame(std::uint32_t frameIndex) { + if (m_frameCompletionFence == nullptr || + m_frameCompletionEvent == nullptr || + frameIndex >= m_frameFenceValues.size()) { + return; + } + + const std::uint64_t fenceValue = m_frameFenceValues[frameIndex]; + if (fenceValue == 0u || + m_frameCompletionFence->GetCompletedValue() >= fenceValue) { + return; + } + + const HRESULT hr = m_frameCompletionFence->SetEventOnCompletion( + fenceValue, + m_frameCompletionEvent); + if (SUCCEEDED(hr)) { + WaitForSingleObject(m_frameCompletionEvent, INFINITE); + } +} + +void D3D12HostDevice::WaitForGpuIdle() { + ID3D12CommandQueue* commandQueue = GetCommandQueue(); + if (commandQueue == nullptr || + m_frameCompletionFence == nullptr || + m_frameCompletionEvent == nullptr) { + if (m_commandQueue != nullptr) { + m_commandQueue->WaitForIdle(); + } + return; + } + + ++m_lastSubmittedFrameValue; + const std::uint64_t fenceValue = m_lastSubmittedFrameValue; + const HRESULT signalHr = commandQueue->Signal( + m_frameCompletionFence.Get(), + fenceValue); + if (FAILED(signalHr)) { + return; + } + + if (m_frameCompletionFence->GetCompletedValue() >= fenceValue) { + return; + } + + const HRESULT waitHr = m_frameCompletionFence->SetEventOnCompletion( + fenceValue, + m_frameCompletionEvent); + if (SUCCEEDED(waitHr)) { + WaitForSingleObject(m_frameCompletionEvent, INFINITE); + } +} + +void D3D12HostDevice::ResetFrameTracking() { + m_frameFenceValues.fill(0u); +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/D3D12HostDevice.cpp b/new_editor/app/Rendering/D3D12/D3D12HostDeviceLifecycle.cpp similarity index 53% rename from new_editor/app/Host/D3D12HostDevice.cpp rename to new_editor/app/Rendering/D3D12/D3D12HostDeviceLifecycle.cpp index 4701955f..2093cbb3 100644 --- a/new_editor/app/Host/D3D12HostDevice.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12HostDeviceLifecycle.cpp @@ -100,111 +100,6 @@ void D3D12HostDevice::Shutdown() { m_lastError.clear(); } -bool D3D12HostDevice::BeginFrame(std::uint32_t frameIndex) { - WaitForFrame(frameIndex); - - RHICommandList* commandList = GetCommandList(frameIndex); - D3D12CommandList* d3d12CommandList = GetD3D12CommandList(frameIndex); - if (commandList == nullptr || d3d12CommandList == nullptr) { - m_lastError = "BeginFrame could not resolve the active command list."; - return false; - } - - commandList->Reset(); - m_lastError.clear(); - return true; -} - -bool D3D12HostDevice::SubmitFrame(std::uint32_t frameIndex) { - RHICommandList* commandList = GetCommandList(frameIndex); - if (commandList == nullptr || m_commandQueue == nullptr) { - m_lastError = "SubmitFrame requires an initialized command list and queue."; - return false; - } - - commandList->Close(); - void* commandLists[] = { commandList }; - m_commandQueue->ExecuteCommandLists(1, commandLists); - m_lastError.clear(); - return true; -} - -bool D3D12HostDevice::SignalFrameCompletion(std::uint32_t frameIndex) { - ID3D12CommandQueue* commandQueue = GetCommandQueue(); - if (commandQueue == nullptr || - m_frameCompletionFence == nullptr || - frameIndex >= m_frameFenceValues.size()) { - return false; - } - - ++m_lastSubmittedFrameValue; - const HRESULT hr = commandQueue->Signal( - m_frameCompletionFence.Get(), - m_lastSubmittedFrameValue); - if (SUCCEEDED(hr)) { - m_frameFenceValues[frameIndex] = m_lastSubmittedFrameValue; - m_lastError.clear(); - } - return SUCCEEDED(hr); -} - -void D3D12HostDevice::WaitForFrame(std::uint32_t frameIndex) { - if (m_frameCompletionFence == nullptr || - m_frameCompletionEvent == nullptr || - frameIndex >= m_frameFenceValues.size()) { - return; - } - - const std::uint64_t fenceValue = m_frameFenceValues[frameIndex]; - if (fenceValue == 0u || - m_frameCompletionFence->GetCompletedValue() >= fenceValue) { - return; - } - - const HRESULT hr = m_frameCompletionFence->SetEventOnCompletion( - fenceValue, - m_frameCompletionEvent); - if (SUCCEEDED(hr)) { - WaitForSingleObject(m_frameCompletionEvent, INFINITE); - } -} - -void D3D12HostDevice::WaitForGpuIdle() { - ID3D12CommandQueue* commandQueue = GetCommandQueue(); - if (commandQueue == nullptr || - m_frameCompletionFence == nullptr || - m_frameCompletionEvent == nullptr) { - if (m_commandQueue != nullptr) { - m_commandQueue->WaitForIdle(); - } - return; - } - - ++m_lastSubmittedFrameValue; - const std::uint64_t fenceValue = m_lastSubmittedFrameValue; - const HRESULT signalHr = commandQueue->Signal( - m_frameCompletionFence.Get(), - fenceValue); - if (FAILED(signalHr)) { - return; - } - - if (m_frameCompletionFence->GetCompletedValue() >= fenceValue) { - return; - } - - const HRESULT waitHr = m_frameCompletionFence->SetEventOnCompletion( - fenceValue, - m_frameCompletionEvent); - if (SUCCEEDED(waitHr)) { - WaitForSingleObject(m_frameCompletionEvent, INFINITE); - } -} - -void D3D12HostDevice::ResetFrameTracking() { - m_frameFenceValues.fill(0u); -} - ID3D12Device* D3D12HostDevice::GetDevice() const { const D3D12Device* device = GetD3D12Device(); return device != nullptr ? device->GetDevice() : nullptr; @@ -256,41 +151,4 @@ D3D12CommandList* D3D12HostDevice::GetD3D12CommandList(std::uint32_t frameIndex) return commandList != nullptr ? static_cast(commandList) : nullptr; } -bool D3D12HostDevice::InitializeFrameCompletionFence() { - ReleaseFrameCompletionFence(); - - ID3D12Device* device = GetDevice(); - if (device == nullptr) { - return false; - } - - const HRESULT hr = device->CreateFence( - 0u, - D3D12_FENCE_FLAG_NONE, - IID_PPV_ARGS(m_frameCompletionFence.ReleaseAndGetAddressOf())); - if (FAILED(hr)) { - return false; - } - - m_frameCompletionEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if (m_frameCompletionEvent == nullptr) { - m_frameCompletionFence.Reset(); - return false; - } - - m_lastSubmittedFrameValue = 0u; - return true; -} - -void D3D12HostDevice::ReleaseFrameCompletionFence() { - if (m_frameCompletionEvent != nullptr) { - CloseHandle(m_frameCompletionEvent); - m_frameCompletionEvent = nullptr; - } - - m_frameCompletionFence.Reset(); - m_frameFenceValues.fill(0u); - m_lastSubmittedFrameValue = 0u; -} - } // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.cpp b/new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.cpp similarity index 100% rename from new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.cpp rename to new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.cpp diff --git a/new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.h b/new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h similarity index 100% rename from new_editor/app/Host/D3D12ShaderResourceDescriptorAllocator.h rename to new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp new file mode 100644 index 00000000..2cb12460 --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp @@ -0,0 +1,100 @@ +#include "D3D12WindowInteropSupport.h" + +#include + +namespace XCEngine::UI::Editor::Host::InteropSupport { + +std::string HrToInteropString(const char* operation, HRESULT hr) { + char buffer[128] = {}; + sprintf_s(buffer, "%s failed with hr=0x%08X.", operation, static_cast(hr)); + return buffer; +} + +D2D1_BITMAP_PROPERTIES1 BuildD2DBitmapProperties( + DXGI_FORMAT format, + D2D1_BITMAP_OPTIONS options) { + return D2D1::BitmapProperties1( + options, + D2D1::PixelFormat(format, D2D1_ALPHA_MODE_IGNORE), + 96.0f, + 96.0f); +} + +bool IsInteropTextureHandle(const ::XCEngine::UI::UITextureHandle& texture) { + return texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView && + texture.resourceHandle != 0u; +} + +void CollectInteropTextureHandles( + const ::XCEngine::UI::UIDrawData& drawData, + std::vector<::XCEngine::UI::UITextureHandle>& outTextures) { + outTextures.clear(); + std::unordered_set seenKeys = {}; + for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) { + for (const ::XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) { + if (!IsInteropTextureHandle(command.texture) || + !seenKeys.insert(command.texture.resourceHandle).second) { + continue; + } + + outTextures.push_back(command.texture); + } + } +} + +} // namespace XCEngine::UI::Editor::Host::InteropSupport + +namespace XCEngine::UI::Editor::Host { + +bool D3D12WindowInteropContext::Attach( + D3D12WindowRenderer& windowRenderer, + ID2D1Factory1& d2dFactory) { + if (m_windowRenderer != &windowRenderer) { + Detach(); + m_windowRenderer = &windowRenderer; + } + + m_d2dFactory = &d2dFactory; + if (!EnsureInterop()) { + return false; + } + + if (HasBackBufferTargets()) { + return true; + } + + return RebuildBackBufferTargets(); +} + +void D3D12WindowInteropContext::Detach() { + ReleaseInteropState(); + m_windowRenderer = nullptr; + m_d2dFactory = nullptr; + m_lastError.clear(); +} + +D3D12WindowRenderer* D3D12WindowInteropContext::GetWindowRenderer() const { + return m_windowRenderer; +} + +ID3D11On12Device* D3D12WindowInteropContext::GetD3D11On12Device() const { + return m_d3d11On12Device.Get(); +} + +ID3D11DeviceContext* D3D12WindowInteropContext::GetD3D11DeviceContext() const { + return m_d3d11DeviceContext.Get(); +} + +ID2D1DeviceContext* D3D12WindowInteropContext::GetD2DDeviceContext() const { + return m_d2dDeviceContext.Get(); +} + +ID2D1SolidColorBrush* D3D12WindowInteropContext::GetInteropBrush() const { + return m_interopBrush.Get(); +} + +const std::string& D3D12WindowInteropContext::GetLastError() const { + return m_lastError; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/D3D12WindowInteropContext.h b/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.h similarity index 100% rename from new_editor/app/Host/D3D12WindowInteropContext.h rename to new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.h diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowInteropDevice.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowInteropDevice.cpp new file mode 100644 index 00000000..7c92ee01 --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12WindowInteropDevice.cpp @@ -0,0 +1,138 @@ +#include "D3D12WindowInteropSupport.h" + +#include + +namespace XCEngine::UI::Editor::Host { + +using namespace InteropSupport; + +bool D3D12WindowInteropContext::EnsureInterop() { + if (m_windowRenderer == nullptr) { + m_lastError = "EnsureInterop requires an attached D3D12 window renderer."; + return false; + } + if (m_d2dFactory == nullptr) { + m_lastError = "EnsureInterop requires an initialized D2D factory."; + return false; + } + if (m_d3d11On12Device != nullptr && + m_d2dDeviceContext != nullptr && + m_interopBrush != nullptr) { + return true; + } + + ReleaseInteropState(); + + ID3D12Device* d3d12Device = m_windowRenderer->GetDevice(); + ID3D12CommandQueue* d3d12CommandQueue = m_windowRenderer->GetCommandQueue(); + if (d3d12Device == nullptr || d3d12CommandQueue == nullptr) { + m_lastError = "The attached D3D12 window renderer does not expose a native device/queue."; + return false; + } + + const std::array featureLevels = { + D3D_FEATURE_LEVEL_12_1, + D3D_FEATURE_LEVEL_12_0, + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0 + }; + const std::array commandQueues = { + reinterpret_cast(d3d12CommandQueue) + }; + + UINT createFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; +#ifdef _DEBUG + createFlags |= D3D11_CREATE_DEVICE_DEBUG; +#endif + + D3D_FEATURE_LEVEL actualFeatureLevel = D3D_FEATURE_LEVEL_11_0; + HRESULT hr = D3D11On12CreateDevice( + d3d12Device, + createFlags, + featureLevels.data(), + static_cast(featureLevels.size()), + commandQueues.data(), + static_cast(commandQueues.size()), + 0u, + m_d3d11Device.ReleaseAndGetAddressOf(), + m_d3d11DeviceContext.ReleaseAndGetAddressOf(), + &actualFeatureLevel); +#ifdef _DEBUG + if (FAILED(hr)) { + createFlags &= ~D3D11_CREATE_DEVICE_DEBUG; + hr = D3D11On12CreateDevice( + d3d12Device, + createFlags, + featureLevels.data(), + static_cast(featureLevels.size()), + commandQueues.data(), + static_cast(commandQueues.size()), + 0u, + m_d3d11Device.ReleaseAndGetAddressOf(), + m_d3d11DeviceContext.ReleaseAndGetAddressOf(), + &actualFeatureLevel); + } +#endif + if (FAILED(hr) || m_d3d11Device == nullptr || m_d3d11DeviceContext == nullptr) { + m_lastError = HrToInteropString("D3D11On12CreateDevice", hr); + ReleaseInteropState(); + return false; + } + + hr = m_d3d11Device.As(&m_d3d11On12Device); + if (FAILED(hr) || m_d3d11On12Device == nullptr) { + m_lastError = HrToInteropString("ID3D11Device::QueryInterface(ID3D11On12Device)", hr); + ReleaseInteropState(); + return false; + } + + Microsoft::WRL::ComPtr dxgiDevice = {}; + hr = m_d3d11Device.As(&dxgiDevice); + if (FAILED(hr) || dxgiDevice == nullptr) { + m_lastError = HrToInteropString("ID3D11Device::QueryInterface(IDXGIDevice)", hr); + ReleaseInteropState(); + return false; + } + + hr = m_d2dFactory->CreateDevice(dxgiDevice.Get(), m_d2dDevice.ReleaseAndGetAddressOf()); + if (FAILED(hr) || m_d2dDevice == nullptr) { + m_lastError = HrToInteropString("ID2D1Factory1::CreateDevice", hr); + ReleaseInteropState(); + return false; + } + + hr = m_d2dDevice->CreateDeviceContext( + D2D1_DEVICE_CONTEXT_OPTIONS_NONE, + m_d2dDeviceContext.ReleaseAndGetAddressOf()); + if (FAILED(hr) || m_d2dDeviceContext == nullptr) { + m_lastError = HrToInteropString("ID2D1Device::CreateDeviceContext", hr); + ReleaseInteropState(); + return false; + } + + hr = m_d2dDeviceContext->CreateSolidColorBrush( + D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), + m_interopBrush.ReleaseAndGetAddressOf()); + if (FAILED(hr) || m_interopBrush == nullptr) { + m_lastError = HrToInteropString("ID2D1DeviceContext::CreateSolidColorBrush", hr); + ReleaseInteropState(); + return false; + } + + m_d2dDeviceContext->SetDpi(96.0f, 96.0f); + m_d2dDeviceContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); + m_lastError.clear(); + return true; +} + +void D3D12WindowInteropContext::ReleaseInteropState() { + ReleaseBackBufferTargets(); + m_interopBrush.Reset(); + m_d2dDeviceContext.Reset(); + m_d2dDevice.Reset(); + m_d3d11On12Device.Reset(); + m_d3d11DeviceContext.Reset(); + m_d3d11Device.Reset(); +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowInteropResources.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowInteropResources.cpp new file mode 100644 index 00000000..a6bec2ad --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12WindowInteropResources.cpp @@ -0,0 +1,128 @@ +#include "D3D12WindowInteropSupport.h" + +namespace XCEngine::UI::Editor::Host { + +using namespace InteropSupport; + +void D3D12WindowInteropContext::ReleaseBackBufferTargets() { + ClearSourceTextures(); + if (m_d2dDeviceContext != nullptr) { + m_d2dDeviceContext->SetTarget(nullptr); + } + if (m_d3d11DeviceContext != nullptr) { + m_d3d11DeviceContext->ClearState(); + } + m_backBufferTargets.clear(); + if (m_d2dDeviceContext != nullptr) { + D2D1_TAG firstTag = 0u; + D2D1_TAG secondTag = 0u; + m_d2dDeviceContext->Flush(&firstTag, &secondTag); + } + if (m_d3d11DeviceContext != nullptr) { + m_d3d11DeviceContext->Flush(); + } +} + +bool D3D12WindowInteropContext::RebuildBackBufferTargets() { + m_backBufferTargets.clear(); + if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) { + return false; + } + + const std::uint32_t backBufferCount = m_windowRenderer->GetBackBufferCount(); + m_backBufferTargets.resize(backBufferCount); + for (std::uint32_t index = 0u; index < backBufferCount; ++index) { + const ::XCEngine::RHI::D3D12Texture* backBufferTexture = + m_windowRenderer->GetBackBufferTexture(index); + if (backBufferTexture == nullptr || backBufferTexture->GetResource() == nullptr) { + m_lastError = "Failed to resolve a D3D12 swap chain back buffer."; + m_backBufferTargets.clear(); + return false; + } + + D3D11_RESOURCE_FLAGS resourceFlags = {}; + resourceFlags.BindFlags = D3D11_BIND_RENDER_TARGET; + HRESULT hr = m_d3d11On12Device->CreateWrappedResource( + backBufferTexture->GetResource(), + &resourceFlags, + D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PRESENT, + IID_PPV_ARGS(m_backBufferTargets[index].wrappedResource.ReleaseAndGetAddressOf())); + if (FAILED(hr) || m_backBufferTargets[index].wrappedResource == nullptr) { + m_lastError = HrToInteropString("ID3D11On12Device::CreateWrappedResource(backbuffer)", hr); + m_backBufferTargets.clear(); + return false; + } + + Microsoft::WRL::ComPtr dxgiSurface = {}; + hr = m_backBufferTargets[index].wrappedResource.As(&dxgiSurface); + if (FAILED(hr) || dxgiSurface == nullptr) { + m_lastError = HrToInteropString("ID3D11Resource::QueryInterface(IDXGISurface)", hr); + m_backBufferTargets.clear(); + return false; + } + + const D2D1_BITMAP_PROPERTIES1 bitmapProperties = + BuildD2DBitmapProperties( + backBufferTexture->GetDesc().Format, + D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW); + hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface( + dxgiSurface.Get(), + &bitmapProperties, + m_backBufferTargets[index].targetBitmap.ReleaseAndGetAddressOf()); + if (FAILED(hr) || m_backBufferTargets[index].targetBitmap == nullptr) { + m_lastError = HrToInteropString( + "ID2D1DeviceContext::CreateBitmapFromDxgiSurface(backbuffer)", + hr); + m_backBufferTargets.clear(); + return false; + } + } + + m_lastError.clear(); + return true; +} + +bool D3D12WindowInteropContext::HasAttachedWindowRenderer() const { + return m_windowRenderer != nullptr && + m_d3d11On12Device != nullptr && + m_d2dDeviceContext != nullptr && + !m_backBufferTargets.empty(); +} + +bool D3D12WindowInteropContext::HasBackBufferTargets() const { + return !m_backBufferTargets.empty(); +} + +void D3D12WindowInteropContext::BuildAcquiredResources( + std::uint32_t backBufferIndex, + std::vector& outResources) const { + outResources.clear(); + + ID3D11Resource* backBufferResource = GetWrappedBackBufferResource(backBufferIndex); + if (backBufferResource != nullptr) { + outResources.push_back(backBufferResource); + } + + for (const SourceTextureResource& resource : m_activeSourceTextures) { + if (resource.wrappedResource != nullptr) { + outResources.push_back(resource.wrappedResource.Get()); + } + } +} + +ID3D11Resource* D3D12WindowInteropContext::GetWrappedBackBufferResource(std::uint32_t index) const { + return index < m_backBufferTargets.size() ? m_backBufferTargets[index].wrappedResource.Get() : nullptr; +} + +ID2D1Bitmap1* D3D12WindowInteropContext::GetBackBufferTargetBitmap(std::uint32_t index) const { + return index < m_backBufferTargets.size() ? m_backBufferTargets[index].targetBitmap.Get() : nullptr; +} + +std::uint32_t D3D12WindowInteropContext::GetCurrentBackBufferIndex() const { + return m_windowRenderer != nullptr && m_windowRenderer->GetSwapChain() != nullptr + ? m_windowRenderer->GetSwapChain()->GetCurrentBackBufferIndex() + : 0u; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowInteropSourceTextures.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowInteropSourceTextures.cpp new file mode 100644 index 00000000..62ddc135 --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12WindowInteropSourceTextures.cpp @@ -0,0 +1,100 @@ +#include "D3D12WindowInteropSupport.h" + +#include +#include + +namespace XCEngine::UI::Editor::Host { + +using namespace InteropSupport; + +bool D3D12WindowInteropContext::PrepareSourceTextures( + const ::XCEngine::UI::UIDrawData& drawData) { + ClearSourceTextures(); + if (m_windowRenderer == nullptr || m_d3d11On12Device == nullptr || m_d2dDeviceContext == nullptr) { + return false; + } + + std::vector<::XCEngine::UI::UITextureHandle> textureHandles = {}; + CollectInteropTextureHandles(drawData, textureHandles); + m_activeSourceTextures.reserve(textureHandles.size()); + + for (const ::XCEngine::UI::UITextureHandle& textureHandle : textureHandles) { + auto* texture = + reinterpret_cast<::XCEngine::RHI::RHITexture*>(textureHandle.resourceHandle); + auto* nativeTexture = dynamic_cast<::XCEngine::RHI::D3D12Texture*>(texture); + if (nativeTexture == nullptr || nativeTexture->GetResource() == nullptr) { + m_lastError = "Failed to resolve a D3D12 source texture for UI composition."; + ClearSourceTextures(); + return false; + } + + D3D11_RESOURCE_FLAGS resourceFlags = {}; + resourceFlags.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + SourceTextureResource resource = {}; + resource.key = textureHandle.resourceHandle; + HRESULT hr = m_d3d11On12Device->CreateWrappedResource( + nativeTexture->GetResource(), + &resourceFlags, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + IID_PPV_ARGS(resource.wrappedResource.ReleaseAndGetAddressOf())); + if (FAILED(hr) || resource.wrappedResource == nullptr) { + m_lastError = HrToInteropString("ID3D11On12Device::CreateWrappedResource(source)", hr); + ClearSourceTextures(); + return false; + } + + Microsoft::WRL::ComPtr dxgiSurface = {}; + hr = resource.wrappedResource.As(&dxgiSurface); + if (FAILED(hr) || dxgiSurface == nullptr) { + m_lastError = HrToInteropString("ID3D11Resource::QueryInterface(IDXGISurface)", hr); + ClearSourceTextures(); + return false; + } + + const D2D1_BITMAP_PROPERTIES1 bitmapProperties = + BuildD2DBitmapProperties( + nativeTexture->GetDesc().Format, + D2D1_BITMAP_OPTIONS_NONE); + hr = m_d2dDeviceContext->CreateBitmapFromDxgiSurface( + dxgiSurface.Get(), + &bitmapProperties, + resource.bitmap.ReleaseAndGetAddressOf()); + if (FAILED(hr) || resource.bitmap == nullptr) { + m_lastError = HrToInteropString("ID2D1DeviceContext::CreateBitmapFromDxgiSurface(source)", hr); + ClearSourceTextures(); + return false; + } + + m_activeBitmaps.emplace(resource.key, resource.bitmap); + m_activeSourceTextures.push_back(std::move(resource)); + } + + m_lastError.clear(); + return true; +} + +void D3D12WindowInteropContext::ClearSourceTextures() { + m_activeBitmaps.clear(); + m_activeSourceTextures.clear(); +} + +bool D3D12WindowInteropContext::ResolveInteropBitmap( + const ::XCEngine::UI::UITextureHandle& texture, + Microsoft::WRL::ComPtr& outBitmap) const { + outBitmap.Reset(); + if (!IsInteropTextureHandle(texture)) { + return false; + } + + const auto found = m_activeBitmaps.find(texture.resourceHandle); + if (found == m_activeBitmaps.end() || found->second == nullptr) { + return false; + } + + outBitmap = found->second; + return true; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowInteropSupport.h b/new_editor/app/Rendering/D3D12/D3D12WindowInteropSupport.h new file mode 100644 index 00000000..69422600 --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12WindowInteropSupport.h @@ -0,0 +1,19 @@ +#pragma once + +#include "D3D12WindowInteropContext.h" + +namespace XCEngine::UI::Editor::Host::InteropSupport { + +std::string HrToInteropString(const char* operation, HRESULT hr); + +D2D1_BITMAP_PROPERTIES1 BuildD2DBitmapProperties( + DXGI_FORMAT format, + D2D1_BITMAP_OPTIONS options); + +bool IsInteropTextureHandle(const ::XCEngine::UI::UITextureHandle& texture); + +void CollectInteropTextureHandles( + const ::XCEngine::UI::UIDrawData& drawData, + std::vector<::XCEngine::UI::UITextureHandle>& outTextures); + +} // namespace XCEngine::UI::Editor::Host::InteropSupport diff --git a/new_editor/app/Host/D3D12WindowRenderLoop.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp similarity index 100% rename from new_editor/app/Host/D3D12WindowRenderLoop.cpp rename to new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp diff --git a/new_editor/app/Host/D3D12WindowRenderLoop.h b/new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h similarity index 93% rename from new_editor/app/Host/D3D12WindowRenderLoop.h rename to new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h index 944cbd77..f8dd4655 100644 --- a/new_editor/app/Host/D3D12WindowRenderLoop.h +++ b/new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h @@ -1,7 +1,7 @@ #pragma once -#include "D3D12WindowRenderer.h" -#include "NativeRenderer.h" +#include +#include #include diff --git a/new_editor/app/Host/D3D12WindowRenderer.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp similarity index 100% rename from new_editor/app/Host/D3D12WindowRenderer.cpp rename to new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp diff --git a/new_editor/app/Host/D3D12WindowRenderer.h b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h similarity index 100% rename from new_editor/app/Host/D3D12WindowRenderer.h rename to new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp new file mode 100644 index 00000000..06c8ccf2 --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp @@ -0,0 +1,8 @@ +#include "D3D12WindowSwapChainPresenter.h" + +namespace XCEngine::UI::Editor::Host { + +// D3D12WindowSwapChainPresenter implementations are split by responsibility +// into lifecycle, resize, back-buffer, and presentation translation units. + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/D3D12WindowSwapChainPresenter.h b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.h similarity index 100% rename from new_editor/app/Host/D3D12WindowSwapChainPresenter.h rename to new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.h diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterBackBuffers.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterBackBuffers.cpp new file mode 100644 index 00000000..21578b3a --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterBackBuffers.cpp @@ -0,0 +1,92 @@ +#include "D3D12WindowSwapChainPresenter.h" + +namespace XCEngine::UI::Editor::Host { + +using ::XCEngine::RHI::D3D12SwapChain; +using ::XCEngine::RHI::D3D12Texture; +using ::XCEngine::RHI::Format; +using ::XCEngine::RHI::ResourceStates; +using ::XCEngine::RHI::ResourceViewDesc; +using ::XCEngine::RHI::ResourceViewDimension; + +void D3D12WindowSwapChainPresenter::ReleaseBackBufferCommandReferences() { + if (m_hostDevice == nullptr) { + return; + } + + for (std::uint32_t frameIndex = 0u; + frameIndex < D3D12HostDevice::kFrameContextCount; + ++frameIndex) { + auto* commandList = m_hostDevice->GetCommandList(frameIndex); + if (commandList == nullptr) { + continue; + } + + commandList->Reset(); + commandList->Close(); + } +} + +void D3D12WindowSwapChainPresenter::ReleaseBackBufferViews() { + for (auto* view : m_backBufferViews) { + if (view != nullptr) { + view->Shutdown(); + delete view; + } + } + + m_backBufferViews.clear(); + m_backBufferSurfaces.clear(); +} + +bool D3D12WindowSwapChainPresenter::RecreateBackBufferViews() { + D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); + if (m_hostDevice == nullptr || + m_hostDevice->GetRHIDevice() == nullptr || + d3d12SwapChain == nullptr) { + return false; + } + + m_backBufferViews.resize(kSwapChainBufferCount, nullptr); + m_backBufferSurfaces.resize(kSwapChainBufferCount); + + ResourceViewDesc viewDesc = {}; + viewDesc.format = static_cast(Format::R8G8B8A8_UNorm); + viewDesc.dimension = ResourceViewDimension::Texture2D; + + for (std::uint32_t backBufferIndex = 0u; + backBufferIndex < kSwapChainBufferCount; + ++backBufferIndex) { + D3D12Texture* backBufferTexture = d3d12SwapChain->TryGetBackBuffer(backBufferIndex); + if (backBufferTexture == nullptr) { + ReleaseBackBufferViews(); + m_lastError = "RecreateBackBufferViews could not resolve swap chain back buffer " + + std::to_string(backBufferIndex) + "."; + return false; + } + + m_backBufferViews[backBufferIndex] = m_hostDevice->GetRHIDevice()->CreateRenderTargetView( + backBufferTexture, + viewDesc); + if (m_backBufferViews[backBufferIndex] == nullptr) { + ReleaseBackBufferViews(); + m_lastError = "RecreateBackBufferViews failed to create RTV for swap chain back buffer " + + std::to_string(backBufferIndex) + "."; + return false; + } + + ::XCEngine::Rendering::RenderSurface& surface = m_backBufferSurfaces[backBufferIndex]; + surface = ::XCEngine::Rendering::RenderSurface( + static_cast(m_width), + static_cast(m_height)); + surface.SetColorAttachment(m_backBufferViews[backBufferIndex]); + surface.SetAutoTransitionEnabled(false); + surface.SetColorStateBefore(ResourceStates::RenderTarget); + surface.SetColorStateAfter(ResourceStates::RenderTarget); + } + + m_lastError.clear(); + return true; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterLifecycle.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterLifecycle.cpp new file mode 100644 index 00000000..4af405a4 --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterLifecycle.cpp @@ -0,0 +1,170 @@ +#include "D3D12WindowSwapChainPresenter.h" + +namespace XCEngine::UI::Editor::Host { + +using ::XCEngine::RHI::D3D12SwapChain; +using ::XCEngine::RHI::D3D12Texture; +using ::XCEngine::RHI::RHISwapChain; +using ::XCEngine::RHI::SwapChainDesc; + +bool D3D12WindowSwapChainPresenter::Initialize( + D3D12HostDevice& hostDevice, + HWND hwnd, + int width, + int height) { + Shutdown(); + + if (hwnd == nullptr || width <= 0 || height <= 0) { + m_lastError = "Initialize rejected an invalid hwnd or size."; + return false; + } + + if (hostDevice.GetRHIDevice() == nullptr || hostDevice.GetRHICommandQueue() == nullptr) { + m_lastError = "Initialize requires an initialized host D3D12 device."; + return false; + } + + m_hwnd = hwnd; + m_width = width; + m_height = height; + m_hostDevice = &hostDevice; + + if (!CreateSwapChain(width, height)) { + Shutdown(); + return false; + } + + m_lastError.clear(); + return true; +} + +bool D3D12WindowSwapChainPresenter::CreateSwapChain(int width, int height) { + if (m_hostDevice == nullptr || + m_hostDevice->GetRHIDevice() == nullptr || + m_hostDevice->GetRHICommandQueue() == nullptr) { + m_lastError = "CreateSwapChain requires an initialized host D3D12 device."; + return false; + } + + SwapChainDesc swapChainDesc = {}; + swapChainDesc.windowHandle = m_hwnd; + swapChainDesc.width = static_cast(width); + swapChainDesc.height = static_cast(height); + swapChainDesc.bufferCount = kSwapChainBufferCount; + m_swapChain = m_hostDevice->GetRHIDevice()->CreateSwapChain( + swapChainDesc, + m_hostDevice->GetRHICommandQueue()); + if (m_swapChain == nullptr || GetD3D12SwapChain() == nullptr) { + m_lastError = "Failed to create the D3D12 swap chain."; + return false; + } + + ConfigureFrameLatency(); + + if (!RecreateBackBufferViews()) { + m_lastError = "Failed to create swap chain back buffer views."; + return false; + } + + m_width = width; + m_height = height; + return true; +} + +void D3D12WindowSwapChainPresenter::ConfigureFrameLatency() { + D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); + if (d3d12SwapChain == nullptr) { + return; + } + + auto* nativeSwapChain = + static_cast(d3d12SwapChain->GetNativeHandle()); + if (nativeSwapChain == nullptr) { + return; + } + + nativeSwapChain->SetMaximumFrameLatency(1u); +} + +void D3D12WindowSwapChainPresenter::DestroySwapChain() { + ReleaseBackBufferViews(); + + if (m_swapChain != nullptr) { + m_swapChain->Shutdown(); + delete m_swapChain; + m_swapChain = nullptr; + } +} + +bool D3D12WindowSwapChainPresenter::RecreateSwapChain(int width, int height) { + DestroySwapChain(); + m_hostDevice->ResetFrameTracking(); + return CreateSwapChain(width, height); +} + +void D3D12WindowSwapChainPresenter::Shutdown() { + if (m_hostDevice != nullptr) { + m_hostDevice->WaitForGpuIdle(); + } + + DestroySwapChain(); + + m_hwnd = nullptr; + m_width = 0; + m_height = 0; + m_hostDevice = nullptr; + m_lastError.clear(); +} + +const std::string& D3D12WindowSwapChainPresenter::GetLastError() const { + return m_lastError; +} + +RHISwapChain* D3D12WindowSwapChainPresenter::GetSwapChain() const { + return m_swapChain; +} + +const ::XCEngine::Rendering::RenderSurface* +D3D12WindowSwapChainPresenter::GetCurrentRenderSurface() const { + if (m_swapChain == nullptr) { + return nullptr; + } + + const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); + if (backBufferIndex >= m_backBufferSurfaces.size()) { + return nullptr; + } + + return &m_backBufferSurfaces[backBufferIndex]; +} + +const D3D12Texture* D3D12WindowSwapChainPresenter::GetCurrentBackBufferTexture() const { + if (m_swapChain == nullptr) { + return nullptr; + } + + return GetBackBufferTexture(m_swapChain->GetCurrentBackBufferIndex()); +} + +const D3D12Texture* D3D12WindowSwapChainPresenter::GetBackBufferTexture(std::uint32_t index) const { + const D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); + if (d3d12SwapChain == nullptr) { + return nullptr; + } + + return d3d12SwapChain->TryGetBackBuffer(index); +} + +std::uint32_t D3D12WindowSwapChainPresenter::GetBackBufferCount() const { + return kSwapChainBufferCount; +} + +std::uint32_t D3D12WindowSwapChainPresenter::GetCurrentBackBufferIndex() const { + return m_swapChain != nullptr ? m_swapChain->GetCurrentBackBufferIndex() : 0u; +} + +D3D12SwapChain* D3D12WindowSwapChainPresenter::GetD3D12SwapChain() const { + return m_swapChain != nullptr ? static_cast(m_swapChain) : nullptr; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterPresentation.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterPresentation.cpp new file mode 100644 index 00000000..db9a0c0f --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterPresentation.cpp @@ -0,0 +1,50 @@ +#include "D3D12WindowSwapChainPresenter.h" + +#include + +namespace XCEngine::UI::Editor::Host { + +bool D3D12WindowSwapChainPresenter::PreparePresentSurface( + const ::XCEngine::Rendering::RenderContext& renderContext) { + if (!renderContext.IsValid() || renderContext.commandList == nullptr) { + m_lastError = "PreparePresentSurface requires a valid render context."; + return false; + } + + if (m_swapChain == nullptr) { + m_lastError = "PreparePresentSurface requires an initialized swap chain."; + return false; + } + + const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); + if (backBufferIndex >= m_backBufferViews.size() || + m_backBufferViews[backBufferIndex] == nullptr) { + std::ostringstream error = {}; + error << "PreparePresentSurface could not find the current swap chain RTV. index=" + << backBufferIndex + << " rtvCount=" + << m_backBufferViews.size(); + m_lastError = error.str(); + return false; + } + + renderContext.commandList->TransitionBarrier( + m_backBufferViews[backBufferIndex], + ::XCEngine::RHI::ResourceStates::Present, + ::XCEngine::RHI::ResourceStates::RenderTarget); + m_lastError.clear(); + return true; +} + +bool D3D12WindowSwapChainPresenter::PresentFrame() { + if (m_swapChain == nullptr) { + m_lastError = "PresentFrame requires an initialized swap chain."; + return false; + } + + m_swapChain->Present(0, 0); + m_lastError.clear(); + return true; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterResize.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterResize.cpp new file mode 100644 index 00000000..c6e1700f --- /dev/null +++ b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterResize.cpp @@ -0,0 +1,100 @@ +#include "D3D12WindowSwapChainPresenter.h" + +#include + +namespace XCEngine::UI::Editor::Host { + +using ::XCEngine::RHI::D3D12SwapChain; +using ::XCEngine::RHI::D3D12Texture; + +bool D3D12WindowSwapChainPresenter::Resize(int width, int height) { + if (width <= 0 || height <= 0 || m_swapChain == nullptr || m_hostDevice == nullptr) { + return false; + } + + if (m_width == width && m_height == height) { + m_lastError.clear(); + return true; + } + + m_hostDevice->WaitForGpuIdle(); + ReleaseBackBufferCommandReferences(); + ReleaseBackBufferViews(); + + D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); + if (d3d12SwapChain == nullptr) { + m_lastError = "Resize could not resolve the native D3D12 swap chain."; + return false; + } + + d3d12SwapChain->Resize( + static_cast(width), + static_cast(height)); + const HRESULT resizeHr = d3d12SwapChain->GetLastResizeResult(); + if (FAILED(resizeHr)) { + if (RecreateSwapChain(width, height)) { + m_lastError.clear(); + return true; + } + + std::ostringstream error = {}; + error << "ResizeBuffers failed. requested=" + << width + << 'x' + << height + << " hr=0x" + << std::uppercase + << std::hex + << static_cast(resizeHr); + m_lastError = error.str(); + return false; + } + + const D3D12Texture* backBufferTexture = GetBackBufferTexture(0u); + if (backBufferTexture == nullptr) { + if (RecreateSwapChain(width, height)) { + m_lastError.clear(); + return true; + } + + m_lastError = "Resize failed to restore swap chain back buffers after ResizeBuffers."; + return false; + } + + m_width = static_cast(backBufferTexture->GetWidth()); + m_height = static_cast(backBufferTexture->GetHeight()); + m_hostDevice->ResetFrameTracking(); + if (!RecreateBackBufferViews()) { + if (RecreateSwapChain(width, height)) { + m_lastError.clear(); + return true; + } + + m_lastError = "Resize failed to recreate swap chain back buffer views."; + return false; + } + + if (m_width != width || m_height != height) { + if (RecreateSwapChain(width, height)) { + m_lastError.clear(); + return true; + } + + std::ostringstream error = {}; + error << "Resize ended with an unexpected swap chain size. requested=" + << width + << 'x' + << height + << " actual=" + << m_width + << 'x' + << m_height; + m_lastError = error.str(); + return false; + } + + m_lastError.clear(); + return true; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/AutoScreenshot.cpp b/new_editor/app/Rendering/Native/AutoScreenshot.cpp similarity index 80% rename from new_editor/app/Host/AutoScreenshot.cpp rename to new_editor/app/Rendering/Native/AutoScreenshot.cpp index 54936063..8b0da9e9 100644 --- a/new_editor/app/Host/AutoScreenshot.cpp +++ b/new_editor/app/Rendering/Native/AutoScreenshot.cpp @@ -1,61 +1,20 @@ #include "AutoScreenshot.h" #include "NativeRenderer.h" +#include "Support/EnvironmentFlags.h" +#include "Support/ExecutablePath.h" #include -#include -#include #include #include #include -#include - -#include namespace XCEngine::UI::Editor::Host { namespace { -bool IsAutoCaptureOnStartupEnabled() { - const char* value = std::getenv("XCUI_AUTO_CAPTURE_ON_STARTUP"); - if (value == nullptr || value[0] == '\0') { - return false; - } - - std::string normalized = value; - for (char& character : normalized) { - character = static_cast(std::tolower(static_cast(character))); - } - - return normalized != "0" && - normalized != "false" && - normalized != "off" && - normalized != "no"; -} - -std::filesystem::path GetExecutableDirectory() { - std::vector buffer(MAX_PATH); - while (true) { - const DWORD copied = ::GetModuleFileNameW( - nullptr, - buffer.data(), - static_cast(buffer.size())); - if (copied == 0u) { - return std::filesystem::current_path().lexically_normal(); - } - - if (copied < buffer.size() - 1u) { - return std::filesystem::path(std::wstring(buffer.data(), copied)) - .parent_path() - .lexically_normal(); - } - - buffer.resize(buffer.size() * 2u); - } -} - std::filesystem::path ResolveBuildCaptureRoot(const std::filesystem::path& requestedCaptureRoot) { - std::filesystem::path captureRoot = GetExecutableDirectory() / "captures"; + std::filesystem::path captureRoot = Support::GetExecutableDirectory() / "captures"; const std::filesystem::path scenarioPath = requestedCaptureRoot.parent_path().filename(); if (!scenarioPath.empty() && scenarioPath != "captures") { captureRoot /= scenarioPath; @@ -75,7 +34,7 @@ void AutoScreenshotController::Initialize(const std::filesystem::path& captureRo m_pendingReason.clear(); m_lastCaptureSummary = "Output: " + m_captureRoot.string(); m_lastCaptureError.clear(); - if (IsAutoCaptureOnStartupEnabled()) { + if (Support::IsEnvironmentFlagEnabled("XCUI_AUTO_CAPTURE_ON_STARTUP")) { RequestCapture("startup"); } } diff --git a/new_editor/app/Host/AutoScreenshot.h b/new_editor/app/Rendering/Native/AutoScreenshot.h similarity index 100% rename from new_editor/app/Host/AutoScreenshot.h rename to new_editor/app/Rendering/Native/AutoScreenshot.h diff --git a/new_editor/app/Rendering/Native/NativeRenderer.cpp b/new_editor/app/Rendering/Native/NativeRenderer.cpp new file mode 100644 index 00000000..8f5e71a2 --- /dev/null +++ b/new_editor/app/Rendering/Native/NativeRenderer.cpp @@ -0,0 +1,9 @@ +#include "NativeRendererSupport.h" + +namespace XCEngine::UI::Editor::Host { + +// NativeRenderer implementations are split by responsibility into dedicated +// translation units to keep lifecycle, texture, text, capture, and rendering +// codepaths isolated. + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/NativeRenderer.h b/new_editor/app/Rendering/Native/NativeRenderer.h similarity index 97% rename from new_editor/app/Host/NativeRenderer.h rename to new_editor/app/Rendering/Native/NativeRenderer.h index cfa89214..cd230ec2 100644 --- a/new_editor/app/Host/NativeRenderer.h +++ b/new_editor/app/Rendering/Native/NativeRenderer.h @@ -4,8 +4,8 @@ #define NOMINMAX #endif -#include "D3D12WindowInteropContext.h" -#include "D3D12WindowRenderer.h" +#include +#include #include diff --git a/new_editor/app/Rendering/Native/NativeRendererCapture.cpp b/new_editor/app/Rendering/Native/NativeRendererCapture.cpp new file mode 100644 index 00000000..6994bf7e --- /dev/null +++ b/new_editor/app/Rendering/Native/NativeRendererCapture.cpp @@ -0,0 +1,154 @@ +#include "NativeRendererSupport.h" + +namespace XCEngine::UI::Editor::Host { + +using namespace NativeRendererSupport; + +bool NativeRenderer::CaptureToPng( + const ::XCEngine::UI::UIDrawData& drawData, + UINT width, + UINT height, + const std::filesystem::path& outputPath, + std::string& outError) { + outError.clear(); + if (width == 0 || height == 0) { + outError = "CaptureToPng rejected an empty render size."; + return false; + } + + if (!m_d2dFactory || !m_dwriteFactory) { + outError = "CaptureToPng requires an initialized NativeRenderer."; + return false; + } + + if (!EnsureWicFactory(outError)) { + return false; + } + + std::error_code errorCode = {}; + std::filesystem::create_directories(outputPath.parent_path(), errorCode); + if (errorCode) { + outError = "Failed to create screenshot directory: " + outputPath.parent_path().string(); + return false; + } + + Microsoft::WRL::ComPtr bitmap; + HRESULT hr = m_wicFactory->CreateBitmap( + width, + height, + GUID_WICPixelFormat32bppPBGRA, + WICBitmapCacheOnLoad, + bitmap.ReleaseAndGetAddressOf()); + if (FAILED(hr)) { + outError = HrToString("IWICImagingFactory::CreateBitmap", hr); + return false; + } + + const D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties = D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), + kBaseDpi, + kBaseDpi); + + Microsoft::WRL::ComPtr offscreenRenderTarget; + hr = m_d2dFactory->CreateWicBitmapRenderTarget( + bitmap.Get(), + renderTargetProperties, + offscreenRenderTarget.ReleaseAndGetAddressOf()); + if (FAILED(hr)) { + outError = HrToString("ID2D1Factory::CreateWicBitmapRenderTarget", hr); + return false; + } + + Microsoft::WRL::ComPtr offscreenBrush; + hr = offscreenRenderTarget->CreateSolidColorBrush( + D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), + offscreenBrush.ReleaseAndGetAddressOf()); + if (FAILED(hr)) { + outError = HrToString("ID2D1RenderTarget::CreateSolidColorBrush", hr); + return false; + } + + const bool rendered = RenderToTarget(*offscreenRenderTarget.Get(), *offscreenBrush.Get(), drawData); + hr = offscreenRenderTarget->EndDraw(); + if (!rendered || FAILED(hr)) { + outError = HrToString("ID2D1RenderTarget::EndDraw", hr); + return false; + } + + const std::wstring wideOutputPath = outputPath.wstring(); + Microsoft::WRL::ComPtr stream; + hr = m_wicFactory->CreateStream(stream.ReleaseAndGetAddressOf()); + if (FAILED(hr)) { + outError = HrToString("IWICImagingFactory::CreateStream", hr); + return false; + } + + hr = stream->InitializeFromFilename(wideOutputPath.c_str(), GENERIC_WRITE); + if (FAILED(hr)) { + outError = HrToString("IWICStream::InitializeFromFilename", hr); + return false; + } + + Microsoft::WRL::ComPtr encoder; + hr = m_wicFactory->CreateEncoder(GUID_ContainerFormatPng, nullptr, encoder.ReleaseAndGetAddressOf()); + if (FAILED(hr)) { + outError = HrToString("IWICImagingFactory::CreateEncoder", hr); + return false; + } + + hr = encoder->Initialize(stream.Get(), WICBitmapEncoderNoCache); + if (FAILED(hr)) { + outError = HrToString("IWICBitmapEncoder::Initialize", hr); + return false; + } + + Microsoft::WRL::ComPtr frame; + Microsoft::WRL::ComPtr propertyBag; + hr = encoder->CreateNewFrame(frame.ReleaseAndGetAddressOf(), propertyBag.ReleaseAndGetAddressOf()); + if (FAILED(hr)) { + outError = HrToString("IWICBitmapEncoder::CreateNewFrame", hr); + return false; + } + + hr = frame->Initialize(propertyBag.Get()); + if (FAILED(hr)) { + outError = HrToString("IWICBitmapFrameEncode::Initialize", hr); + return false; + } + + hr = frame->SetSize(width, height); + if (FAILED(hr)) { + outError = HrToString("IWICBitmapFrameEncode::SetSize", hr); + return false; + } + + WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppPBGRA; + hr = frame->SetPixelFormat(&pixelFormat); + if (FAILED(hr)) { + outError = HrToString("IWICBitmapFrameEncode::SetPixelFormat", hr); + return false; + } + + hr = frame->WriteSource(bitmap.Get(), nullptr); + if (FAILED(hr)) { + outError = HrToString("IWICBitmapFrameEncode::WriteSource", hr); + return false; + } + + hr = frame->Commit(); + if (FAILED(hr)) { + outError = HrToString("IWICBitmapFrameEncode::Commit", hr); + return false; + } + + hr = encoder->Commit(); + if (FAILED(hr)) { + outError = HrToString("IWICBitmapEncoder::Commit", hr); + return false; + } + + return true; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/Native/NativeRendererDraw.cpp b/new_editor/app/Rendering/Native/NativeRendererDraw.cpp new file mode 100644 index 00000000..f0ff52a8 --- /dev/null +++ b/new_editor/app/Rendering/Native/NativeRendererDraw.cpp @@ -0,0 +1,260 @@ +#include "NativeRendererSupport.h" + +#include +#include + +namespace XCEngine::UI::Editor::Host { + +using namespace NativeRendererSupport; + +bool NativeRenderer::RenderToTarget( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawData& drawData) { + renderTarget.SetDpi(kBaseDpi, kBaseDpi); + renderTarget.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); + renderTarget.BeginDraw(); + renderTarget.Clear(D2D1::ColorF(0.04f, 0.05f, 0.06f, 1.0f)); + + std::vector clipStack = {}; + for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) { + for (const ::XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) { + RenderCommand(renderTarget, solidBrush, command, clipStack); + } + } + + while (!clipStack.empty()) { + renderTarget.PopAxisAlignedClip(); + clipStack.pop_back(); + } + + return true; +} + +void NativeRenderer::RenderCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command, + std::vector& clipStack) { + solidBrush.SetColor(ToD2DColor(command.color)); + const float dpiScale = ClampDpiScale(m_dpiScale); + + switch (command.type) { + case ::XCEngine::UI::UIDrawCommandType::FilledRect: { + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; + if (command.rounding > 0.0f) { + renderTarget.FillRoundedRectangle( + D2D1::RoundedRect(rect, rounding, rounding), + &solidBrush); + } else { + renderTarget.FillRectangle(rect, &solidBrush); + } + break; + } + case ::XCEngine::UI::UIDrawCommandType::FilledRectLinearGradient: { + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; + + const D2D1_GRADIENT_STOP stops[2] = { + D2D1::GradientStop(0.0f, ToD2DColor(command.color)), + D2D1::GradientStop(1.0f, ToD2DColor(command.secondaryColor)) + }; + + Microsoft::WRL::ComPtr stopCollection; + HRESULT hr = renderTarget.CreateGradientStopCollection( + stops, + 2u, + stopCollection.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !stopCollection) { + break; + } + + const D2D1_POINT_2F startPoint = + command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical + ? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.top) + : D2D1::Point2F(rect.left, (rect.top + rect.bottom) * 0.5f); + const D2D1_POINT_2F endPoint = + command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical + ? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.bottom) + : D2D1::Point2F(rect.right, (rect.top + rect.bottom) * 0.5f); + + Microsoft::WRL::ComPtr gradientBrush; + hr = renderTarget.CreateLinearGradientBrush( + D2D1::LinearGradientBrushProperties(startPoint, endPoint), + stopCollection.Get(), + gradientBrush.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !gradientBrush) { + break; + } + + if (command.rounding > 0.0f) { + renderTarget.FillRoundedRectangle( + D2D1::RoundedRect(rect, rounding, rounding), + gradientBrush.Get()); + } else { + renderTarget.FillRectangle(rect, gradientBrush.Get()); + } + break; + } + case ::XCEngine::UI::UIDrawCommandType::RectOutline: { + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; + const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; + if (command.rounding > 0.0f) { + renderTarget.DrawRoundedRectangle( + D2D1::RoundedRect(rect, rounding, rounding), + &solidBrush, + thickness); + } else { + renderTarget.DrawRectangle(rect, &solidBrush, thickness); + } + break; + } + case ::XCEngine::UI::UIDrawCommandType::Line: { + const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; + const float pixelOffset = ResolveStrokePixelOffset(thickness); + const D2D1_POINT_2F start = ToD2DPoint(command.position, dpiScale, pixelOffset); + const D2D1_POINT_2F end = ToD2DPoint(command.uvMin, dpiScale, pixelOffset); + renderTarget.DrawLine(start, end, &solidBrush, thickness); + break; + } + case ::XCEngine::UI::UIDrawCommandType::FilledTriangle: { + Microsoft::WRL::ComPtr geometry; + HRESULT hr = m_d2dFactory->CreatePathGeometry(geometry.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !geometry) { + break; + } + + Microsoft::WRL::ComPtr sink; + hr = geometry->Open(sink.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !sink) { + break; + } + + const D2D1_POINT_2F a = ToD2DPoint(command.position, dpiScale); + const D2D1_POINT_2F b = ToD2DPoint(command.uvMin, dpiScale); + const D2D1_POINT_2F c = ToD2DPoint(command.uvMax, dpiScale); + const D2D1_POINT_2F points[2] = { b, c }; + sink->BeginFigure(a, D2D1_FIGURE_BEGIN_FILLED); + sink->AddLines(points, 2u); + sink->EndFigure(D2D1_FIGURE_END_CLOSED); + hr = sink->Close(); + if (FAILED(hr)) { + break; + } + + renderTarget.FillGeometry(geometry.Get(), &solidBrush); + break; + } + case ::XCEngine::UI::UIDrawCommandType::FilledCircle: { + const float radius = command.radius * dpiScale; + renderTarget.FillEllipse( + D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius), + &solidBrush); + break; + } + case ::XCEngine::UI::UIDrawCommandType::CircleOutline: { + const float radius = command.radius * dpiScale; + const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; + renderTarget.DrawEllipse( + D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius), + &solidBrush, + thickness); + break; + } + case ::XCEngine::UI::UIDrawCommandType::Text: { + if (command.text.empty()) { + break; + } + + const float fontSize = ResolveFontSize(command.fontSize); + const float scaledFontSize = fontSize * dpiScale; + IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize); + if (textFormat == nullptr) { + break; + } + + const std::wstring text = Utf8ToWide(command.text); + if (text.empty()) { + break; + } + + const D2D1_SIZE_F targetSize = renderTarget.GetSize(); + const float originX = SnapToPixel(command.position.x, dpiScale); + const float originY = SnapToPixel(command.position.y, dpiScale); + const float lineHeight = std::ceil(scaledFontSize * 1.6f); + const D2D1_RECT_F layoutRect = D2D1::RectF( + originX, + originY, + targetSize.width, + originY + lineHeight); + renderTarget.DrawTextW( + text.c_str(), + static_cast(text.size()), + textFormat, + layoutRect, + &solidBrush, + D2D1_DRAW_TEXT_OPTIONS_CLIP, + DWRITE_MEASURING_MODE_GDI_NATURAL); + break; + } + case ::XCEngine::UI::UIDrawCommandType::Image: { + if (!command.texture.IsValid()) { + break; + } + + Microsoft::WRL::ComPtr bitmap; + if (command.texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView) { + if (!ResolveInteropBitmap(command.texture, bitmap) || !bitmap) { + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); + break; + } + } else { + auto* texture = reinterpret_cast(command.texture.nativeHandle); + if (texture == nullptr || m_liveTextures.find(texture) == m_liveTextures.end()) { + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); + break; + } + + if (!ResolveTextureBitmap(renderTarget, *texture, bitmap) || !bitmap) { + break; + } + } + + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + const float sourceWidth = static_cast(command.texture.width); + const float sourceHeight = static_cast(command.texture.height); + const float sourceLeft = sourceWidth * std::clamp(command.uvMin.x, 0.0f, 1.0f); + const float sourceTop = sourceHeight * std::clamp(command.uvMin.y, 0.0f, 1.0f); + const float sourceRight = sourceWidth * std::clamp(command.uvMax.x, 0.0f, 1.0f); + const float sourceBottom = sourceHeight * std::clamp(command.uvMax.y, 0.0f, 1.0f); + renderTarget.DrawBitmap( + bitmap.Get(), + rect, + std::clamp(command.color.a, 0.0f, 1.0f), + D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, + D2D1::RectF(sourceLeft, sourceTop, sourceRight, sourceBottom)); + break; + } + case ::XCEngine::UI::UIDrawCommandType::PushClipRect: { + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + renderTarget.PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + clipStack.push_back(rect); + break; + } + case ::XCEngine::UI::UIDrawCommandType::PopClipRect: { + if (!clipStack.empty()) { + renderTarget.PopAxisAlignedClip(); + clipStack.pop_back(); + } + break; + } + default: + break; + } +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/Native/NativeRendererLifecycle.cpp b/new_editor/app/Rendering/Native/NativeRendererLifecycle.cpp new file mode 100644 index 00000000..8744e0ca --- /dev/null +++ b/new_editor/app/Rendering/Native/NativeRendererLifecycle.cpp @@ -0,0 +1,255 @@ +#include "NativeRendererSupport.h" + +namespace XCEngine::UI::Editor::Host { + +using namespace NativeRendererSupport; + +bool NativeRenderer::Initialize(HWND hwnd) { + Shutdown(); + + if (hwnd == nullptr) { + m_lastRenderError = "Initialize rejected a null hwnd."; + return false; + } + + m_hwnd = hwnd; + D2D1_FACTORY_OPTIONS factoryOptions = {}; +#ifdef _DEBUG + factoryOptions.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; +#endif + HRESULT hr = D2D1CreateFactory( + D2D1_FACTORY_TYPE_SINGLE_THREADED, + __uuidof(ID2D1Factory1), + &factoryOptions, + reinterpret_cast(m_d2dFactory.ReleaseAndGetAddressOf())); + if (FAILED(hr)) { + m_lastRenderError = HrToString("D2D1CreateFactory", hr); + Shutdown(); + return false; + } + + hr = DWriteCreateFactory( + DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + reinterpret_cast(m_dwriteFactory.ReleaseAndGetAddressOf())); + if (FAILED(hr)) { + m_lastRenderError = HrToString("DWriteCreateFactory", hr); + Shutdown(); + return false; + } + + m_lastRenderError.clear(); + return true; +} + +void NativeRenderer::Shutdown() { + DetachWindowRenderer(); + while (!m_liveTextures.empty()) { + auto it = m_liveTextures.begin(); + delete *it; + m_liveTextures.erase(it); + } + m_textFormats.clear(); + m_solidBrush.Reset(); + m_renderTarget.Reset(); + m_wicFactory.Reset(); + m_dwriteFactory.Reset(); + m_d2dFactory.Reset(); + if (m_wicComInitialized) { + CoUninitialize(); + m_wicComInitialized = false; + } + m_hwnd = nullptr; +} + +void NativeRenderer::SetDpiScale(float dpiScale) { + m_dpiScale = ClampDpiScale(dpiScale); + if (m_renderTarget) { + m_renderTarget->SetDpi(kBaseDpi, kBaseDpi); + } +} + +float NativeRenderer::GetDpiScale() const { + return m_dpiScale; +} + +void NativeRenderer::Resize(UINT width, UINT height) { + if (!m_renderTarget || width == 0 || height == 0) { + return; + } + + const HRESULT hr = m_renderTarget->Resize(D2D1::SizeU(width, height)); + if (hr == D2DERR_RECREATE_TARGET) { + DiscardRenderTarget(); + } +} + +bool NativeRenderer::AttachWindowRenderer(D3D12WindowRenderer& windowRenderer) { + if (m_windowRenderer != &windowRenderer) { + ReleaseWindowRendererInterop(); + m_windowRenderer = &windowRenderer; + } + + if (!EnsureWindowRendererInterop()) { + return false; + } + + DiscardRenderTarget(); + + if (m_windowInterop.HasBackBufferTargets()) { + return true; + } + + return m_windowInterop.RebuildBackBufferTargets(); +} + +void NativeRenderer::DetachWindowRenderer() { + ReleaseWindowRendererInterop(); + m_windowRenderer = nullptr; +} + +void NativeRenderer::ReleaseWindowRendererBackBufferTargets() { + m_windowInterop.ReleaseBackBufferTargets(); +} + +bool NativeRenderer::RebuildWindowRendererBackBufferTargets() { + if (!EnsureWindowRendererInterop()) { + return false; + } + + DiscardRenderTarget(); + ReleaseWindowRendererBackBufferTargets(); + return m_windowInterop.RebuildBackBufferTargets(); +} + +bool NativeRenderer::HasAttachedWindowRenderer() const { + return m_windowInterop.HasAttachedWindowRenderer(); +} + +const std::string& NativeRenderer::GetLastRenderError() const { + return m_lastRenderError; +} + +bool NativeRenderer::EnsureWindowRendererInterop() { + if (m_windowRenderer == nullptr) { + m_lastRenderError = "EnsureWindowRendererInterop requires an attached D3D12 window renderer."; + return false; + } + if (m_d2dFactory == nullptr || m_dwriteFactory == nullptr) { + m_lastRenderError = "EnsureWindowRendererInterop requires initialized D2D and DWrite factories."; + return false; + } + + const bool attached = m_windowInterop.Attach(*m_windowRenderer, *m_d2dFactory.Get()); + if (!attached) { + m_lastRenderError = m_windowInterop.GetLastError(); + } else { + m_lastRenderError.clear(); + } + return attached; +} + +void NativeRenderer::ReleaseWindowRendererInterop() { + m_windowInterop.Detach(); +} + +bool NativeRenderer::EnsureRenderTarget() { + if (!m_hwnd || !m_d2dFactory || !m_dwriteFactory) { + m_lastRenderError = "EnsureRenderTarget requires hwnd, D2D factory, and DWrite factory."; + return false; + } + + return CreateDeviceResources(); +} + +bool NativeRenderer::EnsureWicFactory(std::string& outError) { + outError.clear(); + if (m_wicFactory) { + return true; + } + + const HRESULT initHr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (FAILED(initHr) && initHr != RPC_E_CHANGED_MODE) { + outError = HrToString("CoInitializeEx", initHr); + return false; + } + if (SUCCEEDED(initHr)) { + m_wicComInitialized = true; + } + + const HRESULT factoryHr = CoCreateInstance( + CLSID_WICImagingFactory, + nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(m_wicFactory.ReleaseAndGetAddressOf())); + if (FAILED(factoryHr)) { + outError = HrToString("CoCreateInstance(CLSID_WICImagingFactory)", factoryHr); + return false; + } + + return true; +} + +void NativeRenderer::DiscardRenderTarget() { + InvalidateCachedTextureBitmaps(m_renderTarget.Get()); + m_solidBrush.Reset(); + m_renderTarget.Reset(); +} + +bool NativeRenderer::CreateDeviceResources() { + if (m_renderTarget) { + return true; + } + + RECT clientRect = {}; + GetClientRect(m_hwnd, &clientRect); + const UINT width = static_cast((std::max)(clientRect.right - clientRect.left, 1L)); + const UINT height = static_cast((std::max)(clientRect.bottom - clientRect.top, 1L)); + + const D2D1_RENDER_TARGET_PROPERTIES renderTargetProps = D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), + kBaseDpi, + kBaseDpi); + const D2D1_HWND_RENDER_TARGET_PROPERTIES hwndProps = D2D1::HwndRenderTargetProperties( + m_hwnd, + D2D1::SizeU(width, height)); + + const HRESULT renderTargetHr = m_d2dFactory->CreateHwndRenderTarget( + renderTargetProps, + hwndProps, + m_renderTarget.ReleaseAndGetAddressOf()); + if (FAILED(renderTargetHr)) { + m_lastRenderError = HrToString("ID2D1Factory::CreateHwndRenderTarget", renderTargetHr); + return false; + } + + const HRESULT brushHr = m_renderTarget->CreateSolidColorBrush( + D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), + m_solidBrush.ReleaseAndGetAddressOf()); + if (FAILED(brushHr)) { + m_lastRenderError = HrToString("ID2D1HwndRenderTarget::CreateSolidColorBrush", brushHr); + DiscardRenderTarget(); + return false; + } + + m_renderTarget->SetDpi(kBaseDpi, kBaseDpi); + m_renderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); + m_lastRenderError.clear(); + return true; +} + +void NativeRenderer::InvalidateCachedTextureBitmaps(const ID2D1RenderTarget* renderTarget) { + for (NativeTextureResource* texture : m_liveTextures) { + if (texture == nullptr) { + continue; + } + + if (renderTarget == nullptr || texture->cachedTarget == renderTarget) { + texture->cachedBitmap.Reset(); + texture->cachedTarget = nullptr; + } + } +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/Native/NativeRendererRendering.cpp b/new_editor/app/Rendering/Native/NativeRendererRendering.cpp new file mode 100644 index 00000000..8c40e1de --- /dev/null +++ b/new_editor/app/Rendering/Native/NativeRendererRendering.cpp @@ -0,0 +1,150 @@ +#include "NativeRendererSupport.h" + +namespace XCEngine::UI::Editor::Host { + +using namespace NativeRendererSupport; + +bool NativeRenderer::Render(const ::XCEngine::UI::UIDrawData& drawData) { + if (!EnsureRenderTarget()) { + if (m_lastRenderError.empty()) { + m_lastRenderError = "EnsureRenderTarget failed."; + } + return false; + } + + const bool rendered = RenderToTarget(*m_renderTarget.Get(), *m_solidBrush.Get(), drawData); + const HRESULT hr = m_renderTarget->EndDraw(); + if (hr == D2DERR_RECREATE_TARGET) { + m_lastRenderError = HrToString("ID2D1HwndRenderTarget::EndDraw", hr); + DiscardRenderTarget(); + return false; + } + + if (!rendered || FAILED(hr)) { + m_lastRenderError = HrToString("ID2D1HwndRenderTarget::EndDraw", hr); + return false; + } + + m_lastRenderError.clear(); + return true; +} + +bool NativeRenderer::RenderToWindowRenderer(const ::XCEngine::UI::UIDrawData& drawData) { + if (!EnsureWindowRendererInterop()) { + if (m_lastRenderError.empty()) { + m_lastRenderError = "Window renderer interop is not available."; + } + return false; + } + + if (!m_windowInterop.HasBackBufferTargets() && + !m_windowInterop.RebuildBackBufferTargets()) { + if (m_lastRenderError.empty()) { + m_lastRenderError = "Window renderer back buffer interop targets are unavailable."; + } + return false; + } + + ID3D11On12Device* d3d11On12Device = m_windowInterop.GetD3D11On12Device(); + ID3D11DeviceContext* d3d11DeviceContext = m_windowInterop.GetD3D11DeviceContext(); + ID2D1DeviceContext* d2dDeviceContext = m_windowInterop.GetD2DDeviceContext(); + ID2D1SolidColorBrush* interopBrush = m_windowInterop.GetInteropBrush(); + if (d3d11On12Device == nullptr || + d3d11DeviceContext == nullptr || + d2dDeviceContext == nullptr || + interopBrush == nullptr) { + m_lastRenderError = "Window renderer interop resources are incomplete."; + return false; + } + + const std::uint32_t backBufferIndex = m_windowInterop.GetCurrentBackBufferIndex(); + if (m_windowInterop.GetWrappedBackBufferResource(backBufferIndex) == nullptr || + m_windowInterop.GetBackBufferTargetBitmap(backBufferIndex) == nullptr) { + m_lastRenderError = "Back buffer interop target index is out of range."; + return false; + } + + if (!m_windowRenderer->PreparePresentSurface()) { + m_lastRenderError = "Failed to prepare the D3D12 present surface: " + + m_windowRenderer->GetLastError(); + return false; + } + + if (!m_windowRenderer->SubmitFrame(false)) { + m_lastRenderError = "Failed to submit the D3D12 frame before UI composition."; + return false; + } + + if (!m_windowInterop.PrepareSourceTextures(drawData)) { + ID3D11Resource* backBufferResource = + m_windowInterop.GetWrappedBackBufferResource(backBufferIndex); + if (backBufferResource != nullptr) { + d3d11On12Device->AcquireWrappedResources(&backBufferResource, 1u); + d3d11On12Device->ReleaseWrappedResources(&backBufferResource, 1u); + } + d3d11DeviceContext->Flush(); + m_windowInterop.ClearSourceTextures(); + const bool signaled = m_windowRenderer->SignalFrameCompletion(); + ReleaseWindowRendererInterop(); + if (!signaled) { + m_lastRenderError = + "Failed to signal D3D12 frame completion after interop preparation failed."; + } + return false; + } + + std::vector acquiredResources = {}; + m_windowInterop.BuildAcquiredResources(backBufferIndex, acquiredResources); + if (acquiredResources.empty()) { + m_lastRenderError = "No wrapped interop resources were prepared for UI composition."; + return false; + } + + d3d11On12Device->AcquireWrappedResources( + acquiredResources.data(), + static_cast(acquiredResources.size())); + + d2dDeviceContext->SetTarget(m_windowInterop.GetBackBufferTargetBitmap(backBufferIndex)); + const bool rendered = RenderToTarget(*d2dDeviceContext, *interopBrush, drawData); + const HRESULT hr = d2dDeviceContext->EndDraw(); + + d3d11On12Device->ReleaseWrappedResources( + acquiredResources.data(), + static_cast(acquiredResources.size())); + d3d11DeviceContext->Flush(); + d2dDeviceContext->SetTarget(nullptr); + m_windowInterop.ClearSourceTextures(); + + if (!rendered || FAILED(hr)) { + m_lastRenderError = FAILED(hr) + ? HrToString("ID2D1DeviceContext::EndDraw", hr) + : "RenderToTarget failed during D3D11On12 composition."; + const bool signaled = m_windowRenderer->SignalFrameCompletion(); + if (hr == D2DERR_RECREATE_TARGET) { + ReleaseWindowRendererBackBufferTargets(); + } else { + ReleaseWindowRendererInterop(); + } + if (!signaled) { + m_lastRenderError = + "Failed to signal D3D12 frame completion after UI composition failed."; + } + return false; + } + + if (!m_windowRenderer->SignalFrameCompletion()) { + m_lastRenderError = "Failed to signal D3D12 frame completion after UI composition."; + ReleaseWindowRendererInterop(); + return false; + } + if (!m_windowRenderer->PresentFrame()) { + m_lastRenderError = "Failed to present the D3D12 swap chain."; + ReleaseWindowRendererInterop(); + return false; + } + + m_lastRenderError.clear(); + return true; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/Native/NativeRendererSupport.h b/new_editor/app/Rendering/Native/NativeRendererSupport.h new file mode 100644 index 00000000..38d5ff04 --- /dev/null +++ b/new_editor/app/Rendering/Native/NativeRendererSupport.h @@ -0,0 +1,88 @@ +#pragma once + +#include "NativeRenderer.h" + +#include +#include +#include + +namespace XCEngine::UI::Editor::Host::NativeRendererSupport { + +inline constexpr float kBaseDpi = 96.0f; +inline constexpr float kDefaultFontSize = 16.0f; + +inline std::string HrToString(const char* operation, HRESULT hr) { + char buffer[128] = {}; + sprintf_s(buffer, "%s failed with hr=0x%08X.", operation, static_cast(hr)); + return buffer; +} + +inline float ClampDpiScale(float dpiScale) { + return dpiScale > 0.0f ? dpiScale : 1.0f; +} + +inline float ResolveFontSize(float fontSize) { + return fontSize > 0.0f ? fontSize : kDefaultFontSize; +} + +inline float SnapToPixel(float value, float dpiScale) { + const float scale = ClampDpiScale(dpiScale); + return std::round(value * scale); +} + +inline D2D1_RECT_F ToD2DRect(const ::XCEngine::UI::UIRect& rect, float dpiScale) { + const float left = SnapToPixel(rect.x, dpiScale); + const float top = SnapToPixel(rect.y, dpiScale); + const float right = SnapToPixel(rect.x + rect.width, dpiScale); + const float bottom = SnapToPixel(rect.y + rect.height, dpiScale); + return D2D1::RectF(left, top, right, bottom); +} + +inline D2D1_POINT_2F ToD2DPoint( + const ::XCEngine::UI::UIPoint& point, + float dpiScale, + float pixelOffset = 0.0f) { + return D2D1::Point2F( + SnapToPixel(point.x, dpiScale) + pixelOffset, + SnapToPixel(point.y, dpiScale) + pixelOffset); +} + +inline float ResolveStrokePixelOffset(float thickness) { + const float roundedThickness = std::round(thickness); + return std::fmod(roundedThickness, 2.0f) == 1.0f ? 0.5f : 0.0f; +} + +inline D2D1_BITMAP_PROPERTIES1 BuildD2DBitmapProperties( + DXGI_FORMAT format, + D2D1_BITMAP_OPTIONS options, + D2D1_ALPHA_MODE alphaMode = D2D1_ALPHA_MODE_IGNORE) { + return D2D1::BitmapProperties1( + options, + D2D1::PixelFormat(format, alphaMode), + kBaseDpi, + kBaseDpi); +} + +inline bool IsInteropTextureHandle(const ::XCEngine::UI::UITextureHandle& texture) { + return texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView && + texture.resourceHandle != 0u; +} + +inline void CollectInteropTextureHandles( + const ::XCEngine::UI::UIDrawData& drawData, + std::vector<::XCEngine::UI::UITextureHandle>& outTextures) { + outTextures.clear(); + std::unordered_set seenKeys = {}; + for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) { + for (const ::XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) { + if (!IsInteropTextureHandle(command.texture) || + !seenKeys.insert(command.texture.resourceHandle).second) { + continue; + } + + outTextures.push_back(command.texture); + } + } +} + +} // namespace XCEngine::UI::Editor::Host::NativeRendererSupport diff --git a/new_editor/app/Rendering/Native/NativeRendererText.cpp b/new_editor/app/Rendering/Native/NativeRendererText.cpp new file mode 100644 index 00000000..6ad7d12f --- /dev/null +++ b/new_editor/app/Rendering/Native/NativeRendererText.cpp @@ -0,0 +1,122 @@ +#include "NativeRendererSupport.h" + +#include +#include + +namespace XCEngine::UI::Editor::Host { + +using namespace NativeRendererSupport; + +float NativeRenderer::MeasureTextWidth( + const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const { + if (!m_dwriteFactory || request.text.empty()) { + return 0.0f; + } + + const std::wstring text = Utf8ToWide(request.text); + if (text.empty()) { + return 0.0f; + } + + const float dpiScale = ClampDpiScale(m_dpiScale); + const float scaledFontSize = ResolveFontSize(request.fontSize) * dpiScale; + IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize); + if (textFormat == nullptr) { + return 0.0f; + } + + Microsoft::WRL::ComPtr textLayout; + HRESULT hr = m_dwriteFactory->CreateTextLayout( + text.c_str(), + static_cast(text.size()), + textFormat, + 4096.0f, + scaledFontSize * 2.0f, + textLayout.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !textLayout) { + return 0.0f; + } + + DWRITE_TEXT_METRICS textMetrics = {}; + hr = textLayout->GetMetrics(&textMetrics); + if (FAILED(hr)) { + return 0.0f; + } + + DWRITE_OVERHANG_METRICS overhangMetrics = {}; + float width = textMetrics.widthIncludingTrailingWhitespace; + if (SUCCEEDED(textLayout->GetOverhangMetrics(&overhangMetrics))) { + width += (std::max)(overhangMetrics.left, 0.0f); + width += (std::max)(overhangMetrics.right, 0.0f); + } + + return std::ceil(width) / dpiScale; +} + +IDWriteTextFormat* NativeRenderer::GetTextFormat(float fontSize) const { + if (!m_dwriteFactory) { + return nullptr; + } + + const float resolvedFontSize = ResolveFontSize(fontSize); + const int key = static_cast(std::lround(resolvedFontSize * 10.0f)); + const auto found = m_textFormats.find(key); + if (found != m_textFormats.end()) { + return found->second.Get(); + } + + Microsoft::WRL::ComPtr textFormat; + const HRESULT hr = m_dwriteFactory->CreateTextFormat( + L"Segoe UI", + nullptr, + DWRITE_FONT_WEIGHT_REGULAR, + DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, + resolvedFontSize, + L"", + textFormat.ReleaseAndGetAddressOf()); + if (FAILED(hr)) { + return nullptr; + } + + textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); + textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR); + textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); + + IDWriteTextFormat* result = textFormat.Get(); + m_textFormats.emplace(key, std::move(textFormat)); + return result; +} + +D2D1_COLOR_F NativeRenderer::ToD2DColor(const ::XCEngine::UI::UIColor& color) { + return D2D1::ColorF(color.r, color.g, color.b, color.a); +} + +std::wstring NativeRenderer::Utf8ToWide(std::string_view text) { + if (text.empty()) { + return {}; + } + + const int sizeNeeded = MultiByteToWideChar( + CP_UTF8, + 0, + text.data(), + static_cast(text.size()), + nullptr, + 0); + if (sizeNeeded <= 0) { + return {}; + } + + std::wstring wideText(static_cast(sizeNeeded), L'\0'); + MultiByteToWideChar( + CP_UTF8, + 0, + text.data(), + static_cast(text.size()), + wideText.data(), + sizeNeeded); + return wideText; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/Native/NativeRendererTextures.cpp b/new_editor/app/Rendering/Native/NativeRendererTextures.cpp new file mode 100644 index 00000000..422db24b --- /dev/null +++ b/new_editor/app/Rendering/Native/NativeRendererTextures.cpp @@ -0,0 +1,259 @@ +#include "NativeRendererSupport.h" + +#include +#include +#include + +namespace XCEngine::UI::Editor::Host { + +using namespace NativeRendererSupport; + +bool NativeRenderer::LoadTextureFromFile( + const std::filesystem::path& path, + ::XCEngine::UI::UITextureHandle& outTexture, + std::string& outError) { + outError.clear(); + ReleaseTexture(outTexture); + + auto texture = std::make_unique(); + if (!DecodeTextureFile(path, *texture, outError)) { + outTexture = {}; + return false; + } + + outTexture.nativeHandle = reinterpret_cast(texture.get()); + outTexture.width = texture->width; + outTexture.height = texture->height; + outTexture.kind = ::XCEngine::UI::UITextureHandleKind::DescriptorHandle; + m_liveTextures.insert(texture.get()); + texture.release(); + return true; +} + +bool NativeRenderer::LoadTextureFromMemory( + const std::uint8_t* data, + std::size_t size, + ::XCEngine::UI::UITextureHandle& outTexture, + std::string& outError) { + outError.clear(); + ReleaseTexture(outTexture); + + auto texture = std::make_unique(); + if (!DecodeTextureMemory(data, size, *texture, outError)) { + outTexture = {}; + return false; + } + + outTexture.nativeHandle = reinterpret_cast(texture.get()); + outTexture.width = texture->width; + outTexture.height = texture->height; + outTexture.kind = ::XCEngine::UI::UITextureHandleKind::DescriptorHandle; + m_liveTextures.insert(texture.get()); + texture.release(); + return true; +} + +void NativeRenderer::ReleaseTexture(::XCEngine::UI::UITextureHandle& texture) { + if (!texture.IsValid()) { + texture = {}; + return; + } + + auto* resource = reinterpret_cast(texture.nativeHandle); + if (resource != nullptr) { + const auto found = m_liveTextures.find(resource); + if (found != m_liveTextures.end()) { + m_liveTextures.erase(found); + delete resource; + } + } + + texture = {}; +} + +bool NativeRenderer::DecodeTextureFile( + const std::filesystem::path& path, + NativeTextureResource& outTexture, + std::string& outError) { + outError.clear(); + if (!EnsureWicFactory(outError)) { + return false; + } + + const std::wstring widePath = path.wstring(); + Microsoft::WRL::ComPtr decoder; + HRESULT hr = m_wicFactory->CreateDecoderFromFilename( + widePath.c_str(), + nullptr, + GENERIC_READ, + WICDecodeMetadataCacheOnLoad, + decoder.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !decoder) { + outError = HrToString("IWICImagingFactory::CreateDecoderFromFilename", hr); + return false; + } + + Microsoft::WRL::ComPtr frame; + hr = decoder->GetFrame(0u, frame.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !frame) { + outError = HrToString("IWICBitmapDecoder::GetFrame", hr); + return false; + } + + return DecodeTextureFrame(*frame.Get(), outTexture, outError); +} + +bool NativeRenderer::DecodeTextureMemory( + const std::uint8_t* data, + std::size_t size, + NativeTextureResource& outTexture, + std::string& outError) { + outError.clear(); + if (data == nullptr || size == 0u) { + outError = "DecodeTextureMemory rejected an empty image payload."; + return false; + } + + if (size > static_cast((std::numeric_limits::max)())) { + outError = "DecodeTextureMemory payload exceeds WIC stream limits."; + return false; + } + + if (!EnsureWicFactory(outError)) { + return false; + } + + Microsoft::WRL::ComPtr stream; + HRESULT hr = m_wicFactory->CreateStream(stream.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !stream) { + outError = HrToString("IWICImagingFactory::CreateStream", hr); + return false; + } + + hr = stream->InitializeFromMemory( + const_cast(reinterpret_cast(data)), + static_cast(size)); + if (FAILED(hr)) { + outError = HrToString("IWICStream::InitializeFromMemory", hr); + return false; + } + + Microsoft::WRL::ComPtr decoder; + hr = m_wicFactory->CreateDecoderFromStream( + stream.Get(), + nullptr, + WICDecodeMetadataCacheOnLoad, + decoder.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !decoder) { + outError = HrToString("IWICImagingFactory::CreateDecoderFromStream", hr); + return false; + } + + Microsoft::WRL::ComPtr frame; + hr = decoder->GetFrame(0u, frame.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !frame) { + outError = HrToString("IWICBitmapDecoder::GetFrame", hr); + return false; + } + + return DecodeTextureFrame(*frame.Get(), outTexture, outError); +} + +bool NativeRenderer::DecodeTextureFrame( + IWICBitmapSource& source, + NativeTextureResource& outTexture, + std::string& outError) { + outError.clear(); + + Microsoft::WRL::ComPtr converter; + HRESULT hr = m_wicFactory->CreateFormatConverter(converter.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !converter) { + outError = HrToString("IWICImagingFactory::CreateFormatConverter", hr); + return false; + } + + hr = converter->Initialize( + &source, + GUID_WICPixelFormat32bppPBGRA, + WICBitmapDitherTypeNone, + nullptr, + 0.0f, + WICBitmapPaletteTypeCustom); + if (FAILED(hr)) { + outError = HrToString("IWICFormatConverter::Initialize", hr); + return false; + } + + UINT width = 0u; + UINT height = 0u; + hr = converter->GetSize(&width, &height); + if (FAILED(hr) || width == 0u || height == 0u) { + outError = HrToString("IWICBitmapSource::GetSize", hr); + return false; + } + + std::vector pixels( + static_cast(width) * static_cast(height) * 4u); + hr = converter->CopyPixels( + nullptr, + width * 4u, + static_cast(pixels.size()), + pixels.data()); + if (FAILED(hr)) { + outError = HrToString("IWICBitmapSource::CopyPixels", hr); + return false; + } + + outTexture.pixels = std::move(pixels); + outTexture.width = width; + outTexture.height = height; + outTexture.cachedBitmap.Reset(); + outTexture.cachedTarget = nullptr; + return true; +} + +bool NativeRenderer::ResolveTextureBitmap( + ID2D1RenderTarget& renderTarget, + NativeTextureResource& texture, + Microsoft::WRL::ComPtr& outBitmap) { + outBitmap.Reset(); + if (texture.width == 0u || texture.height == 0u || texture.pixels.empty()) { + return false; + } + + if (texture.cachedBitmap && texture.cachedTarget == &renderTarget) { + outBitmap = texture.cachedBitmap; + return true; + } + + Microsoft::WRL::ComPtr bitmap; + const D2D1_BITMAP_PROPERTIES properties = D2D1::BitmapProperties( + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), + kBaseDpi, + kBaseDpi); + const HRESULT hr = renderTarget.CreateBitmap( + D2D1::SizeU(texture.width, texture.height), + texture.pixels.data(), + texture.width * 4u, + &properties, + bitmap.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !bitmap) { + return false; + } + + if (&renderTarget == m_renderTarget.Get()) { + texture.cachedBitmap = bitmap; + texture.cachedTarget = &renderTarget; + } + + outBitmap = std::move(bitmap); + return true; +} + +bool NativeRenderer::ResolveInteropBitmap( + const ::XCEngine::UI::UITextureHandle& texture, + Microsoft::WRL::ComPtr& outBitmap) const { + return m_windowInterop.ResolveInteropBitmap(texture, outBitmap); +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/Viewport/ViewportHostService.cpp b/new_editor/app/Rendering/Viewport/ViewportHostService.cpp new file mode 100644 index 00000000..45b1bd01 --- /dev/null +++ b/new_editor/app/Rendering/Viewport/ViewportHostService.cpp @@ -0,0 +1,9 @@ +#include "ViewportHostService.h" + +namespace XCEngine::UI::Editor::App { + +// ViewportHostService implementations are split into lifecycle and frame files +// so window/renderer attachment and per-frame viewport work do not stay mixed. + +} // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Viewport/ViewportHostService.h b/new_editor/app/Rendering/Viewport/ViewportHostService.h similarity index 72% rename from new_editor/app/Viewport/ViewportHostService.h rename to new_editor/app/Rendering/Viewport/ViewportHostService.h index 7799c623..f1f28042 100644 --- a/new_editor/app/Viewport/ViewportHostService.h +++ b/new_editor/app/Rendering/Viewport/ViewportHostService.h @@ -1,9 +1,9 @@ #pragma once -#include "ProductViewportRenderTargets.h" +#include "ViewportRenderTargets.h" -#include -#include +#include +#include #include #include @@ -14,7 +14,7 @@ namespace XCEngine::UI::Editor::App { -class ProductViewportHostService { +class ViewportHostService { public: void AttachWindowRenderer(Host::D3D12WindowRenderer& windowRenderer); void DetachWindowRenderer(); @@ -23,8 +23,8 @@ public: void Shutdown(); void BeginFrame(); - ProductViewportFrame RequestViewport( - ProductViewportKind kind, + ViewportFrame RequestViewport( + ViewportKind kind, const ::XCEngine::UI::UISize& requestedSize); void RenderRequestedViewports( @@ -32,17 +32,17 @@ public: private: struct ViewportEntry { - ProductViewportKind kind = ProductViewportKind::Scene; + ViewportKind kind = ViewportKind::Scene; std::uint32_t requestedWidth = 0; std::uint32_t requestedHeight = 0; bool requestedThisFrame = false; bool renderedThisFrame = false; - ProductViewportRenderTargets renderTargets = {}; + ViewportRenderTargets renderTargets = {}; std::string statusText = {}; }; - ViewportEntry& GetEntry(ProductViewportKind kind); - const ViewportEntry& GetEntry(ProductViewportKind kind) const; + ViewportEntry& GetEntry(ViewportKind kind); + const ViewportEntry& GetEntry(ViewportKind kind) const; void DestroyViewportEntry(ViewportEntry& entry); bool EnsureViewportResources(ViewportEntry& entry); void ClearViewport( @@ -52,16 +52,17 @@ private: float g, float b, float a); - ProductViewportFrame BuildFrame( + ViewportFrame BuildFrame( const ViewportEntry& entry, const ::XCEngine::UI::UISize& requestedSize) const; Host::D3D12WindowRenderer* m_windowRenderer = nullptr; ::XCEngine::RHI::RHIDevice* m_device = nullptr; Host::D3D12ShaderResourceDescriptorAllocator m_textureDescriptorAllocator = {}; - ProductViewportRenderTargetManager m_renderTargetManager = {}; + ViewportRenderTargetManager m_renderTargetManager = {}; bool m_surfacePresentationEnabled = false; std::array m_entries = {}; }; } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Viewport/ViewportHostService.cpp b/new_editor/app/Rendering/Viewport/ViewportHostServiceFrame.cpp similarity index 55% rename from new_editor/app/Viewport/ViewportHostService.cpp rename to new_editor/app/Rendering/Viewport/ViewportHostServiceFrame.cpp index 649aa1f6..224121f3 100644 --- a/new_editor/app/Viewport/ViewportHostService.cpp +++ b/new_editor/app/Rendering/Viewport/ViewportHostServiceFrame.cpp @@ -1,4 +1,4 @@ -#include "ProductViewportHostService.h" +#include "ViewportHostService.h" #include @@ -10,55 +10,8 @@ using ::XCEngine::RHI::ResourceStates; } // namespace -void ProductViewportHostService::AttachWindowRenderer( - Host::D3D12WindowRenderer& windowRenderer) { - if (m_windowRenderer == &windowRenderer) { - m_device = windowRenderer.GetRHIDevice(); - if (m_device != nullptr && !m_textureDescriptorAllocator.IsInitialized()) { - m_textureDescriptorAllocator.Initialize(*m_device); - } - return; - } - - Shutdown(); - m_windowRenderer = &windowRenderer; - m_device = windowRenderer.GetRHIDevice(); - if (m_device != nullptr) { - m_textureDescriptorAllocator.Initialize(*m_device); - } -} - -void ProductViewportHostService::DetachWindowRenderer() { - Shutdown(); -} - -void ProductViewportHostService::SetSurfacePresentationEnabled(bool enabled) { - m_surfacePresentationEnabled = enabled; -} - -void ProductViewportHostService::Shutdown() { - for (ViewportEntry& entry : m_entries) { - DestroyViewportEntry(entry); - } - - m_textureDescriptorAllocator.Shutdown(); - m_windowRenderer = nullptr; - m_device = nullptr; - m_surfacePresentationEnabled = false; -} - -void ProductViewportHostService::BeginFrame() { - for (ViewportEntry& entry : m_entries) { - entry.requestedWidth = 0; - entry.requestedHeight = 0; - entry.requestedThisFrame = false; - entry.renderedThisFrame = false; - entry.kind = (&entry == &m_entries[0]) ? ProductViewportKind::Scene : ProductViewportKind::Game; - } -} - -ProductViewportFrame ProductViewportHostService::RequestViewport( - ProductViewportKind kind, +ViewportFrame ViewportHostService::RequestViewport( + ViewportKind kind, const ::XCEngine::UI::UISize& requestedSize) { ViewportEntry& entry = GetEntry(kind); entry.requestedThisFrame = requestedSize.width > 1.0f && requestedSize.height > 1.0f; @@ -84,7 +37,7 @@ ProductViewportFrame ProductViewportHostService::RequestViewport( return BuildFrame(entry, requestedSize); } -void ProductViewportHostService::RenderRequestedViewports( +void ViewportHostService::RenderRequestedViewports( const ::XCEngine::Rendering::RenderContext& renderContext) { if (m_windowRenderer == nullptr || m_device == nullptr || !renderContext.IsValid()) { return; @@ -95,43 +48,24 @@ void ProductViewportHostService::RenderRequestedViewports( continue; } - if (entry.kind == ProductViewportKind::Scene) { - ClearViewport(entry, renderContext, 0.16f, 0.17f, 0.19f, 1.0f); + if (entry.kind == ViewportKind::Scene) { + ClearViewport(entry, renderContext, 0.09f, 0.09f, 0.09f, 1.0f); } else { - ClearViewport(entry, renderContext, 0.11f, 0.11f, 0.12f, 1.0f); + ClearViewport(entry, renderContext, 0.09f, 0.09f, 0.09f, 1.0f); } entry.renderedThisFrame = true; } } -ProductViewportHostService::ViewportEntry& ProductViewportHostService::GetEntry( - ProductViewportKind kind) { - const std::size_t index = kind == ProductViewportKind::Scene ? 0u : 1u; - ViewportEntry& entry = m_entries[index]; - entry.kind = kind; - return entry; -} - -const ProductViewportHostService::ViewportEntry& ProductViewportHostService::GetEntry( - ProductViewportKind kind) const { - const std::size_t index = kind == ProductViewportKind::Scene ? 0u : 1u; - return m_entries[index]; -} - -void ProductViewportHostService::DestroyViewportEntry(ViewportEntry& entry) { - m_renderTargetManager.DestroyTargets(&m_textureDescriptorAllocator, entry.renderTargets); - entry = {}; -} - -bool ProductViewportHostService::EnsureViewportResources(ViewportEntry& entry) { - const ProductViewportResourceReuseQuery reuseQuery = - BuildProductViewportRenderTargetsReuseQuery( +bool ViewportHostService::EnsureViewportResources(ViewportEntry& entry) { + const ViewportResourceReuseQuery reuseQuery = + BuildViewportRenderTargetsReuseQuery( entry.kind, entry.renderTargets, entry.requestedWidth, entry.requestedHeight); - if (CanReuseProductViewportResources(reuseQuery)) { + if (CanReuseViewportResources(reuseQuery)) { return true; } @@ -151,7 +85,7 @@ bool ProductViewportHostService::EnsureViewportResources(ViewportEntry& entry) { entry.renderTargets); } -void ProductViewportHostService::ClearViewport( +void ViewportHostService::ClearViewport( ViewportEntry& entry, const ::XCEngine::Rendering::RenderContext& renderContext, float r, @@ -182,10 +116,10 @@ void ProductViewportHostService::ClearViewport( entry.renderTargets.hasValidObjectIdFrame = false; } -ProductViewportFrame ProductViewportHostService::BuildFrame( +ViewportFrame ViewportHostService::BuildFrame( const ViewportEntry& entry, const ::XCEngine::UI::UISize& requestedSize) const { - ProductViewportFrame frame = {}; + ViewportFrame frame = {}; frame.requestedSize = requestedSize; frame.renderSize = ::XCEngine::UI::UISize( static_cast(entry.renderTargets.width), diff --git a/new_editor/app/Rendering/Viewport/ViewportHostServiceLifecycle.cpp b/new_editor/app/Rendering/Viewport/ViewportHostServiceLifecycle.cpp new file mode 100644 index 00000000..c22bb749 --- /dev/null +++ b/new_editor/app/Rendering/Viewport/ViewportHostServiceLifecycle.cpp @@ -0,0 +1,71 @@ +#include "ViewportHostService.h" + +namespace XCEngine::UI::Editor::App { + +void ViewportHostService::AttachWindowRenderer( + Host::D3D12WindowRenderer& windowRenderer) { + if (m_windowRenderer == &windowRenderer) { + m_device = windowRenderer.GetRHIDevice(); + if (m_device != nullptr && !m_textureDescriptorAllocator.IsInitialized()) { + m_textureDescriptorAllocator.Initialize(*m_device); + } + return; + } + + Shutdown(); + m_windowRenderer = &windowRenderer; + m_device = windowRenderer.GetRHIDevice(); + if (m_device != nullptr) { + m_textureDescriptorAllocator.Initialize(*m_device); + } +} + +void ViewportHostService::DetachWindowRenderer() { + Shutdown(); +} + +void ViewportHostService::SetSurfacePresentationEnabled(bool enabled) { + m_surfacePresentationEnabled = enabled; +} + +void ViewportHostService::Shutdown() { + for (ViewportEntry& entry : m_entries) { + DestroyViewportEntry(entry); + } + + m_textureDescriptorAllocator.Shutdown(); + m_windowRenderer = nullptr; + m_device = nullptr; + m_surfacePresentationEnabled = false; +} + +void ViewportHostService::BeginFrame() { + for (ViewportEntry& entry : m_entries) { + entry.requestedWidth = 0; + entry.requestedHeight = 0; + entry.requestedThisFrame = false; + entry.renderedThisFrame = false; + entry.kind = (&entry == &m_entries[0]) ? ViewportKind::Scene : ViewportKind::Game; + } +} + +ViewportHostService::ViewportEntry& ViewportHostService::GetEntry( + ViewportKind kind) { + const std::size_t index = kind == ViewportKind::Scene ? 0u : 1u; + ViewportEntry& entry = m_entries[index]; + entry.kind = kind; + return entry; +} + +const ViewportHostService::ViewportEntry& ViewportHostService::GetEntry( + ViewportKind kind) const { + const std::size_t index = kind == ViewportKind::Scene ? 0u : 1u; + return m_entries[index]; +} + +void ViewportHostService::DestroyViewportEntry(ViewportEntry& entry) { + m_renderTargetManager.DestroyTargets(&m_textureDescriptorAllocator, entry.renderTargets); + entry = {}; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargetManager.cpp b/new_editor/app/Rendering/Viewport/ViewportRenderTargetManager.cpp new file mode 100644 index 00000000..464213cc --- /dev/null +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargetManager.cpp @@ -0,0 +1,8 @@ +#include "ViewportRenderTargets.h" + +namespace XCEngine::UI::Editor::App { + +// Viewport render target management is split into resource and surface +// translation units to keep viewport host rendering responsibilities isolated. + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Viewport/ViewportRenderTargetManager.cpp b/new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerResources.cpp similarity index 61% rename from new_editor/app/Viewport/ViewportRenderTargetManager.cpp rename to new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerResources.cpp index 95a788d8..7255cc0d 100644 --- a/new_editor/app/Viewport/ViewportRenderTargetManager.cpp +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerResources.cpp @@ -1,4 +1,4 @@ -#include "ProductViewportRenderTargets.h" +#include "ViewportRenderTargets.h" namespace XCEngine::UI::Editor::App { @@ -17,32 +17,32 @@ void ShutdownAndDeleteViewportResource(ResourceType*& resource) { bool CreateViewportColorResources( ::XCEngine::RHI::RHIDevice& device, - ProductViewportRenderTargets& targets) { + ViewportRenderTargets& targets) { const auto colorDesc = - BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm); + BuildViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm); targets.colorTexture = device.CreateTexture(colorDesc); if (targets.colorTexture == nullptr) { return false; } const auto colorViewDesc = - BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm); + BuildViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm); targets.colorView = device.CreateRenderTargetView(targets.colorTexture, colorViewDesc); return targets.colorView != nullptr; } bool CreateViewportDepthResources( ::XCEngine::RHI::RHIDevice& device, - ProductViewportRenderTargets& targets) { + ViewportRenderTargets& targets) { const auto depthDesc = - BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::D24_UNorm_S8_UInt); + BuildViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::D24_UNorm_S8_UInt); targets.depthTexture = device.CreateTexture(depthDesc); if (targets.depthTexture == nullptr) { return false; } const auto depthViewDesc = - BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::D24_UNorm_S8_UInt); + BuildViewportTextureViewDesc(::XCEngine::RHI::Format::D24_UNorm_S8_UInt); targets.depthView = device.CreateDepthStencilView(targets.depthTexture, depthViewDesc); if (targets.depthView == nullptr) { return false; @@ -59,16 +59,16 @@ bool CreateViewportDepthResources( bool CreateViewportObjectIdResources( ::XCEngine::RHI::RHIDevice& device, - ProductViewportRenderTargets& targets) { + ViewportRenderTargets& targets) { const auto objectIdDesc = - BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm); + BuildViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm); targets.objectIdTexture = device.CreateTexture(objectIdDesc); if (targets.objectIdTexture == nullptr) { return false; } const auto objectIdViewDesc = - BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm); + BuildViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm); targets.objectIdView = device.CreateRenderTargetView(targets.objectIdTexture, objectIdViewDesc); if (targets.objectIdView == nullptr) { return false; @@ -82,14 +82,14 @@ bool CreateViewportObjectIdResources( } const auto objectIdDepthDesc = - BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::D24_UNorm_S8_UInt); + BuildViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::D24_UNorm_S8_UInt); targets.objectIdDepthTexture = device.CreateTexture(objectIdDepthDesc); if (targets.objectIdDepthTexture == nullptr) { return false; } const auto objectIdDepthViewDesc = - BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::D24_UNorm_S8_UInt); + BuildViewportTextureViewDesc(::XCEngine::RHI::Format::D24_UNorm_S8_UInt); targets.objectIdDepthView = device.CreateDepthStencilView( targets.objectIdDepthTexture, objectIdDepthViewDesc); @@ -98,16 +98,16 @@ bool CreateViewportObjectIdResources( bool CreateViewportSelectionMaskResources( ::XCEngine::RHI::RHIDevice& device, - ProductViewportRenderTargets& targets) { + ViewportRenderTargets& targets) { const auto selectionMaskDesc = - BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm); + BuildViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm); targets.selectionMaskTexture = device.CreateTexture(selectionMaskDesc); if (targets.selectionMaskTexture == nullptr) { return false; } const auto selectionMaskViewDesc = - BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm); + BuildViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm); targets.selectionMaskView = device.CreateRenderTargetView( targets.selectionMaskTexture, selectionMaskViewDesc); @@ -123,7 +123,7 @@ bool CreateViewportSelectionMaskResources( bool CreateViewportTextureDescriptor( Host::D3D12ShaderResourceDescriptorAllocator& textureDescriptorAllocator, - ProductViewportRenderTargets& targets) { + ViewportRenderTargets& targets) { if (!textureDescriptorAllocator.CreateTextureDescriptor( targets.colorTexture, &targets.srvCpuHandle, @@ -142,71 +142,13 @@ bool CreateViewportTextureDescriptor( } // namespace -ProductViewportResourceReuseQuery BuildProductViewportRenderTargetsReuseQuery( - ProductViewportKind kind, - const ProductViewportRenderTargets& targets, - std::uint32_t requestedWidth, - std::uint32_t requestedHeight) { - ProductViewportResourceReuseQuery query = {}; - query.kind = kind; - query.width = targets.width; - query.height = targets.height; - query.requestedWidth = requestedWidth; - query.requestedHeight = requestedHeight; - query.resources.hasColorTexture = targets.colorTexture != nullptr; - query.resources.hasColorView = targets.colorView != nullptr; - query.resources.hasDepthTexture = targets.depthTexture != nullptr; - query.resources.hasDepthView = targets.depthView != nullptr; - query.resources.hasDepthShaderView = targets.depthShaderView != nullptr; - query.resources.hasObjectIdTexture = targets.objectIdTexture != nullptr; - query.resources.hasObjectIdDepthTexture = targets.objectIdDepthTexture != nullptr; - query.resources.hasObjectIdDepthView = targets.objectIdDepthView != nullptr; - query.resources.hasObjectIdView = targets.objectIdView != nullptr; - query.resources.hasObjectIdShaderView = targets.objectIdShaderView != nullptr; - query.resources.hasSelectionMaskTexture = targets.selectionMaskTexture != nullptr; - query.resources.hasSelectionMaskView = targets.selectionMaskView != nullptr; - query.resources.hasSelectionMaskShaderView = targets.selectionMaskShaderView != nullptr; - query.resources.hasTextureDescriptor = targets.textureHandle.IsValid(); - return query; -} - -::XCEngine::Rendering::RenderSurface BuildProductViewportColorSurface( - const ProductViewportRenderTargets& targets) { - return BuildProductViewportRenderSurface( - targets.width, - targets.height, - targets.colorView, - targets.depthView, - targets.colorState); -} - -::XCEngine::Rendering::RenderSurface BuildProductViewportObjectIdSurface( - const ProductViewportRenderTargets& targets) { - return BuildProductViewportRenderSurface( - targets.width, - targets.height, - targets.objectIdView, - targets.objectIdDepthView, - targets.objectIdState); -} - -::XCEngine::Rendering::RenderSurface BuildProductViewportSelectionMaskSurface( - const ProductViewportRenderTargets& targets) { - return BuildProductViewportRenderSurface( - targets.width, - targets.height, - targets.selectionMaskView, - targets.depthView, - targets.selectionMaskState); -} - -bool ProductViewportRenderTargetManager::EnsureTargets( - ProductViewportKind kind, +bool ViewportRenderTargetManager::EnsureTargets( + ViewportKind kind, std::uint32_t width, std::uint32_t height, ::XCEngine::RHI::RHIDevice& device, Host::D3D12ShaderResourceDescriptorAllocator& textureDescriptorAllocator, - ProductViewportRenderTargets& targets) const { + ViewportRenderTargets& targets) const { if (width == 0u || height == 0u) { return false; } @@ -217,7 +159,7 @@ bool ProductViewportRenderTargetManager::EnsureTargets( if (!CreateViewportColorResources(device, targets) || !CreateViewportDepthResources(device, targets) || - (ProductViewportRequiresObjectIdResources(kind) && + (ViewportRequiresObjectIdResources(kind) && (!CreateViewportObjectIdResources(device, targets) || !CreateViewportSelectionMaskResources(device, targets))) || !CreateViewportTextureDescriptor(textureDescriptorAllocator, targets)) { @@ -232,9 +174,9 @@ bool ProductViewportRenderTargetManager::EnsureTargets( return true; } -void ProductViewportRenderTargetManager::DestroyTargets( +void ViewportRenderTargetManager::DestroyTargets( Host::D3D12ShaderResourceDescriptorAllocator* textureDescriptorAllocator, - ProductViewportRenderTargets& targets) const { + ViewportRenderTargets& targets) const { if (textureDescriptorAllocator != nullptr && targets.srvCpuHandle.ptr != 0) { textureDescriptorAllocator->Free(targets.srvCpuHandle, targets.srvGpuHandle); } diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerSurfaces.cpp b/new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerSurfaces.cpp new file mode 100644 index 00000000..84bc3984 --- /dev/null +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerSurfaces.cpp @@ -0,0 +1,63 @@ +#include "ViewportRenderTargets.h" + +namespace XCEngine::UI::Editor::App { + +ViewportResourceReuseQuery BuildViewportRenderTargetsReuseQuery( + ViewportKind kind, + const ViewportRenderTargets& targets, + std::uint32_t requestedWidth, + std::uint32_t requestedHeight) { + ViewportResourceReuseQuery query = {}; + query.kind = kind; + query.width = targets.width; + query.height = targets.height; + query.requestedWidth = requestedWidth; + query.requestedHeight = requestedHeight; + query.resources.hasColorTexture = targets.colorTexture != nullptr; + query.resources.hasColorView = targets.colorView != nullptr; + query.resources.hasDepthTexture = targets.depthTexture != nullptr; + query.resources.hasDepthView = targets.depthView != nullptr; + query.resources.hasDepthShaderView = targets.depthShaderView != nullptr; + query.resources.hasObjectIdTexture = targets.objectIdTexture != nullptr; + query.resources.hasObjectIdDepthTexture = targets.objectIdDepthTexture != nullptr; + query.resources.hasObjectIdDepthView = targets.objectIdDepthView != nullptr; + query.resources.hasObjectIdView = targets.objectIdView != nullptr; + query.resources.hasObjectIdShaderView = targets.objectIdShaderView != nullptr; + query.resources.hasSelectionMaskTexture = targets.selectionMaskTexture != nullptr; + query.resources.hasSelectionMaskView = targets.selectionMaskView != nullptr; + query.resources.hasSelectionMaskShaderView = targets.selectionMaskShaderView != nullptr; + query.resources.hasTextureDescriptor = targets.textureHandle.IsValid(); + return query; +} + +::XCEngine::Rendering::RenderSurface BuildViewportColorSurface( + const ViewportRenderTargets& targets) { + return BuildViewportRenderSurface( + targets.width, + targets.height, + targets.colorView, + targets.depthView, + targets.colorState); +} + +::XCEngine::Rendering::RenderSurface BuildViewportObjectIdSurface( + const ViewportRenderTargets& targets) { + return BuildViewportRenderSurface( + targets.width, + targets.height, + targets.objectIdView, + targets.objectIdDepthView, + targets.objectIdState); +} + +::XCEngine::Rendering::RenderSurface BuildViewportSelectionMaskSurface( + const ViewportRenderTargets& targets) { + return BuildViewportRenderSurface( + targets.width, + targets.height, + targets.selectionMaskView, + targets.depthView, + targets.selectionMaskState); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Viewport/ViewportSurfaceUtils.h b/new_editor/app/Rendering/Viewport/ViewportRenderTargetSupport.h similarity index 82% rename from new_editor/app/Viewport/ViewportSurfaceUtils.h rename to new_editor/app/Rendering/Viewport/ViewportRenderTargetSupport.h index 7c331c75..57836188 100644 --- a/new_editor/app/Viewport/ViewportSurfaceUtils.h +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargetSupport.h @@ -1,6 +1,6 @@ #pragma once -#include "ProductViewportTypes.h" +#include "ViewportTypes.h" #include #include @@ -13,7 +13,7 @@ namespace XCEngine::UI::Editor::App { -struct ProductViewportResourcePresence { +struct ViewportResourcePresence { bool hasColorTexture = false; bool hasColorView = false; bool hasDepthTexture = false; @@ -30,17 +30,17 @@ struct ProductViewportResourcePresence { bool hasTextureDescriptor = false; }; -struct ProductViewportResourceReuseQuery { - ProductViewportKind kind = ProductViewportKind::Scene; +struct ViewportResourceReuseQuery { + ViewportKind kind = ViewportKind::Scene; std::uint32_t width = 0; std::uint32_t height = 0; std::uint32_t requestedWidth = 0; std::uint32_t requestedHeight = 0; - ProductViewportResourcePresence resources = {}; + ViewportResourcePresence resources = {}; }; -inline bool ProductViewportRequiresObjectIdResources(ProductViewportKind kind) { - return kind == ProductViewportKind::Scene; +inline bool ViewportRequiresObjectIdResources(ViewportKind kind) { + return kind == ViewportKind::Scene; } inline std::uint32_t ClampViewportPixelCoordinate(float value, std::uint32_t extent) { @@ -53,7 +53,7 @@ inline std::uint32_t ClampViewportPixelCoordinate(float value, std::uint32_t ext return static_cast(std::floor(clamped)); } -inline bool CanReuseProductViewportResources(const ProductViewportResourceReuseQuery& query) { +inline bool CanReuseViewportResources(const ViewportResourceReuseQuery& query) { if (query.requestedWidth == 0u || query.requestedHeight == 0u) { return false; } @@ -70,7 +70,7 @@ inline bool CanReuseProductViewportResources(const ProductViewportResourceReuseQ return false; } - if (!ProductViewportRequiresObjectIdResources(query.kind)) { + if (!ViewportRequiresObjectIdResources(query.kind)) { return true; } @@ -85,7 +85,7 @@ inline bool CanReuseProductViewportResources(const ProductViewportResourceReuseQ query.resources.hasSelectionMaskShaderView; } -inline ::XCEngine::RHI::TextureDesc BuildProductViewportTextureDesc( +inline ::XCEngine::RHI::TextureDesc BuildViewportTextureDesc( std::uint32_t width, std::uint32_t height, ::XCEngine::RHI::Format format) { @@ -103,7 +103,7 @@ inline ::XCEngine::RHI::TextureDesc BuildProductViewportTextureDesc( return desc; } -inline ::XCEngine::RHI::ResourceViewDesc BuildProductViewportTextureViewDesc( +inline ::XCEngine::RHI::ResourceViewDesc BuildViewportTextureViewDesc( ::XCEngine::RHI::Format format) { ::XCEngine::RHI::ResourceViewDesc desc = {}; desc.format = static_cast(format); @@ -111,7 +111,7 @@ inline ::XCEngine::RHI::ResourceViewDesc BuildProductViewportTextureViewDesc( return desc; } -inline ::XCEngine::Rendering::RenderSurface BuildProductViewportRenderSurface( +inline ::XCEngine::Rendering::RenderSurface BuildViewportRenderSurface( std::uint32_t width, std::uint32_t height, ::XCEngine::RHI::RHIResourceView* colorView, @@ -128,3 +128,4 @@ inline ::XCEngine::Rendering::RenderSurface BuildProductViewportRenderSurface( } } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp new file mode 100644 index 00000000..b1f49b4f --- /dev/null +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp @@ -0,0 +1,2 @@ +#include "ViewportRenderTargets.h" + diff --git a/new_editor/app/Viewport/ViewportRenderTargets.h b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h similarity index 70% rename from new_editor/app/Viewport/ViewportRenderTargets.h rename to new_editor/app/Rendering/Viewport/ViewportRenderTargets.h index 513cfbda..7eaaa070 100644 --- a/new_editor/app/Viewport/ViewportRenderTargets.h +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h @@ -1,8 +1,8 @@ #pragma once -#include "ProductViewportSurfaceUtils.h" +#include "ViewportRenderTargetSupport.h" -#include +#include #include #include @@ -11,7 +11,7 @@ namespace XCEngine::UI::Editor::App { -struct ProductViewportRenderTargets { +struct ViewportRenderTargets { std::uint32_t width = 0; std::uint32_t height = 0; ::XCEngine::RHI::RHITexture* colorTexture = nullptr; @@ -36,31 +36,32 @@ struct ProductViewportRenderTargets { bool hasValidObjectIdFrame = false; }; -ProductViewportResourceReuseQuery BuildProductViewportRenderTargetsReuseQuery( - ProductViewportKind kind, - const ProductViewportRenderTargets& targets, +ViewportResourceReuseQuery BuildViewportRenderTargetsReuseQuery( + ViewportKind kind, + const ViewportRenderTargets& targets, std::uint32_t requestedWidth, std::uint32_t requestedHeight); -::XCEngine::Rendering::RenderSurface BuildProductViewportColorSurface( - const ProductViewportRenderTargets& targets); -::XCEngine::Rendering::RenderSurface BuildProductViewportObjectIdSurface( - const ProductViewportRenderTargets& targets); -::XCEngine::Rendering::RenderSurface BuildProductViewportSelectionMaskSurface( - const ProductViewportRenderTargets& targets); +::XCEngine::Rendering::RenderSurface BuildViewportColorSurface( + const ViewportRenderTargets& targets); +::XCEngine::Rendering::RenderSurface BuildViewportObjectIdSurface( + const ViewportRenderTargets& targets); +::XCEngine::Rendering::RenderSurface BuildViewportSelectionMaskSurface( + const ViewportRenderTargets& targets); -class ProductViewportRenderTargetManager { +class ViewportRenderTargetManager { public: bool EnsureTargets( - ProductViewportKind kind, + ViewportKind kind, std::uint32_t width, std::uint32_t height, ::XCEngine::RHI::RHIDevice& device, Host::D3D12ShaderResourceDescriptorAllocator& textureDescriptorAllocator, - ProductViewportRenderTargets& targets) const; + ViewportRenderTargets& targets) const; void DestroyTargets( Host::D3D12ShaderResourceDescriptorAllocator* textureDescriptorAllocator, - ProductViewportRenderTargets& targets) const; + ViewportRenderTargets& targets) const; }; } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Viewport/ViewportTypes.h b/new_editor/app/Rendering/Viewport/ViewportTypes.h similarity index 84% rename from new_editor/app/Viewport/ViewportTypes.h rename to new_editor/app/Rendering/Viewport/ViewportTypes.h index 3e47acd5..8c7dcce9 100644 --- a/new_editor/app/Viewport/ViewportTypes.h +++ b/new_editor/app/Rendering/Viewport/ViewportTypes.h @@ -7,12 +7,12 @@ namespace XCEngine::UI::Editor::App { -enum class ProductViewportKind : std::uint8_t { +enum class ViewportKind : std::uint8_t { Scene = 0, Game }; -struct ProductViewportFrame { +struct ViewportFrame { ::XCEngine::UI::UITextureHandle texture = {}; ::XCEngine::UI::UISize requestedSize = {}; ::XCEngine::UI::UISize renderSize = {}; @@ -22,3 +22,4 @@ struct ProductViewportFrame { }; } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Core/EditorContext.cpp b/new_editor/app/State/EditorContext.cpp similarity index 80% rename from new_editor/app/Core/EditorContext.cpp rename to new_editor/app/State/EditorContext.cpp index 409ed7ef..1c190f42 100644 --- a/new_editor/app/Core/EditorContext.cpp +++ b/new_editor/app/State/EditorContext.cpp @@ -1,6 +1,6 @@ -#include "ProductEditorContext.h" +#include "EditorContext.h" -#include "Shell/ProductShellAsset.h" +#include "Composition/EditorShellAssetBuilder.h" #include #include @@ -29,8 +29,8 @@ std::string ComposeStatusText( } // namespace -bool ProductEditorContext::Initialize(const std::filesystem::path& repoRoot) { - m_shellAsset = BuildProductShellAsset(repoRoot); +bool EditorContext::Initialize(const std::filesystem::path& repoRoot) { + m_shellAsset = BuildEditorShellAsset(repoRoot); m_shellValidation = ValidateEditorShellAsset(m_shellAsset); if (!m_shellValidation.IsValid()) { return false; @@ -49,60 +49,60 @@ bool ProductEditorContext::Initialize(const std::filesystem::path& repoRoot) { return true; } -void ProductEditorContext::AttachTextMeasurer( +void EditorContext::AttachTextMeasurer( const UIEditorTextMeasurer& textMeasurer) { m_shellServices.textMeasurer = &textMeasurer; } -void ProductEditorContext::SetExitRequestHandler(std::function handler) { +void EditorContext::SetExitRequestHandler(std::function handler) { m_hostCommandBridge.SetExitRequestHandler(std::move(handler)); } -void ProductEditorContext::SyncSessionFromWorkspace( +void EditorContext::SyncSessionFromWorkspace( const UIEditorWorkspaceController& workspaceController) { - SyncProductEditorSessionFromWorkspace(m_session, workspaceController); + SyncEditorSessionFromWorkspace(m_session, workspaceController); } -bool ProductEditorContext::IsValid() const { +bool EditorContext::IsValid() const { return m_shellValidation.IsValid(); } -const std::string& ProductEditorContext::GetValidationMessage() const { +const std::string& EditorContext::GetValidationMessage() const { return m_shellValidation.message; } -const EditorShellAsset& ProductEditorContext::GetShellAsset() const { +const EditorShellAsset& EditorContext::GetShellAsset() const { return m_shellAsset; } -const ProductEditorSession& ProductEditorContext::GetSession() const { +const EditorSession& EditorContext::GetSession() const { return m_session; } -void ProductEditorContext::SetSelection(ProductEditorSelectionState selection) { +void EditorContext::SetSelection(EditorSelectionState selection) { m_session.selection = std::move(selection); } -void ProductEditorContext::ClearSelection() { +void EditorContext::ClearSelection() { m_session.selection = {}; } -UIEditorWorkspaceController ProductEditorContext::BuildWorkspaceController() const { +UIEditorWorkspaceController EditorContext::BuildWorkspaceController() const { return UIEditorWorkspaceController( m_shellAsset.panelRegistry, m_shellAsset.workspace, m_shellAsset.workspaceSession); } -const UIEditorShellInteractionServices& ProductEditorContext::GetShellServices() const { +const UIEditorShellInteractionServices& EditorContext::GetShellServices() const { return m_shellServices; } -UIEditorShellInteractionDefinition ProductEditorContext::BuildShellDefinition( +UIEditorShellInteractionDefinition EditorContext::BuildShellDefinition( const UIEditorWorkspaceController& workspaceController, std::string_view captureText, - ProductEditorShellVariant variant) const { - return BuildProductShellInteractionDefinition( + EditorShellVariant variant) const { + return BuildEditorShellInteractionDefinition( m_shellAsset, workspaceController, ComposeStatusText(m_lastStatus, m_lastMessage), @@ -110,11 +110,11 @@ UIEditorShellInteractionDefinition ProductEditorContext::BuildShellDefinition( variant); } -void ProductEditorContext::SetReadyStatus() { +void EditorContext::SetReadyStatus() { SetStatus("Ready", "Old editor shell baseline loaded."); } -void ProductEditorContext::SetStatus( +void EditorContext::SetStatus( std::string status, std::string message) { if (m_lastStatus != status || m_lastMessage != message) { @@ -124,7 +124,7 @@ void ProductEditorContext::SetStatus( m_lastMessage = std::move(message); } -void ProductEditorContext::UpdateStatusFromShellResult( +void EditorContext::UpdateStatusFromShellResult( const UIEditorWorkspaceController& workspaceController, const UIEditorShellInteractionResult& result) { if (result.commandDispatched) { @@ -183,10 +183,10 @@ void ProductEditorContext::UpdateStatusFromShellResult( } } -void ProductEditorContext::AppendConsoleEntry( +void EditorContext::AppendConsoleEntry( std::string channel, std::string message) { - ProductEditorConsoleEntry entry = {}; + EditorConsoleEntry entry = {}; entry.channel = std::move(channel); entry.message = std::move(message); m_session.consoleEntries.push_back(std::move(entry)); @@ -195,7 +195,7 @@ void ProductEditorContext::AppendConsoleEntry( } } -std::string ProductEditorContext::DescribeWorkspaceState( +std::string EditorContext::DescribeWorkspaceState( const UIEditorWorkspaceController& workspaceController, const UIEditorShellInteractionState& interactionState) const { std::ostringstream stream = {}; @@ -224,3 +224,4 @@ std::string ProductEditorContext::DescribeWorkspaceState( } } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Core/EditorContext.h b/new_editor/app/State/EditorContext.h similarity index 78% rename from new_editor/app/Core/EditorContext.h rename to new_editor/app/State/EditorContext.h index f3e2364e..b48b4370 100644 --- a/new_editor/app/Core/EditorContext.h +++ b/new_editor/app/State/EditorContext.h @@ -1,13 +1,13 @@ #pragma once -#include "Commands/ProductEditorHostCommandBridge.h" -#include "Core/ProductEditorSession.h" -#include "Shell/ProductShellAsset.h" +#include "Composition/EditorHostCommandBridge.h" +#include "State/EditorSession.h" +#include "Composition/EditorShellAssetBuilder.h" #include #include #include -#include +#include #include #include @@ -16,7 +16,7 @@ namespace XCEngine::UI::Editor::App { -class ProductEditorContext { +class EditorContext { public: bool Initialize(const std::filesystem::path& repoRoot); void AttachTextMeasurer(const UIEditorTextMeasurer& textMeasurer); @@ -26,8 +26,8 @@ public: bool IsValid() const; const std::string& GetValidationMessage() const; const EditorShellAsset& GetShellAsset() const; - const ProductEditorSession& GetSession() const; - void SetSelection(ProductEditorSelectionState selection); + const EditorSession& GetSession() const; + void SetSelection(EditorSelectionState selection); void ClearSelection(); UIEditorWorkspaceController BuildWorkspaceController() const; @@ -36,7 +36,7 @@ public: UIEditorShellInteractionDefinition BuildShellDefinition( const UIEditorWorkspaceController& workspaceController, std::string_view captureText, - ProductEditorShellVariant variant = ProductEditorShellVariant::Primary) const; + EditorShellVariant variant = EditorShellVariant::Primary) const; void SetReadyStatus(); void SetStatus(std::string status, std::string message); @@ -54,10 +54,11 @@ private: EditorShellAssetValidationResult m_shellValidation = {}; UIEditorShortcutManager m_shortcutManager = {}; UIEditorShellInteractionServices m_shellServices = {}; - ProductEditorSession m_session = {}; - ProductEditorHostCommandBridge m_hostCommandBridge = {}; + EditorSession m_session = {}; + EditorHostCommandBridge m_hostCommandBridge = {}; std::string m_lastStatus = {}; std::string m_lastMessage = {}; }; } // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/State/EditorSession.cpp b/new_editor/app/State/EditorSession.cpp new file mode 100644 index 00000000..2aae8c02 --- /dev/null +++ b/new_editor/app/State/EditorSession.cpp @@ -0,0 +1,80 @@ +#include "EditorSession.h" + +namespace XCEngine::UI::Editor::App { + +std::string_view GetEditorRuntimeModeName(EditorRuntimeMode mode) { + switch (mode) { + case EditorRuntimeMode::Edit: + return "Edit"; + case EditorRuntimeMode::Play: + return "Play"; + case EditorRuntimeMode::Paused: + return "Paused"; + default: + return "Unknown"; + } +} + +std::string_view GetEditorActionRouteName(EditorActionRoute route) { + switch (route) { + case EditorActionRoute::Hierarchy: + return "Hierarchy"; + case EditorActionRoute::Project: + return "Project"; + case EditorActionRoute::Inspector: + return "Inspector"; + case EditorActionRoute::Console: + return "Console"; + case EditorActionRoute::Scene: + return "Scene"; + case EditorActionRoute::Game: + return "Game"; + case EditorActionRoute::None: + default: + return "None"; + } +} + +std::string_view GetEditorSelectionKindName(EditorSelectionKind kind) { + switch (kind) { + case EditorSelectionKind::HierarchyNode: + return "HierarchyNode"; + case EditorSelectionKind::ProjectItem: + return "ProjectItem"; + case EditorSelectionKind::None: + default: + return "None"; + } +} + +EditorActionRoute ResolveEditorActionRoute(std::string_view panelId) { + if (panelId == "hierarchy") { + return EditorActionRoute::Hierarchy; + } + if (panelId == "project") { + return EditorActionRoute::Project; + } + if (panelId == "inspector") { + return EditorActionRoute::Inspector; + } + if (panelId == "console") { + return EditorActionRoute::Console; + } + if (panelId == "scene") { + return EditorActionRoute::Scene; + } + if (panelId == "game") { + return EditorActionRoute::Game; + } + return EditorActionRoute::None; +} + +void SyncEditorSessionFromWorkspace( + EditorSession& session, + const UIEditorWorkspaceController& controller) { + session.activePanelId = controller.GetWorkspace().activePanelId; + session.activeRoute = ResolveEditorActionRoute(session.activePanelId); +} + +} // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/State/EditorSession.h b/new_editor/app/State/EditorSession.h new file mode 100644 index 00000000..d979aaa9 --- /dev/null +++ b/new_editor/app/State/EditorSession.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::App { + +enum class EditorRuntimeMode : std::uint8_t { + Edit = 0, + Play, + Paused +}; + +enum class EditorActionRoute : std::uint8_t { + None = 0, + Hierarchy, + Project, + Inspector, + Console, + Scene, + Game +}; + +enum class EditorSelectionKind : std::uint8_t { + None = 0, + HierarchyNode, + ProjectItem +}; + +struct EditorSelectionState { + EditorSelectionKind kind = EditorSelectionKind::None; + std::string itemId = {}; + std::string displayName = {}; + std::filesystem::path absolutePath = {}; + bool directory = false; +}; + +struct EditorConsoleEntry { + std::string channel = {}; + std::string message = {}; +}; + +struct EditorSession { + std::filesystem::path repoRoot = {}; + std::filesystem::path projectRoot = {}; + std::string activePanelId = {}; + EditorRuntimeMode runtimeMode = EditorRuntimeMode::Edit; + EditorActionRoute activeRoute = EditorActionRoute::None; + EditorSelectionState selection = {}; + std::vector consoleEntries = {}; +}; + +std::string_view GetEditorRuntimeModeName(EditorRuntimeMode mode); +std::string_view GetEditorActionRouteName(EditorActionRoute route); +std::string_view GetEditorSelectionKindName(EditorSelectionKind kind); + +EditorActionRoute ResolveEditorActionRoute(std::string_view panelId); + +void SyncEditorSessionFromWorkspace( + EditorSession& session, + const UIEditorWorkspaceController& controller); + +} // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Support/EmbeddedPngLoader.h b/new_editor/app/Support/EmbeddedPngLoader.h new file mode 100644 index 00000000..6ebdf86c --- /dev/null +++ b/new_editor/app/Support/EmbeddedPngLoader.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include + +#include +#include +#include + +#include + +namespace XCEngine::UI::Editor::Support { + +inline bool LoadEmbeddedPngBytes( + UINT resourceId, + const std::uint8_t*& outData, + std::size_t& outSize, + std::string& outError) { + outData = nullptr; + outSize = 0u; + outError.clear(); + + HMODULE module = GetModuleHandleW(nullptr); + if (module == nullptr) { + outError = "GetModuleHandleW(nullptr) returned null."; + return false; + } + + HRSRC resource = FindResourceW(module, MAKEINTRESOURCEW(resourceId), L"PNG"); + if (resource == nullptr) { + outError = "FindResourceW failed."; + return false; + } + + HGLOBAL resourceData = LoadResource(module, resource); + if (resourceData == nullptr) { + outError = "LoadResource failed."; + return false; + } + + const DWORD resourceSize = SizeofResource(module, resource); + if (resourceSize == 0u) { + outError = "SizeofResource returned zero."; + return false; + } + + const void* lockedBytes = LockResource(resourceData); + if (lockedBytes == nullptr) { + outError = "LockResource failed."; + return false; + } + + outData = reinterpret_cast(lockedBytes); + outSize = static_cast(resourceSize); + return true; +} + +inline bool LoadEmbeddedPngTexture( + Host::NativeRenderer& renderer, + UINT resourceId, + ::XCEngine::UI::UITextureHandle& outTexture, + std::string& outError) { + const std::uint8_t* bytes = nullptr; + std::size_t byteCount = 0u; + if (!LoadEmbeddedPngBytes(resourceId, bytes, byteCount, outError)) { + return false; + } + + return renderer.LoadTextureFromMemory(bytes, byteCount, outTexture, outError); +} + +} // namespace XCEngine::UI::Editor::Support diff --git a/new_editor/app/Support/EnvironmentFlags.h b/new_editor/app/Support/EnvironmentFlags.h new file mode 100644 index 00000000..1aabfd23 --- /dev/null +++ b/new_editor/app/Support/EnvironmentFlags.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::Support { + +inline bool IsEnvironmentFlagEnabled(const char* envName) { + if (envName == nullptr || envName[0] == '\0') { + return false; + } + + const char* value = std::getenv(envName); + if (value == nullptr || value[0] == '\0') { + return false; + } + + std::string normalized = value; + std::transform( + normalized.begin(), + normalized.end(), + normalized.begin(), + [](unsigned char character) { + return static_cast(std::tolower(character)); + }); + return normalized != "0" && + normalized != "false" && + normalized != "off" && + normalized != "no"; +} + +inline std::optional TryGetEnvironmentInt(const char* envName) { + if (envName == nullptr || envName[0] == '\0') { + return std::nullopt; + } + + const char* value = std::getenv(envName); + if (value == nullptr || value[0] == '\0') { + return std::nullopt; + } + + char* parseEnd = nullptr; + const long parsed = std::strtol(value, &parseEnd, 10); + if (parseEnd == value || (parseEnd != nullptr && *parseEnd != '\0')) { + return std::nullopt; + } + + return static_cast(parsed); +} + +} // namespace XCEngine::UI::Editor::Support diff --git a/new_editor/app/Support/ExecutablePath.h b/new_editor/app/Support/ExecutablePath.h new file mode 100644 index 00000000..4488b27c --- /dev/null +++ b/new_editor/app/Support/ExecutablePath.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include + +namespace XCEngine::UI::Editor::Support { + +inline std::filesystem::path GetExecutableDirectory() { + std::vector buffer(MAX_PATH); + while (true) { + const DWORD copied = ::GetModuleFileNameW( + nullptr, + buffer.data(), + static_cast(buffer.size())); + if (copied == 0u) { + return std::filesystem::current_path().lexically_normal(); + } + + if (copied < buffer.size() - 1u) { + return std::filesystem::path(std::wstring(buffer.data(), copied)) + .parent_path() + .lexically_normal(); + } + + buffer.resize(buffer.size() * 2u); + } +} + +} // namespace XCEngine::UI::Editor::Support diff --git a/new_editor/app/Support/StringEncoding.h b/new_editor/app/Support/StringEncoding.h new file mode 100644 index 00000000..a85abf91 --- /dev/null +++ b/new_editor/app/Support/StringEncoding.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include + +namespace XCEngine::UI::Editor::Support { + +inline std::string WideToUtf8(std::wstring_view text) { + if (text.empty()) { + return {}; + } + + const int requiredBytes = WideCharToMultiByte( + CP_UTF8, + 0, + text.data(), + static_cast(text.size()), + nullptr, + 0, + nullptr, + nullptr); + if (requiredBytes <= 0) { + return {}; + } + + std::string utf8(static_cast(requiredBytes), '\0'); + const int convertedBytes = WideCharToMultiByte( + CP_UTF8, + 0, + text.data(), + static_cast(text.size()), + utf8.data(), + requiredBytes, + nullptr, + nullptr); + if (convertedBytes <= 0) { + return {}; + } + + utf8.resize(static_cast(convertedBytes)); + return utf8; +} + +} // namespace XCEngine::UI::Editor::Support diff --git a/new_editor/app/Support/TextFormat.h b/new_editor/app/Support/TextFormat.h new file mode 100644 index 00000000..e2e23cd0 --- /dev/null +++ b/new_editor/app/Support/TextFormat.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace XCEngine::UI::Editor::Support { + +inline std::string TruncateText(const std::string& text, std::size_t maxLength) { + if (text.size() <= maxLength) { + return text; + } + + if (maxLength <= 3u) { + return text.substr(0u, maxLength); + } + + return text.substr(0u, maxLength - 3u) + "..."; +} + +} // namespace XCEngine::UI::Editor::Support diff --git a/new_editor/app/UI/Styles/EditorTreeViewStyle.h b/new_editor/app/UI/Styles/EditorTreeViewStyle.h new file mode 100644 index 00000000..babccfcb --- /dev/null +++ b/new_editor/app/UI/Styles/EditorTreeViewStyle.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +namespace XCEngine::UI::Editor::App { + +inline constexpr ::XCEngine::UI::UIColor kEditorTreeSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); +inline constexpr ::XCEngine::UI::UIColor kEditorTreeHoverColor(0.13f, 0.13f, 0.13f, 1.0f); +inline constexpr ::XCEngine::UI::UIColor kEditorTreeSelectedColor(0.17f, 0.17f, 0.17f, 1.0f); +inline constexpr ::XCEngine::UI::UIColor kEditorTreeDisclosureColor(0.620f, 0.620f, 0.620f, 1.0f); +inline constexpr ::XCEngine::UI::UIColor kEditorTreeTextColor(0.920f, 0.920f, 0.920f, 1.0f); + +inline Widgets::UIEditorTreeViewMetrics BuildEditorTreeViewMetrics() { + Widgets::UIEditorTreeViewMetrics metrics = {}; + metrics.rowHeight = 20.0f; + metrics.rowGap = 0.0f; + metrics.horizontalPadding = 6.0f; + metrics.indentWidth = 14.0f; + metrics.disclosureExtent = 18.0f; + metrics.disclosureLabelGap = 2.0f; + metrics.iconExtent = 18.0f; + metrics.iconLabelGap = 2.0f; + metrics.iconInsetY = -1.0f; + metrics.labelInsetY = 0.0f; + metrics.cornerRounding = 0.0f; + metrics.borderThickness = 0.0f; + metrics.focusedBorderThickness = 0.0f; + return metrics; +} + +inline Widgets::UIEditorTreeViewPalette BuildEditorTreeViewPalette() { + Widgets::UIEditorTreeViewPalette palette = {}; + palette.surfaceColor = kEditorTreeSurfaceColor; + palette.borderColor = kEditorTreeSurfaceColor; + palette.focusedBorderColor = kEditorTreeSurfaceColor; + palette.rowHoverColor = kEditorTreeHoverColor; + palette.rowSelectedColor = kEditorTreeSelectedColor; + palette.rowSelectedFocusedColor = kEditorTreeSelectedColor; + palette.disclosureColor = kEditorTreeDisclosureColor; + palette.textColor = kEditorTreeTextColor; + return palette; +} + +} // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Viewport/ViewportRenderTargets.cpp b/new_editor/app/Viewport/ViewportRenderTargets.cpp deleted file mode 100644 index ec3a38fd..00000000 --- a/new_editor/app/Viewport/ViewportRenderTargets.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "ProductViewportRenderTargets.h" diff --git a/new_editor/app/Workspace/EditorWorkspace.cpp b/new_editor/app/Workspace/EditorWorkspace.cpp deleted file mode 100644 index 70b646f2..00000000 --- a/new_editor/app/Workspace/EditorWorkspace.cpp +++ /dev/null @@ -1,411 +0,0 @@ -#include "ProductEditorWorkspace.h" - -#include "Workspace/ProductEditorWorkspaceEventRouter.h" - -#include -#include - -#include - -namespace XCEngine::UI::Editor::App { - -namespace { - -using ::XCEngine::UI::UIColor; -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using Widgets::UIEditorDockHostHitTargetKind; - -bool IsProductViewportPanel(std::string_view panelId) { - return panelId == "scene" || panelId == "game"; -} - -ProductViewportKind ResolveProductViewportKind(std::string_view panelId) { - return panelId == "game" - ? ProductViewportKind::Game - : ProductViewportKind::Scene; -} - -void ApplyViewportFrameToPresentation( - const ProductViewportFrame& viewportFrame, - UIEditorWorkspacePanelPresentationModel& presentation) { - presentation.viewportShellModel.frame.texture = viewportFrame.texture; - presentation.viewportShellModel.frame.requestedSize = viewportFrame.requestedSize; - presentation.viewportShellModel.frame.presentedSize = viewportFrame.renderSize; - presentation.viewportShellModel.frame.hasTexture = viewportFrame.hasTexture; - presentation.viewportShellModel.frame.statusText = viewportFrame.statusText; -} - -void ApplyViewportFrameToShellModel( - const ProductViewportFrame& viewportFrame, - UIEditorViewportShellModel& shellModel) { - shellModel.frame.texture = viewportFrame.texture; - shellModel.frame.requestedSize = viewportFrame.requestedSize; - shellModel.frame.presentedSize = viewportFrame.renderSize; - shellModel.frame.hasTexture = viewportFrame.hasTexture; - shellModel.frame.statusText = viewportFrame.statusText; -} - -UIEditorWorkspacePanelPresentationModel* FindMutableWorkspacePresentation( - std::vector& presentations, - std::string_view panelId) { - for (UIEditorWorkspacePanelPresentationModel& presentation : presentations) { - if (presentation.panelId == panelId) { - return &presentation; - } - } - - return nullptr; -} - -void ApplyViewportFramesToShellFrame( - UIEditorShellInteractionFrame& shellFrame, - ProductViewportHostService& viewportHostService) { - auto applyToViewportFrames = - [&](std::vector& viewportFrames) { - for (UIEditorWorkspaceViewportComposeFrame& viewportComposeFrame : viewportFrames) { - if (!IsProductViewportPanel(viewportComposeFrame.panelId)) { - continue; - } - - const ProductViewportFrame viewportFrame = - viewportHostService.RequestViewport( - ResolveProductViewportKind(viewportComposeFrame.panelId), - viewportComposeFrame.viewportShellFrame.requestedViewportSize); - ApplyViewportFrameToShellModel( - viewportFrame, - viewportComposeFrame.viewportShellModel); - if (UIEditorWorkspacePanelPresentationModel* presentation = - FindMutableWorkspacePresentation( - shellFrame.model.workspacePresentations, - viewportComposeFrame.panelId); - presentation != nullptr) { - ApplyViewportFrameToPresentation(viewportFrame, *presentation); - } - } - }; - - applyToViewportFrames(shellFrame.workspaceInteractionFrame.composeFrame.viewportFrames); - applyToViewportFrames(shellFrame.shellFrame.workspaceFrame.viewportFrames); -} - -std::vector BuildWorkspacePresentations( - const UIEditorShellInteractionDefinition& definition) { - return definition.workspacePresentations; -} - -UIEditorShellComposeModel BuildShellComposeModelFromFrame( - const UIEditorShellInteractionFrame& frame) { - UIEditorShellComposeModel model = {}; - model.menuBarItems = frame.request.menuBarItems; - model.toolbarButtons = frame.model.toolbarButtons; - model.statusSegments = frame.model.statusSegments; - model.workspacePresentations = frame.model.workspacePresentations; - return model; -} - -void AppendShellPopups( - UIDrawList& drawList, - const UIEditorShellInteractionFrame& frame, - const UIEditorShellInteractionPalette& palette, - const UIEditorShellInteractionMetrics& metrics) { - const std::size_t popupCount = - (std::min)(frame.request.popupRequests.size(), frame.popupFrames.size()); - for (std::size_t index = 0; index < popupCount; ++index) { - const UIEditorShellInteractionPopupRequest& popupRequest = - frame.request.popupRequests[index]; - const UIEditorShellInteractionPopupFrame& popupFrame = - frame.popupFrames[index]; - Widgets::AppendUIEditorMenuPopupBackground( - drawList, - popupRequest.layout, - popupRequest.widgetItems, - popupFrame.popupState, - palette.popupPalette, - metrics.popupMetrics); - Widgets::AppendUIEditorMenuPopupForeground( - drawList, - popupRequest.layout, - popupRequest.widgetItems, - popupFrame.popupState, - palette.popupPalette, - metrics.popupMetrics); - } -} - -std::vector FilterShellInputEventsForHostedContentCapture( - const std::vector& inputEvents) { - std::vector filteredEvents = {}; - filteredEvents.reserve(inputEvents.size()); - for (const UIInputEvent& event : inputEvents) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerLeave: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - break; - default: - filteredEvents.push_back(event); - break; - } - } - return filteredEvents; -} - -bool IsPointerInputEventType(UIInputEventType type) { - switch (type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerLeave: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - return true; - default: - return false; - } -} - -bool ShouldHostedContentYieldPointerStream( - const UIEditorShellInteractionFrame& shellFrame, - bool shellInteractiveCaptureActive) { - if (shellInteractiveCaptureActive || - shellFrame.result.requestPointerCapture || - shellFrame.result.releasePointerCapture) { - return true; - } - - return shellFrame.result.workspaceResult.dockHostResult.hitTarget.kind == - UIEditorDockHostHitTargetKind::SplitterHandle; -} - -std::vector FilterHostedContentInputEventsForShellOwnership( - const std::vector& inputEvents, - bool shellOwnsPointerStream) { - if (!shellOwnsPointerStream) { - return inputEvents; - } - - std::vector filteredEvents = {}; - filteredEvents.reserve(inputEvents.size() + 1u); - - bool strippedPointerInput = false; - UIInputEvent lastPointerEvent = {}; - for (const UIInputEvent& event : inputEvents) { - if (IsPointerInputEventType(event.type)) { - strippedPointerInput = true; - lastPointerEvent = event; - continue; - } - - filteredEvents.push_back(event); - } - - if (strippedPointerInput) { - UIInputEvent leaveEvent = {}; - leaveEvent.type = UIInputEventType::PointerLeave; - leaveEvent.position = lastPointerEvent.position; - leaveEvent.modifiers = lastPointerEvent.modifiers; - filteredEvents.push_back(leaveEvent); - } - - return filteredEvents; -} - -} // namespace - -void ProductEditorWorkspace::Initialize( - const std::filesystem::path& repoRoot, - Host::NativeRenderer& renderer) { - m_builtInIcons.Initialize(renderer); - m_hierarchyPanel.SetBuiltInIcons(&m_builtInIcons); - m_projectPanel.SetBuiltInIcons(&m_builtInIcons); - m_projectPanel.SetTextMeasurer(&renderer); - m_hierarchyPanel.Initialize(); - m_projectPanel.Initialize(repoRoot); -} - -void ProductEditorWorkspace::AttachViewportWindowRenderer(Host::D3D12WindowRenderer& renderer) { - m_viewportHostService.AttachWindowRenderer(renderer); -} - -void ProductEditorWorkspace::DetachViewportWindowRenderer() { - m_viewportHostService.DetachWindowRenderer(); -} - -void ProductEditorWorkspace::SetViewportSurfacePresentationEnabled(bool enabled) { - m_viewportHostService.SetSurfacePresentationEnabled(enabled); -} - -void ProductEditorWorkspace::Shutdown() { - m_shellFrame = {}; - m_shellInteractionState = {}; - m_traceEntries.clear(); - m_viewportHostService.Shutdown(); - m_builtInIcons.Shutdown(); -} - -void ProductEditorWorkspace::ResetInteractionState() { - m_shellFrame = {}; - m_shellInteractionState = {}; - m_traceEntries.clear(); - m_hierarchyPanel.ResetInteractionState(); - m_projectPanel.ResetInteractionState(); -} - -void ProductEditorWorkspace::Update( - ProductEditorContext& context, - UIEditorWorkspaceController& workspaceController, - const ::XCEngine::UI::UIRect& bounds, - const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - std::string_view captureText, - ProductEditorShellVariant shellVariant) { - const auto& metrics = ResolveUIEditorShellInteractionMetrics(); - context.SyncSessionFromWorkspace(workspaceController); - UIEditorShellInteractionDefinition definition = - context.BuildShellDefinition(workspaceController, captureText, shellVariant); - m_viewportHostService.BeginFrame(); - definition.workspacePresentations = BuildWorkspacePresentations(definition); - const std::vector shellEvents = - HasHostedContentCapture() - ? FilterShellInputEventsForHostedContentCapture(inputEvents) - : inputEvents; - - m_shellFrame = UpdateUIEditorShellInteraction( - m_shellInteractionState, - workspaceController, - bounds, - definition, - shellEvents, - context.GetShellServices(), - metrics); - const bool shellOwnsHostedContentPointerStream = - ShouldHostedContentYieldPointerStream(m_shellFrame, HasShellInteractiveCapture()); - const std::vector hostedContentEvents = - FilterHostedContentInputEventsForShellOwnership( - inputEvents, - shellOwnsHostedContentPointerStream); - ApplyViewportFramesToShellFrame(m_shellFrame, m_viewportHostService); - context.SyncSessionFromWorkspace(workspaceController); - context.UpdateStatusFromShellResult(workspaceController, m_shellFrame.result); - - const std::string& activePanelId = - workspaceController.GetWorkspace().activePanelId; - m_hierarchyPanel.Update( - m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, - hostedContentEvents, - !m_shellFrame.result.workspaceInputSuppressed, - activePanelId == "hierarchy"); - m_projectPanel.Update( - m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, - hostedContentEvents, - !m_shellFrame.result.workspaceInputSuppressed, - activePanelId == "project"); - m_traceEntries = ConsumeProductEditorWorkspaceEvents(context, *this); - m_inspectorPanel.Update( - context.GetSession(), - m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame); - m_consolePanel.Update( - context.GetSession(), - m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame); -} - -void ProductEditorWorkspace::RenderRequestedViewports( - const ::XCEngine::Rendering::RenderContext& renderContext) { - m_viewportHostService.RenderRequestedViewports(renderContext); -} - -void ProductEditorWorkspace::Append(UIDrawList& drawList) const { - const auto& metrics = ResolveUIEditorShellInteractionMetrics(); - const auto& palette = ResolveUIEditorShellInteractionPalette(); - const UIEditorShellComposeModel shellComposeModel = - BuildShellComposeModelFromFrame(m_shellFrame); - AppendUIEditorShellCompose( - drawList, - m_shellFrame.shellFrame, - shellComposeModel, - m_shellInteractionState.composeState, - palette.shellPalette, - metrics.shellMetrics); - m_consolePanel.Append(drawList); - m_hierarchyPanel.Append(drawList); - m_inspectorPanel.Append(drawList); - m_projectPanel.Append(drawList); - AppendShellPopups(drawList, m_shellFrame, palette, metrics); -} - -const UIEditorShellInteractionFrame& ProductEditorWorkspace::GetShellFrame() const { - return m_shellFrame; -} - -const UIEditorShellInteractionState& ProductEditorWorkspace::GetShellInteractionState() const { - return m_shellInteractionState; -} - -const std::vector& ProductEditorWorkspace::GetTraceEntries() const { - return m_traceEntries; -} - -const std::vector& ProductEditorWorkspace::GetHierarchyPanelEvents() const { - return m_hierarchyPanel.GetFrameEvents(); -} - -const std::vector& ProductEditorWorkspace::GetProjectPanelEvents() const { - return m_projectPanel.GetFrameEvents(); -} - -const std::string& ProductEditorWorkspace::GetBuiltInIconError() const { - return m_builtInIcons.GetLastError(); -} - -ProductProjectPanel::CursorKind ProductEditorWorkspace::GetHostedContentCursorKind() const { - return m_projectPanel.GetCursorKind(); -} - -Widgets::UIEditorDockHostCursorKind ProductEditorWorkspace::GetDockCursorKind() const { - return Widgets::ResolveUIEditorDockHostCursorKind( - m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout); -} - -bool ProductEditorWorkspace::WantsHostPointerCapture() const { - return m_hierarchyPanel.WantsHostPointerCapture() || - m_projectPanel.WantsHostPointerCapture(); -} - -bool ProductEditorWorkspace::WantsHostPointerRelease() const { - return (m_hierarchyPanel.WantsHostPointerRelease() || - m_projectPanel.WantsHostPointerRelease()) && - !HasHostedContentCapture(); -} - -bool ProductEditorWorkspace::HasHostedContentCapture() const { - return m_hierarchyPanel.HasActivePointerCapture() || - m_projectPanel.HasActivePointerCapture(); -} - -bool ProductEditorWorkspace::HasShellInteractiveCapture() const { - if (m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) { - return true; - } - - if (!m_shellInteractionState.workspaceInteractionState.dockHostInteractionState.activeTabDragNodeId.empty()) { - return true; - } - - for (const auto& panelState : m_shellInteractionState.workspaceInteractionState.composeState.panelStates) { - if (panelState.viewportShellState.inputBridgeState.captured) { - return true; - } - } - - return false; -} - -bool ProductEditorWorkspace::HasInteractiveCapture() const { - return HasShellInteractiveCapture() || HasHostedContentCapture(); -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Workspace/EditorWorkspaceEventRouter.cpp b/new_editor/app/Workspace/EditorWorkspaceEventRouter.cpp deleted file mode 100644 index 106d6314..00000000 --- a/new_editor/app/Workspace/EditorWorkspaceEventRouter.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include "Workspace/ProductEditorWorkspaceEventRouter.h" - -#include "Core/ProductEditorContext.h" -#include "Panels/ProductHierarchyPanel.h" -#include "Panels/ProductProjectPanel.h" -#include "Workspace/ProductEditorWorkspace.h" - -#include -#include - -namespace XCEngine::UI::Editor::App { - -namespace { - -std::string DescribeProjectPanelEvent(const ProductProjectPanel::Event& event) { - std::ostringstream stream = {}; - switch (event.kind) { - case ProductProjectPanel::EventKind::AssetSelected: - stream << "AssetSelected"; - break; - case ProductProjectPanel::EventKind::AssetSelectionCleared: - stream << "AssetSelectionCleared"; - break; - case ProductProjectPanel::EventKind::FolderNavigated: - stream << "FolderNavigated"; - break; - case ProductProjectPanel::EventKind::AssetOpened: - stream << "AssetOpened"; - break; - case ProductProjectPanel::EventKind::ContextMenuRequested: - stream << "ContextMenuRequested"; - break; - case ProductProjectPanel::EventKind::None: - default: - stream << "None"; - break; - } - - stream << " source="; - switch (event.source) { - case ProductProjectPanel::EventSource::Tree: - stream << "Tree"; - break; - case ProductProjectPanel::EventSource::Breadcrumb: - stream << "Breadcrumb"; - break; - case ProductProjectPanel::EventSource::GridPrimary: - stream << "GridPrimary"; - break; - case ProductProjectPanel::EventSource::GridDoubleClick: - stream << "GridDoubleClick"; - break; - case ProductProjectPanel::EventSource::GridSecondary: - stream << "GridSecondary"; - break; - case ProductProjectPanel::EventSource::Background: - stream << "Background"; - break; - case ProductProjectPanel::EventSource::None: - default: - stream << "None"; - break; - } - - if (!event.itemId.empty()) { - stream << " item=" << event.itemId; - } - if (!event.displayName.empty()) { - stream << " label=" << event.displayName; - } - return stream.str(); -} - -std::string DescribeHierarchyPanelEvent(const ProductHierarchyPanel::Event& event) { - std::ostringstream stream = {}; - switch (event.kind) { - case ProductHierarchyPanel::EventKind::SelectionChanged: - stream << "SelectionChanged"; - break; - case ProductHierarchyPanel::EventKind::Reparented: - stream << "Reparented"; - break; - case ProductHierarchyPanel::EventKind::MovedToRoot: - stream << "MovedToRoot"; - break; - case ProductHierarchyPanel::EventKind::RenameRequested: - stream << "RenameRequested"; - break; - case ProductHierarchyPanel::EventKind::None: - default: - stream << "None"; - break; - } - - if (!event.itemId.empty()) { - stream << " item=" << event.itemId; - } - if (!event.targetItemId.empty()) { - stream << " target=" << event.targetItemId; - } - if (!event.label.empty()) { - stream << " label=" << event.label; - } - return stream.str(); -} - -void ApplyHierarchySelection( - ProductEditorContext& context, - const ProductHierarchyPanel::Event& event) { - if (event.kind != ProductHierarchyPanel::EventKind::SelectionChanged) { - return; - } - - if (event.itemId.empty()) { - context.ClearSelection(); - return; - } - - ProductEditorSelectionState selection = {}; - selection.kind = ProductEditorSelectionKind::HierarchyNode; - selection.itemId = event.itemId; - selection.displayName = event.label.empty() ? event.itemId : event.label; - context.SetSelection(std::move(selection)); -} - -void ApplyProjectSelection( - ProductEditorContext& context, - const ProductProjectPanel::Event& event) { - switch (event.kind) { - case ProductProjectPanel::EventKind::AssetSelected: { - ProductEditorSelectionState selection = {}; - selection.kind = ProductEditorSelectionKind::ProjectItem; - selection.itemId = event.itemId; - selection.displayName = event.displayName.empty() ? event.itemId : event.displayName; - selection.absolutePath = event.absolutePath; - selection.directory = event.directory; - context.SetSelection(std::move(selection)); - return; - } - case ProductProjectPanel::EventKind::AssetSelectionCleared: - case ProductProjectPanel::EventKind::FolderNavigated: - if (context.GetSession().selection.kind == ProductEditorSelectionKind::ProjectItem) { - context.ClearSelection(); - } - return; - case ProductProjectPanel::EventKind::AssetOpened: - case ProductProjectPanel::EventKind::ContextMenuRequested: - case ProductProjectPanel::EventKind::None: - default: - return; - } -} - -} // namespace - -std::vector ConsumeProductEditorWorkspaceEvents( - ProductEditorContext& context, - const ProductEditorWorkspace& workspace) { - std::vector entries = {}; - - for (const ProductHierarchyPanel::Event& event : workspace.GetHierarchyPanelEvents()) { - ApplyHierarchySelection(context, event); - const std::string message = DescribeHierarchyPanelEvent(event); - context.SetStatus("Hierarchy", message); - entries.push_back(ProductEditorWorkspaceTraceEntry{ "hierarchy", std::move(message) }); - } - - for (const ProductProjectPanel::Event& event : workspace.GetProjectPanelEvents()) { - ApplyProjectSelection(context, event); - const std::string message = DescribeProjectPanelEvent(event); - context.SetStatus("Project", message); - entries.push_back(ProductEditorWorkspaceTraceEntry{ "project", std::move(message) }); - } - - return entries; -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Workspace/EditorWorkspaceEventRouter.h b/new_editor/app/Workspace/EditorWorkspaceEventRouter.h deleted file mode 100644 index 2118713f..00000000 --- a/new_editor/app/Workspace/EditorWorkspaceEventRouter.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include - -namespace XCEngine::UI::Editor::App { - -class ProductEditorContext; -class ProductEditorWorkspace; - -struct ProductEditorWorkspaceTraceEntry { - std::string channel = {}; - std::string message = {}; -}; - -std::vector ConsumeProductEditorWorkspaceEvents( - ProductEditorContext& context, - const ProductEditorWorkspace& workspace); - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/include/XCEditor/Collections/UIEditorListView.h b/new_editor/include/XCEditor/Collections/UIEditorListView.h index 522ebafc..133fce7e 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorListView.h +++ b/new_editor/include/XCEditor/Collections/UIEditorListView.h @@ -44,21 +44,21 @@ struct UIEditorListViewMetrics { struct UIEditorListViewPalette { ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor rowHoverColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor rowSelectedColor = - ::XCEngine::UI::UIColor(0.31f, 0.31f, 0.31f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor rowSelectedFocusedColor = - ::XCEngine::UI::UIColor(0.40f, 0.40f, 0.40f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor primaryTextColor = ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); ::XCEngine::UI::UIColor secondaryTextColor = - ::XCEngine::UI::UIColor(0.70f, 0.70f, 0.70f, 1.0f); + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); }; struct UIEditorListViewLayout { diff --git a/new_editor/include/XCEditor/Collections/UIEditorScrollView.h b/new_editor/include/XCEditor/Collections/UIEditorScrollView.h index 35e2566d..4b853611 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorScrollView.h +++ b/new_editor/include/XCEditor/Collections/UIEditorScrollView.h @@ -33,19 +33,19 @@ struct UIEditorScrollViewMetrics { struct UIEditorScrollViewPalette { ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.16f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.30f, 0.32f, 0.34f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.78f, 0.80f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor scrollbarTrackColor = - ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.21f, 1.0f); + ::XCEngine::UI::UIColor(0.09f, 0.09f, 0.09f, 1.0f); ::XCEngine::UI::UIColor scrollbarThumbColor = - ::XCEngine::UI::UIColor(0.32f, 0.34f, 0.36f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor scrollbarThumbHoverColor = - ::XCEngine::UI::UIColor(0.42f, 0.44f, 0.47f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor scrollbarThumbActiveColor = - ::XCEngine::UI::UIColor(0.50f, 0.52f, 0.56f, 1.0f); + ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); }; struct UIEditorScrollViewLayout { diff --git a/new_editor/include/XCEditor/Collections/UIEditorTabStrip.h b/new_editor/include/XCEditor/Collections/UIEditorTabStrip.h index 5bf51347..9d407087 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorTabStrip.h +++ b/new_editor/include/XCEditor/Collections/UIEditorTabStrip.h @@ -59,45 +59,45 @@ struct UIEditorTabStripMetrics { struct UIEditorTabStripPalette { ::XCEngine::UI::UIColor stripBackgroundColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor headerBackgroundColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); ::XCEngine::UI::UIColor contentBackgroundColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor stripBorderColor = - ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor headerContentSeparatorColor = - ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.36f, 0.36f, 0.36f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor tabColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); ::XCEngine::UI::UIColor tabHoveredColor = - ::XCEngine::UI::UIColor(0.23f, 0.23f, 0.23f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor tabSelectedColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); ::XCEngine::UI::UIColor tabBorderColor = - ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor tabHoveredBorderColor = - ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor tabSelectedBorderColor = - ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor textPrimary = - ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor textSecondary = - ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor textMuted = - ::XCEngine::UI::UIColor(0.58f, 0.58f, 0.58f, 1.0f); + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); ::XCEngine::UI::UIColor closeButtonColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); ::XCEngine::UI::UIColor closeButtonHoveredColor = - ::XCEngine::UI::UIColor(0.25f, 0.25f, 0.25f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor closeButtonBorderColor = - ::XCEngine::UI::UIColor(0.30f, 0.30f, 0.30f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor closeGlyphColor = - ::XCEngine::UI::UIColor(0.83f, 0.83f, 0.83f, 1.0f); + ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); ::XCEngine::UI::UIColor reorderPreviewColor = - ::XCEngine::UI::UIColor(0.82f, 0.82f, 0.82f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 0.90f); }; struct UIEditorTabStripInsertionPreview { diff --git a/new_editor/include/XCEditor/Collections/UIEditorTreeView.h b/new_editor/include/XCEditor/Collections/UIEditorTreeView.h index 79ebae47..55f73dd4 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorTreeView.h +++ b/new_editor/include/XCEditor/Collections/UIEditorTreeView.h @@ -52,21 +52,21 @@ struct UIEditorTreeViewMetrics { struct UIEditorTreeViewPalette { ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor rowHoverColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor rowSelectedColor = - ::XCEngine::UI::UIColor(0.32f, 0.32f, 0.32f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor rowSelectedFocusedColor = - ::XCEngine::UI::UIColor(0.40f, 0.40f, 0.40f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor disclosureColor = - ::XCEngine::UI::UIColor(0.74f, 0.74f, 0.74f, 1.0f); + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); ::XCEngine::UI::UIColor textColor = - ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); }; struct UIEditorTreeViewLayout { diff --git a/new_editor/include/XCEditor/Shell/UIEditorDockHost.h b/new_editor/include/XCEditor/Docking/UIEditorDockHost.h similarity index 88% rename from new_editor/include/XCEditor/Shell/UIEditorDockHost.h rename to new_editor/include/XCEditor/Docking/UIEditorDockHost.h index 7902e506..857472ab 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorDockHost.h +++ b/new_editor/include/XCEditor/Docking/UIEditorDockHost.h @@ -1,9 +1,9 @@ #pragma once -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -75,21 +75,21 @@ struct UIEditorDockHostPalette { UIEditorTabStripPalette tabStripPalette = {}; UIEditorPanelFramePalette panelFramePalette = {}; ::XCEngine::UI::UIColor splitterColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); ::XCEngine::UI::UIColor splitterHoveredColor = - ::XCEngine::UI::UIColor(0.16f, 0.16f, 0.16f, 1.0f); + ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); ::XCEngine::UI::UIColor splitterActiveColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); ::XCEngine::UI::UIColor placeholderTitleColor = - ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); + ::XCEngine::UI::UIColor(0.93f, 0.94f, 0.96f, 1.0f); ::XCEngine::UI::UIColor placeholderTextColor = - ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); + ::XCEngine::UI::UIColor(0.70f, 0.72f, 0.74f, 1.0f); ::XCEngine::UI::UIColor placeholderMutedColor = - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); + ::XCEngine::UI::UIColor(0.58f, 0.59f, 0.62f, 1.0f); ::XCEngine::UI::UIColor dropPreviewFillColor = - ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 0.06f); + ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 0.14f); ::XCEngine::UI::UIColor dropPreviewBorderColor = - ::XCEngine::UI::UIColor(0.95f, 0.95f, 0.95f, 0.55f); + ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 0.78f); }; struct UIEditorDockHostTabItemLayout { @@ -165,10 +165,6 @@ UIEditorDockHostLayout BuildUIEditorDockHostLayout( const UIEditorDockHostState& state = {}, const UIEditorDockHostMetrics& metrics = {}); -UIEditorDockHostDropPreviewLayout ResolveUIEditorDockHostDropPreviewLayout( - const UIEditorDockHostLayout& layout, - const UIEditorDockHostDropPreviewState& state); - UIEditorDockHostHitTarget HitTestUIEditorDockHost( const UIEditorDockHostLayout& layout, const ::XCEngine::UI::UIPoint& point); diff --git a/new_editor/include/XCEditor/Shell/UIEditorDockHostInteraction.h b/new_editor/include/XCEditor/Docking/UIEditorDockHostInteraction.h similarity index 94% rename from new_editor/include/XCEditor/Shell/UIEditorDockHostInteraction.h rename to new_editor/include/XCEditor/Docking/UIEditorDockHostInteraction.h index 7c6feec7..aa05e973 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorDockHostInteraction.h +++ b/new_editor/include/XCEditor/Docking/UIEditorDockHostInteraction.h @@ -1,8 +1,8 @@ #pragma once #include -#include -#include +#include +#include #include #include diff --git a/new_editor/include/XCEditor/Fields/UIEditorAssetField.h b/new_editor/include/XCEditor/Fields/UIEditorAssetField.h index 655c4403..cf88b10b 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorAssetField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorAssetField.h @@ -81,49 +81,49 @@ struct UIEditorAssetFieldPalette { ::XCEngine::UI::UIColor rowActiveColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor valueBoxColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); - ::XCEngine::UI::UIColor valueBoxHoverColor = - ::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f); - ::XCEngine::UI::UIColor valueBoxActiveColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); - ::XCEngine::UI::UIColor readOnlyColor = - ::XCEngine::UI::UIColor(0.16f, 0.16f, 0.16f, 1.0f); - ::XCEngine::UI::UIColor controlBorderColor = ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor valueBoxHoverColor = + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor valueBoxActiveColor = + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor readOnlyColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); + ::XCEngine::UI::UIColor controlBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor controlFocusedBorderColor = - ::XCEngine::UI::UIColor(0.42f, 0.42f, 0.42f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor labelColor = - ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor emptyValueColor = ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); ::XCEngine::UI::UIColor previewBaseColor = - ::XCEngine::UI::UIColor(0.23f, 0.25f, 0.28f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor previewEmptyColor = - ::XCEngine::UI::UIColor(0.26f, 0.26f, 0.26f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor previewBorderColor = - ::XCEngine::UI::UIColor(0.08f, 0.08f, 0.08f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor previewGlyphColor = - ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor statusBadgeColor = - ::XCEngine::UI::UIColor(0.25f, 0.29f, 0.34f, 1.0f); + ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); ::XCEngine::UI::UIColor statusBadgeBorderColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor statusBadgeTextColor = - ::XCEngine::UI::UIColor(0.82f, 0.87f, 0.96f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor actionButtonHoverColor = - ::XCEngine::UI::UIColor(0.28f, 0.28f, 0.28f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor actionButtonActiveColor = - ::XCEngine::UI::UIColor(0.32f, 0.32f, 0.32f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor actionButtonColor = - ::XCEngine::UI::UIColor(0.16f, 0.16f, 0.16f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor separatorColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor pickerGlyphColor = - ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor clearGlyphColor = - ::XCEngine::UI::UIColor(0.95f, 0.68f, 0.68f, 1.0f); + ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); }; struct UIEditorAssetFieldLayout { diff --git a/new_editor/include/XCEditor/Fields/UIEditorBoolField.h b/new_editor/include/XCEditor/Fields/UIEditorBoolField.h index 7643832a..390173ab 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorBoolField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorBoolField.h @@ -50,23 +50,23 @@ struct UIEditorBoolFieldPalette { ::XCEngine::UI::UIColor borderColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor rowHoverColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor rowActiveColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor checkboxColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor checkboxHoverColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); - ::XCEngine::UI::UIColor checkboxReadOnlyColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); - ::XCEngine::UI::UIColor checkboxBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor checkboxReadOnlyColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); + ::XCEngine::UI::UIColor checkboxBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor checkboxMarkColor = - ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor labelColor = - ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); }; struct UIEditorBoolFieldLayout { diff --git a/new_editor/include/XCEditor/Fields/UIEditorColorField.h b/new_editor/include/XCEditor/Fields/UIEditorColorField.h index f4a07fe7..08979e54 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorColorField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorColorField.h @@ -91,51 +91,51 @@ struct UIEditorColorFieldPalette { ::XCEngine::UI::UIColor rowActiveColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor swatchBorderColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor swatchHoverBorderColor = - ::XCEngine::UI::UIColor(0.34f, 0.34f, 0.34f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor swatchReadOnlyOverlayColor = ::XCEngine::UI::UIColor(0.08f, 0.08f, 0.08f, 0.18f); ::XCEngine::UI::UIColor popupColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor popupBorderColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor popupHeaderColor = - ::XCEngine::UI::UIColor(0.43f, 0.24f, 0.05f, 1.0f); + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); ::XCEngine::UI::UIColor popupTitleColor = - ::XCEngine::UI::UIColor(0.95f, 0.95f, 0.95f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor closeButtonColor = - ::XCEngine::UI::UIColor(0.76f, 0.35f, 0.34f, 1.0f); + ::XCEngine::UI::UIColor(0.20f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor closeButtonHoverColor = - ::XCEngine::UI::UIColor(0.82f, 0.40f, 0.39f, 1.0f); + ::XCEngine::UI::UIColor(0.28f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor closeGlyphColor = - ::XCEngine::UI::UIColor(0.95f, 0.95f, 0.95f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor labelColor = - ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); - ::XCEngine::UI::UIColor popupTextColor = - ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); - ::XCEngine::UI::UIColor popupTextMutedColor = ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); + ::XCEngine::UI::UIColor popupTextColor = + ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor popupTextMutedColor = + ::XCEngine::UI::UIColor(0.66f, 0.66f, 0.66f, 1.0f); ::XCEngine::UI::UIColor previewBorderColor = - ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor previewBaseColor = - ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor checkerLightColor = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); ::XCEngine::UI::UIColor checkerDarkColor = - ::XCEngine::UI::UIColor(0.55f, 0.55f, 0.55f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor sliderBorderColor = - ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor numericBoxColor = - ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); - ::XCEngine::UI::UIColor numericBoxBorderColor = ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor numericBoxBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor numericBoxTextColor = - ::XCEngine::UI::UIColor(0.90f, 0.90f, 0.90f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor handleFillColor = ::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f); ::XCEngine::UI::UIColor handleStrokeColor = - ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.4f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 0.5f); }; struct UIEditorColorFieldLayout { diff --git a/new_editor/include/XCEditor/Fields/UIEditorEnumField.h b/new_editor/include/XCEditor/Fields/UIEditorEnumField.h index ae79a2a6..e731818f 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorEnumField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorEnumField.h @@ -60,25 +60,25 @@ struct UIEditorEnumFieldPalette { ::XCEngine::UI::UIColor borderColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor rowHoverColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor rowActiveColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor valueBoxColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor valueBoxHoverColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); - ::XCEngine::UI::UIColor readOnlyColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); - ::XCEngine::UI::UIColor controlBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor readOnlyColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); + ::XCEngine::UI::UIColor controlBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor labelColor = - ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = - ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor arrowColor = - ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); }; struct UIEditorEnumFieldLayout { diff --git a/new_editor/include/XCEditor/Fields/UIEditorEnumFieldInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorEnumFieldInteraction.h index b12f7d5c..ab4b5ac3 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorEnumFieldInteraction.h +++ b/new_editor/include/XCEditor/Fields/UIEditorEnumFieldInteraction.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Fields/UIEditorNumberField.h b/new_editor/include/XCEditor/Fields/UIEditorNumberField.h index 129acddf..6e9c1742 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorNumberField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorNumberField.h @@ -65,23 +65,23 @@ struct UIEditorNumberFieldPalette { ::XCEngine::UI::UIColor rowActiveColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor valueBoxColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor valueBoxHoverColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); - ::XCEngine::UI::UIColor valueBoxEditingColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); - ::XCEngine::UI::UIColor readOnlyColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); - ::XCEngine::UI::UIColor controlBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor valueBoxEditingColor = + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor readOnlyColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); + ::XCEngine::UI::UIColor controlBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor controlFocusedBorderColor = - ::XCEngine::UI::UIColor(0.46f, 0.46f, 0.46f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor labelColor = - ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor readOnlyValueColor = - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); + ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); }; struct UIEditorNumberFieldLayout { diff --git a/new_editor/include/XCEditor/Fields/UIEditorObjectField.h b/new_editor/include/XCEditor/Fields/UIEditorObjectField.h index 7e88e08e..b84d32b8 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorObjectField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorObjectField.h @@ -73,33 +73,33 @@ struct UIEditorObjectFieldPalette { ::XCEngine::UI::UIColor rowActiveColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor valueBoxColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor valueBoxHoverColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); - ::XCEngine::UI::UIColor readOnlyColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); - ::XCEngine::UI::UIColor controlBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor readOnlyColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); + ::XCEngine::UI::UIColor controlBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor controlFocusedBorderColor = - ::XCEngine::UI::UIColor(0.46f, 0.46f, 0.46f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor buttonColor = - ::XCEngine::UI::UIColor(0.16f, 0.16f, 0.16f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor buttonHoverColor = - ::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor buttonActiveColor = - ::XCEngine::UI::UIColor(0.26f, 0.26f, 0.26f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor buttonGlyphColor = - ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor separatorColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor labelColor = - ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor emptyValueColor = - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); + ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); ::XCEngine::UI::UIColor typeColor = - ::XCEngine::UI::UIColor(0.68f, 0.68f, 0.68f, 1.0f); + ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); }; struct UIEditorObjectFieldLayout { diff --git a/new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h b/new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h index 5e927263..8a734ee6 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h +++ b/new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include @@ -172,45 +172,45 @@ struct UIEditorPropertyGridMetrics { struct UIEditorPropertyGridPalette { ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f); - ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); - ::XCEngine::UI::UIColor sectionHeaderColor = - ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); - ::XCEngine::UI::UIColor sectionHeaderHoverColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); - ::XCEngine::UI::UIColor fieldHoverColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); - ::XCEngine::UI::UIColor fieldSelectedColor = - ::XCEngine::UI::UIColor(0.31f, 0.31f, 0.31f, 1.0f); - ::XCEngine::UI::UIColor fieldSelectedFocusedColor = - ::XCEngine::UI::UIColor(0.40f, 0.40f, 0.40f, 1.0f); - ::XCEngine::UI::UIColor valueBoxColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); - ::XCEngine::UI::UIColor valueBoxHoverColor = - ::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f); - ::XCEngine::UI::UIColor valueBoxEditingColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); - ::XCEngine::UI::UIColor valueBoxReadOnlyColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); + ::XCEngine::UI::UIColor focusedBorderColor = + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor sectionHeaderColor = + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); + ::XCEngine::UI::UIColor sectionHeaderHoverColor = + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor fieldHoverColor = + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor fieldSelectedColor = + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor fieldSelectedFocusedColor = + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor valueBoxColor = + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor valueBoxHoverColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); + ::XCEngine::UI::UIColor valueBoxEditingColor = + ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor valueBoxReadOnlyColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor valueBoxBorderColor = - ::XCEngine::UI::UIColor(0.32f, 0.32f, 0.32f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor valueBoxEditingBorderColor = - ::XCEngine::UI::UIColor(0.75f, 0.75f, 0.75f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor disclosureColor = - ::XCEngine::UI::UIColor(0.74f, 0.74f, 0.74f, 1.0f); + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); ::XCEngine::UI::UIColor sectionTextColor = - ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor labelTextColor = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueTextColor = - ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor readOnlyValueTextColor = ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); ::XCEngine::UI::UIColor editTagColor = - ::XCEngine::UI::UIColor(0.62f, 0.78f, 0.96f, 1.0f); + ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); }; struct UIEditorPropertyGridLayout { diff --git a/new_editor/include/XCEditor/Fields/UIEditorTextField.h b/new_editor/include/XCEditor/Fields/UIEditorTextField.h index 345b960a..a428fc3f 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorTextField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorTextField.h @@ -60,23 +60,23 @@ struct UIEditorTextFieldPalette { ::XCEngine::UI::UIColor rowActiveColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor valueBoxColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor valueBoxHoverColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); - ::XCEngine::UI::UIColor valueBoxEditingColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); - ::XCEngine::UI::UIColor readOnlyColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); - ::XCEngine::UI::UIColor controlBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor valueBoxEditingColor = + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor readOnlyColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); + ::XCEngine::UI::UIColor controlBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor controlFocusedBorderColor = - ::XCEngine::UI::UIColor(0.46f, 0.46f, 0.46f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor labelColor = - ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor readOnlyValueColor = - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); + ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); }; struct UIEditorTextFieldLayout { diff --git a/new_editor/include/XCEditor/Fields/UIEditorVector2Field.h b/new_editor/include/XCEditor/Fields/UIEditorVector2Field.h index dfdd4e83..fab34537 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorVector2Field.h +++ b/new_editor/include/XCEditor/Fields/UIEditorVector2Field.h @@ -77,31 +77,31 @@ struct UIEditorVector2FieldPalette { ::XCEngine::UI::UIColor rowActiveColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor componentColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor componentHoverColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); - ::XCEngine::UI::UIColor componentEditingColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); - ::XCEngine::UI::UIColor readOnlyColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); - ::XCEngine::UI::UIColor componentBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor componentEditingColor = + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor readOnlyColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); + ::XCEngine::UI::UIColor componentBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor componentFocusedBorderColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor prefixColor = - ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); ::XCEngine::UI::UIColor prefixBorderColor = - ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor labelColor = - ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor readOnlyValueColor = - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); + ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); ::XCEngine::UI::UIColor axisXColor = - ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f); + ::XCEngine::UI::UIColor(0.78f, 0.42f, 0.42f, 1.0f); ::XCEngine::UI::UIColor axisYColor = - ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f); + ::XCEngine::UI::UIColor(0.56f, 0.72f, 0.46f, 1.0f); }; struct UIEditorVector2FieldLayout { diff --git a/new_editor/include/XCEditor/Fields/UIEditorVector3Field.h b/new_editor/include/XCEditor/Fields/UIEditorVector3Field.h index a28eea3d..0ea428bf 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorVector3Field.h +++ b/new_editor/include/XCEditor/Fields/UIEditorVector3Field.h @@ -77,33 +77,33 @@ struct UIEditorVector3FieldPalette { ::XCEngine::UI::UIColor rowActiveColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor componentColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor componentHoverColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); - ::XCEngine::UI::UIColor componentEditingColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); - ::XCEngine::UI::UIColor readOnlyColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); - ::XCEngine::UI::UIColor componentBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor componentEditingColor = + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor readOnlyColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); + ::XCEngine::UI::UIColor componentBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor componentFocusedBorderColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor prefixColor = - ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); ::XCEngine::UI::UIColor prefixBorderColor = - ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor labelColor = - ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor readOnlyValueColor = - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); + ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); ::XCEngine::UI::UIColor axisXColor = - ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f); + ::XCEngine::UI::UIColor(0.78f, 0.42f, 0.42f, 1.0f); ::XCEngine::UI::UIColor axisYColor = - ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f); + ::XCEngine::UI::UIColor(0.56f, 0.72f, 0.46f, 1.0f); ::XCEngine::UI::UIColor axisZColor = - ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f); + ::XCEngine::UI::UIColor(0.45f, 0.62f, 0.82f, 1.0f); }; struct UIEditorVector3FieldLayout { diff --git a/new_editor/include/XCEditor/Fields/UIEditorVector4Field.h b/new_editor/include/XCEditor/Fields/UIEditorVector4Field.h index 451258e7..4ccd4105 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorVector4Field.h +++ b/new_editor/include/XCEditor/Fields/UIEditorVector4Field.h @@ -88,35 +88,35 @@ struct UIEditorVector4FieldPalette { ::XCEngine::UI::UIColor rowActiveColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor componentColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor componentHoverColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); - ::XCEngine::UI::UIColor componentEditingColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); - ::XCEngine::UI::UIColor readOnlyColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); - ::XCEngine::UI::UIColor componentBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor componentEditingColor = + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor readOnlyColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); + ::XCEngine::UI::UIColor componentBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor componentFocusedBorderColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor prefixColor = - ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); ::XCEngine::UI::UIColor prefixBorderColor = - ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor labelColor = - ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor readOnlyValueColor = - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); + ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); ::XCEngine::UI::UIColor axisXColor = - ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f); + ::XCEngine::UI::UIColor(0.78f, 0.42f, 0.42f, 1.0f); ::XCEngine::UI::UIColor axisYColor = - ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f); + ::XCEngine::UI::UIColor(0.56f, 0.72f, 0.46f, 1.0f); ::XCEngine::UI::UIColor axisZColor = - ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f); + ::XCEngine::UI::UIColor(0.45f, 0.62f, 0.82f, 1.0f); ::XCEngine::UI::UIColor axisWColor = - ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f); + ::XCEngine::UI::UIColor(0.76f, 0.66f, 0.42f, 1.0f); }; struct UIEditorVector4FieldLayout { diff --git a/new_editor/include/XCEditor/Foundation/UIEditorCommandRegistry.h b/new_editor/include/XCEditor/Foundation/UIEditorCommandRegistry.h index e2b52846..5447e747 100644 --- a/new_editor/include/XCEditor/Foundation/UIEditorCommandRegistry.h +++ b/new_editor/include/XCEditor/Foundation/UIEditorCommandRegistry.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Foundation/UIEditorTheme.h b/new_editor/include/XCEditor/Foundation/UIEditorTheme.h index b9b18224..c92b7bc7 100644 --- a/new_editor/include/XCEditor/Foundation/UIEditorTheme.h +++ b/new_editor/include/XCEditor/Foundation/UIEditorTheme.h @@ -15,14 +15,14 @@ #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include -#include +#include namespace XCEngine::UI::Editor { diff --git a/new_editor/include/XCEditor/Shell/UIEditorMenuBar.h b/new_editor/include/XCEditor/Menu/UIEditorMenuBar.h similarity index 91% rename from new_editor/include/XCEditor/Shell/UIEditorMenuBar.h rename to new_editor/include/XCEditor/Menu/UIEditorMenuBar.h index 20982c32..c87e3f5f 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorMenuBar.h +++ b/new_editor/include/XCEditor/Menu/UIEditorMenuBar.h @@ -43,13 +43,13 @@ struct UIEditorMenuBarMetrics { struct UIEditorMenuBarPalette { ::XCEngine::UI::UIColor barColor = - ::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor buttonColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor buttonHoveredColor = - ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor buttonOpenColor = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor borderColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor focusedBorderColor = @@ -57,11 +57,11 @@ struct UIEditorMenuBarPalette { ::XCEngine::UI::UIColor openBorderColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor textPrimary = - ::XCEngine::UI::UIColor(0.08f, 0.08f, 0.08f, 1.0f); + ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); ::XCEngine::UI::UIColor textMuted = - ::XCEngine::UI::UIColor(0.28f, 0.28f, 0.28f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor textDisabled = - ::XCEngine::UI::UIColor(0.50f, 0.50f, 0.50f, 1.0f); + ::XCEngine::UI::UIColor(0.46f, 0.46f, 0.46f, 1.0f); }; struct UIEditorMenuBarLayout { diff --git a/new_editor/include/XCEditor/Shell/UIEditorMenuModel.h b/new_editor/include/XCEditor/Menu/UIEditorMenuModel.h similarity index 100% rename from new_editor/include/XCEditor/Shell/UIEditorMenuModel.h rename to new_editor/include/XCEditor/Menu/UIEditorMenuModel.h diff --git a/new_editor/include/XCEditor/Shell/UIEditorMenuPopup.h b/new_editor/include/XCEditor/Menu/UIEditorMenuPopup.h similarity index 88% rename from new_editor/include/XCEditor/Shell/UIEditorMenuPopup.h rename to new_editor/include/XCEditor/Menu/UIEditorMenuPopup.h index bda33779..27afddb0 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorMenuPopup.h +++ b/new_editor/include/XCEditor/Menu/UIEditorMenuPopup.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -55,23 +55,23 @@ struct UIEditorMenuPopupMetrics { struct UIEditorMenuPopupPalette { ::XCEngine::UI::UIColor popupColor = - ::XCEngine::UI::UIColor(0.97f, 0.97f, 0.97f, 1.0f); - ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); - ::XCEngine::UI::UIColor itemHoverColor = - ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); - ::XCEngine::UI::UIColor itemOpenColor = - ::XCEngine::UI::UIColor(0.82f, 0.82f, 0.82f, 1.0f); - ::XCEngine::UI::UIColor separatorColor = - ::XCEngine::UI::UIColor(0.78f, 0.78f, 0.78f, 1.0f); - ::XCEngine::UI::UIColor textPrimary = ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); + ::XCEngine::UI::UIColor borderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); + ::XCEngine::UI::UIColor itemHoverColor = + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor itemOpenColor = + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor separatorColor = + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor textPrimary = + ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); ::XCEngine::UI::UIColor textMuted = - ::XCEngine::UI::UIColor(0.34f, 0.34f, 0.34f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor textDisabled = - ::XCEngine::UI::UIColor(0.58f, 0.58f, 0.58f, 1.0f); + ::XCEngine::UI::UIColor(0.46f, 0.46f, 0.46f, 1.0f); ::XCEngine::UI::UIColor glyphColor = - ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); }; struct UIEditorMenuPopupLayout { diff --git a/new_editor/include/XCEditor/Shell/UIEditorMenuSession.h b/new_editor/include/XCEditor/Menu/UIEditorMenuSession.h similarity index 100% rename from new_editor/include/XCEditor/Shell/UIEditorMenuSession.h rename to new_editor/include/XCEditor/Menu/UIEditorMenuSession.h diff --git a/new_editor/include/XCEditor/Shell/UIEditorPanelContentHost.h b/new_editor/include/XCEditor/Panels/UIEditorPanelContentHost.h similarity index 96% rename from new_editor/include/XCEditor/Shell/UIEditorPanelContentHost.h rename to new_editor/include/XCEditor/Panels/UIEditorPanelContentHost.h index c13d83a3..8751b89b 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorPanelContentHost.h +++ b/new_editor/include/XCEditor/Panels/UIEditorPanelContentHost.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorPanelFrame.h b/new_editor/include/XCEditor/Panels/UIEditorPanelFrame.h similarity index 84% rename from new_editor/include/XCEditor/Shell/UIEditorPanelFrame.h rename to new_editor/include/XCEditor/Panels/UIEditorPanelFrame.h index ca9c4fe9..97de69bc 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorPanelFrame.h +++ b/new_editor/include/XCEditor/Panels/UIEditorPanelFrame.h @@ -61,35 +61,35 @@ struct UIEditorPanelFrameMetrics { struct UIEditorPanelFramePalette { ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor headerColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); ::XCEngine::UI::UIColor footerColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.23f, 0.23f, 0.23f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor hoveredBorderColor = - ::XCEngine::UI::UIColor(0.28f, 0.28f, 0.28f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor activeBorderColor = - ::XCEngine::UI::UIColor(0.32f, 0.32f, 0.32f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.36f, 0.36f, 0.36f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor textPrimary = - ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor textSecondary = - ::XCEngine::UI::UIColor(0.64f, 0.64f, 0.64f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor textMuted = - ::XCEngine::UI::UIColor(0.58f, 0.58f, 0.58f, 1.0f); + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); ::XCEngine::UI::UIColor actionButtonColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); ::XCEngine::UI::UIColor actionButtonHoveredColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor actionButtonSelectedColor = - ::XCEngine::UI::UIColor(0.27f, 0.27f, 0.27f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor actionButtonBorderColor = - ::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor actionGlyphColor = - ::XCEngine::UI::UIColor(0.82f, 0.82f, 0.82f, 1.0f); + ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); }; struct UIEditorPanelFrameLayout { diff --git a/new_editor/include/XCEditor/Shell/UIEditorPanelHostLifecycle.h b/new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.h similarity index 94% rename from new_editor/include/XCEditor/Shell/UIEditorPanelHostLifecycle.h rename to new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.h index 81531db6..8238186a 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorPanelHostLifecycle.h +++ b/new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorPanelRegistry.h b/new_editor/include/XCEditor/Panels/UIEditorPanelRegistry.h similarity index 100% rename from new_editor/include/XCEditor/Shell/UIEditorPanelRegistry.h rename to new_editor/include/XCEditor/Panels/UIEditorPanelRegistry.h diff --git a/new_editor/include/XCEditor/Shell/UIEditorShellAsset.h b/new_editor/include/XCEditor/Shell/UIEditorShellAsset.h index b981371a..59df21e8 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorShellAsset.h +++ b/new_editor/include/XCEditor/Shell/UIEditorShellAsset.h @@ -1,11 +1,11 @@ #pragma once #include -#include -#include +#include +#include #include -#include -#include +#include +#include #include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h b/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h index 94c71e63..934f99c0 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h +++ b/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include namespace XCEngine::UI::Editor { @@ -39,17 +39,17 @@ struct UIEditorShellToolbarMetrics { struct UIEditorShellToolbarPalette { ::XCEngine::UI::UIColor barColor = - ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor groupColor = - ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); ::XCEngine::UI::UIColor groupBorderColor = - ::XCEngine::UI::UIColor(0.25f, 0.25f, 0.25f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor buttonColor = - ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor buttonBorderColor = - ::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor iconColor = - ::XCEngine::UI::UIColor(0.82f, 0.82f, 0.82f, 1.0f); + ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); }; struct UIEditorShellComposeModel { @@ -78,9 +78,9 @@ struct UIEditorShellComposeMetrics { struct UIEditorShellComposePalette { ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor surfaceBorderColor = - ::XCEngine::UI::UIColor(0.27f, 0.27f, 0.27f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); Widgets::UIEditorMenuBarPalette menuBarPalette = {}; UIEditorShellToolbarPalette toolbarPalette = {}; Widgets::UIEditorDockHostPalette dockHostPalette = {}; diff --git a/new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h b/new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h index 8e0aa266..4880183a 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h +++ b/new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h @@ -1,12 +1,12 @@ #pragma once #include -#include -#include +#include +#include #include -#include -#include -#include +#include +#include +#include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorStatusBar.h b/new_editor/include/XCEditor/Shell/UIEditorStatusBar.h index acced79c..6985e140 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorStatusBar.h +++ b/new_editor/include/XCEditor/Shell/UIEditorStatusBar.h @@ -64,27 +64,27 @@ struct UIEditorStatusBarMetrics { struct UIEditorStatusBarPalette { ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(0.16f, 0.16f, 0.16f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.23f, 0.23f, 0.23f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.35f, 0.35f, 0.35f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor segmentColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor segmentHoveredColor = - ::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor segmentActiveColor = - ::XCEngine::UI::UIColor(0.25f, 0.25f, 0.25f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor segmentBorderColor = - ::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor separatorColor = - ::XCEngine::UI::UIColor(0.27f, 0.27f, 0.27f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor textPrimary = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor textMuted = ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); ::XCEngine::UI::UIColor textAccent = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); }; struct UIEditorStatusBarLayout { diff --git a/new_editor/include/XCEditor/Shell/UIEditorStructuredShell.h b/new_editor/include/XCEditor/Shell/UIEditorStructuredShell.h index 25597e27..d932d33a 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorStructuredShell.h +++ b/new_editor/include/XCEditor/Shell/UIEditorStructuredShell.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorViewportInputBridge.h b/new_editor/include/XCEditor/Viewport/UIEditorViewportInputBridge.h similarity index 100% rename from new_editor/include/XCEditor/Shell/UIEditorViewportInputBridge.h rename to new_editor/include/XCEditor/Viewport/UIEditorViewportInputBridge.h diff --git a/new_editor/include/XCEditor/Shell/UIEditorViewportShell.h b/new_editor/include/XCEditor/Viewport/UIEditorViewportShell.h similarity index 94% rename from new_editor/include/XCEditor/Shell/UIEditorViewportShell.h rename to new_editor/include/XCEditor/Viewport/UIEditorViewportShell.h index 8522fb82..37c3008d 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorViewportShell.h +++ b/new_editor/include/XCEditor/Viewport/UIEditorViewportShell.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorViewportSlot.h b/new_editor/include/XCEditor/Viewport/UIEditorViewportSlot.h similarity index 85% rename from new_editor/include/XCEditor/Shell/UIEditorViewportSlot.h rename to new_editor/include/XCEditor/Viewport/UIEditorViewportSlot.h index 68d8e4ff..690af7cb 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorViewportSlot.h +++ b/new_editor/include/XCEditor/Viewport/UIEditorViewportSlot.h @@ -86,45 +86,45 @@ struct UIEditorViewportSlotMetrics { struct UIEditorViewportSlotPalette { ::XCEngine::UI::UIColor frameColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor(0.09f, 0.09f, 0.09f, 1.0f); ::XCEngine::UI::UIColor topBarColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor(0.09f, 0.09f, 0.09f, 1.0f); ::XCEngine::UI::UIColor surfaceHoverOverlayColor = - ::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 0.24f); + ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 0.08f); ::XCEngine::UI::UIColor surfaceActiveOverlayColor = - ::XCEngine::UI::UIColor(0.34f, 0.34f, 0.34f, 0.18f); + ::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 0.08f); ::XCEngine::UI::UIColor captureOverlayColor = - ::XCEngine::UI::UIColor(0.70f, 0.70f, 0.70f, 0.10f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 0.05f); ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.23f, 0.23f, 0.23f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.36f, 0.36f, 0.36f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor surfaceBorderColor = - ::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor surfaceHoveredBorderColor = - ::XCEngine::UI::UIColor(0.28f, 0.28f, 0.28f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor surfaceActiveBorderColor = - ::XCEngine::UI::UIColor(0.33f, 0.33f, 0.33f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor surfaceCapturedBorderColor = - ::XCEngine::UI::UIColor(0.38f, 0.38f, 0.38f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor toolColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor toolHoveredColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor toolSelectedColor = - ::XCEngine::UI::UIColor(0.27f, 0.27f, 0.27f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor toolDisabledColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.09f, 0.09f, 0.09f, 1.0f); ::XCEngine::UI::UIColor toolBorderColor = - ::XCEngine::UI::UIColor(0.28f, 0.28f, 0.28f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor textPrimary = - ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor textSecondary = - ::XCEngine::UI::UIColor(0.68f, 0.68f, 0.68f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor textMuted = - ::XCEngine::UI::UIColor(0.58f, 0.58f, 0.58f, 1.0f); + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); ::XCEngine::UI::UIColor imageTint = ::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f); UIEditorStatusBarPalette statusBarPalette = {}; diff --git a/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h b/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h index 1f1437a7..aa74cdf3 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h +++ b/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h @@ -6,31 +6,31 @@ namespace XCEngine::UI::Editor::Widgets { struct UIEditorInspectorFieldStyleTokens { ::XCEngine::UI::UIColor rowHoverColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor rowActiveColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); ::XCEngine::UI::UIColor labelColor = - ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor readOnlyValueColor = ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); ::XCEngine::UI::UIColor controlColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor controlHoverColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); - ::XCEngine::UI::UIColor controlEditingColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); - ::XCEngine::UI::UIColor controlReadOnlyColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); + ::XCEngine::UI::UIColor controlEditingColor = + ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor controlReadOnlyColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor controlBorderColor = - ::XCEngine::UI::UIColor(0.30f, 0.30f, 0.30f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor controlFocusedBorderColor = - ::XCEngine::UI::UIColor(0.64f, 0.64f, 0.64f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor prefixColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); ::XCEngine::UI::UIColor prefixBorderColor = - ::XCEngine::UI::UIColor(0.31f, 0.31f, 0.31f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor axisXColor = ::XCEngine::UI::UIColor(0.78f, 0.42f, 0.42f, 1.0f); ::XCEngine::UI::UIColor axisYColor = @@ -40,47 +40,47 @@ struct UIEditorInspectorFieldStyleTokens { ::XCEngine::UI::UIColor axisWColor = ::XCEngine::UI::UIColor(0.76f, 0.66f, 0.42f, 1.0f); ::XCEngine::UI::UIColor arrowColor = - ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor swatchBorderColor = - ::XCEngine::UI::UIColor(0.30f, 0.30f, 0.30f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor swatchHoverBorderColor = - ::XCEngine::UI::UIColor(0.64f, 0.64f, 0.64f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor swatchReadOnlyOverlayColor = ::XCEngine::UI::UIColor(0.08f, 0.08f, 0.08f, 0.18f); ::XCEngine::UI::UIColor popupColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); ::XCEngine::UI::UIColor popupBorderColor = - ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor popupHeaderColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); ::XCEngine::UI::UIColor popupTitleColor = - ::XCEngine::UI::UIColor(0.93f, 0.93f, 0.93f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor popupTextColor = - ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); + ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); ::XCEngine::UI::UIColor popupTextMutedColor = - ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); + ::XCEngine::UI::UIColor(0.66f, 0.66f, 0.66f, 1.0f); ::XCEngine::UI::UIColor previewBorderColor = - ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor previewBaseColor = - ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor checkerLightColor = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); ::XCEngine::UI::UIColor checkerDarkColor = - ::XCEngine::UI::UIColor(0.55f, 0.55f, 0.55f, 1.0f); + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); ::XCEngine::UI::UIColor sliderBorderColor = - ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor numericBoxColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); ::XCEngine::UI::UIColor numericBoxBorderColor = - ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor numericBoxTextColor = - ::XCEngine::UI::UIColor(0.93f, 0.93f, 0.93f, 1.0f); + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor closeButtonColor = ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); ::XCEngine::UI::UIColor closeButtonHoverColor = - ::XCEngine::UI::UIColor(0.25f, 0.25f, 0.25f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor closeGlyphColor = - ::XCEngine::UI::UIColor(0.85f, 0.85f, 0.85f, 1.0f); + ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); ::XCEngine::UI::UIColor handleFillColor = ::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f); ::XCEngine::UI::UIColor handleStrokeColor = diff --git a/new_editor/include/XCEditor/Shell/UIEditorWindowWorkspaceController.h b/new_editor/include/XCEditor/Workspace/UIEditorWindowWorkspaceController.h similarity index 97% rename from new_editor/include/XCEditor/Shell/UIEditorWindowWorkspaceController.h rename to new_editor/include/XCEditor/Workspace/UIEditorWindowWorkspaceController.h index ac4e1932..70ae3014 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorWindowWorkspaceController.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWindowWorkspaceController.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorWindowWorkspaceModel.h b/new_editor/include/XCEditor/Workspace/UIEditorWindowWorkspaceModel.h similarity index 94% rename from new_editor/include/XCEditor/Shell/UIEditorWindowWorkspaceModel.h rename to new_editor/include/XCEditor/Workspace/UIEditorWindowWorkspaceModel.h index b743083c..40c1b08c 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorWindowWorkspaceModel.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWindowWorkspaceModel.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceCompose.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceCompose.h similarity index 93% rename from new_editor/include/XCEditor/Shell/UIEditorWorkspaceCompose.h rename to new_editor/include/XCEditor/Workspace/UIEditorWorkspaceCompose.h index 19f9a0af..848436a1 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceCompose.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceCompose.h @@ -1,10 +1,10 @@ #pragma once -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceController.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h similarity index 97% rename from new_editor/include/XCEditor/Shell/UIEditorWorkspaceController.h rename to new_editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h index 9ac80923..79c6c1e5 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceController.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceInteraction.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h similarity index 92% rename from new_editor/include/XCEditor/Shell/UIEditorWorkspaceInteraction.h rename to new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h index 1d0327c1..6e5cdf8f 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceInteraction.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceLayoutPersistence.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceLayoutPersistence.h similarity index 96% rename from new_editor/include/XCEditor/Shell/UIEditorWorkspaceLayoutPersistence.h rename to new_editor/include/XCEditor/Workspace/UIEditorWorkspaceLayoutPersistence.h index c07acca4..f9182d5f 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceLayoutPersistence.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceLayoutPersistence.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceModel.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceModel.h similarity index 100% rename from new_editor/include/XCEditor/Shell/UIEditorWorkspaceModel.h rename to new_editor/include/XCEditor/Workspace/UIEditorWorkspaceModel.h diff --git a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceSession.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceSession.h similarity index 96% rename from new_editor/include/XCEditor/Shell/UIEditorWorkspaceSession.h rename to new_editor/include/XCEditor/Workspace/UIEditorWorkspaceSession.h index 807c8dba..cc54e931 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceSession.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceSession.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceTransfer.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceTransfer.h similarity index 95% rename from new_editor/include/XCEditor/Shell/UIEditorWorkspaceTransfer.h rename to new_editor/include/XCEditor/Workspace/UIEditorWorkspaceTransfer.h index fe783ba8..e5355539 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorWorkspaceTransfer.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceTransfer.h @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace XCEngine::UI::Editor { diff --git a/new_editor/src/Shell/UIEditorDockHost.cpp b/new_editor/src/Docking/UIEditorDockHost.cpp similarity index 98% rename from new_editor/src/Shell/UIEditorDockHost.cpp rename to new_editor/src/Docking/UIEditorDockHost.cpp index 4e84458e..d65fe6f3 100644 --- a/new_editor/src/Shell/UIEditorDockHost.cpp +++ b/new_editor/src/Docking/UIEditorDockHost.cpp @@ -1,4 +1,4 @@ -#include +#include #include @@ -653,14 +653,6 @@ UIEditorDockHostLayout BuildUIEditorDockHostLayout( return layout; } -UIEditorDockHostDropPreviewLayout ResolveUIEditorDockHostDropPreviewLayout( - const UIEditorDockHostLayout& layout, - const UIEditorDockHostDropPreviewState& state) { - UIEditorDockHostState dockHostState = {}; - dockHostState.dropPreview = state; - return ResolveDropPreviewLayout(layout, dockHostState); -} - UIEditorDockHostHitTarget HitTestUIEditorDockHost( const UIEditorDockHostLayout& layout, const UIPoint& point) { diff --git a/new_editor/src/Shell/UIEditorDockHostInteraction.cpp b/new_editor/src/Docking/UIEditorDockHostInteraction.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorDockHostInteraction.cpp rename to new_editor/src/Docking/UIEditorDockHostInteraction.cpp index d2c82c7a..7cc8f330 100644 --- a/new_editor/src/Shell/UIEditorDockHostInteraction.cpp +++ b/new_editor/src/Docking/UIEditorDockHostInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Fields/UIEditorFieldStyle.cpp b/new_editor/src/Fields/UIEditorFieldStyle.cpp index f17e4271..8eea856b 100644 --- a/new_editor/src/Fields/UIEditorFieldStyle.cpp +++ b/new_editor/src/Fields/UIEditorFieldStyle.cpp @@ -1,4 +1,5 @@ #include +#include namespace XCEngine::UI::Editor { @@ -16,8 +17,7 @@ const Widgets::UIEditorPropertyGridMetrics& GetUIEditorFixedPropertyGridMetrics( } const Widgets::UIEditorPropertyGridPalette& GetUIEditorFixedPropertyGridPalette() { - static const Widgets::UIEditorPropertyGridPalette kPalette = {}; - return kPalette; + return ResolveUIEditorPropertyGridPalette(); } Widgets::UIEditorBoolFieldMetrics BuildUIEditorPropertyGridBoolFieldMetrics( diff --git a/new_editor/src/Fields/UIEditorPropertyGrid.cpp b/new_editor/src/Fields/UIEditorPropertyGrid.cpp index 71f4fa4c..f02a8f04 100644 --- a/new_editor/src/Fields/UIEditorPropertyGrid.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGrid.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp index 7b559e5a..a92d863f 100644 --- a/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include diff --git a/new_editor/src/Foundation/UIEditorRuntimeTrace.cpp b/new_editor/src/Foundation/UIEditorRuntimeTrace.cpp index 42ff5a59..c3395eb6 100644 --- a/new_editor/src/Foundation/UIEditorRuntimeTrace.cpp +++ b/new_editor/src/Foundation/UIEditorRuntimeTrace.cpp @@ -104,6 +104,7 @@ void AppendUIEditorRuntimeTrace( } AppendTraceLine(g_runtimeTraceStream, channel, message); + g_runtimeTraceStream.flush(); } void AppendUIEditorCrashTrace( diff --git a/new_editor/src/Foundation/UIEditorTheme.cpp b/new_editor/src/Foundation/UIEditorTheme.cpp index f438553d..1f8d8d19 100644 --- a/new_editor/src/Foundation/UIEditorTheme.cpp +++ b/new_editor/src/Foundation/UIEditorTheme.cpp @@ -6,6 +6,22 @@ namespace { using ::XCEngine::UI::UIColor; +const UIColor kSurfaceLower(0.09f, 0.09f, 0.09f, 1.0f); +const UIColor kSurfaceBase(0.10f, 0.10f, 0.10f, 1.0f); +const UIColor kSurfaceRaised(0.11f, 0.11f, 0.11f, 1.0f); +const UIColor kSurfacePanel(0.12f, 0.12f, 0.12f, 1.0f); +const UIColor kSurfaceHover(0.14f, 0.14f, 0.14f, 1.0f); +const UIColor kSurfaceActive(0.17f, 0.17f, 0.17f, 1.0f); +const UIColor kSurfaceActiveStrong(0.19f, 0.19f, 0.19f, 1.0f); +const UIColor kBorderSubtle(0.14f, 0.14f, 0.14f, 1.0f); +const UIColor kBorderDefault(0.15f, 0.15f, 0.15f, 1.0f); +const UIColor kBorderFocused(0.19f, 0.19f, 0.19f, 1.0f); +const UIColor kTextPrimary(0.92f, 0.92f, 0.92f, 1.0f); +const UIColor kTextStrong(0.94f, 0.94f, 0.94f, 1.0f); +const UIColor kTextSecondary(0.72f, 0.72f, 0.72f, 1.0f); +const UIColor kTextMuted(0.62f, 0.62f, 0.62f, 1.0f); +const UIColor kTextDisabled(0.46f, 0.46f, 0.46f, 1.0f); + template const TValue& GetDefaultValue() { static const TValue value = {}; @@ -14,129 +30,129 @@ const TValue& GetDefaultValue() { Widgets::UIEditorMenuBarPalette BuildMenuBarPalette() { Widgets::UIEditorMenuBarPalette palette = {}; - palette.barColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); + palette.barColor = kSurfaceBase; palette.buttonColor = UIColor(0.0f, 0.0f, 0.0f, 0.0f); - palette.buttonHoveredColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.buttonOpenColor = UIColor(0.17f, 0.17f, 0.17f, 1.0f); + palette.buttonHoveredColor = kSurfaceHover; + palette.buttonOpenColor = kSurfaceActive; palette.borderColor = UIColor(0.0f, 0.0f, 0.0f, 0.0f); palette.focusedBorderColor = UIColor(0.0f, 0.0f, 0.0f, 0.0f); palette.openBorderColor = UIColor(0.0f, 0.0f, 0.0f, 0.0f); - palette.textPrimary = UIColor(0.93f, 0.93f, 0.93f, 1.0f); - palette.textMuted = UIColor(0.72f, 0.72f, 0.72f, 1.0f); - palette.textDisabled = UIColor(0.45f, 0.45f, 0.45f, 1.0f); + palette.textPrimary = kTextStrong; + palette.textMuted = kTextSecondary; + palette.textDisabled = kTextDisabled; return palette; } Widgets::UIEditorMenuPopupPalette BuildMenuPopupPalette() { Widgets::UIEditorMenuPopupPalette palette = {}; - palette.popupColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.borderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.itemHoverColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.itemOpenColor = UIColor(0.17f, 0.17f, 0.17f, 1.0f); - palette.separatorColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.textPrimary = UIColor(0.93f, 0.93f, 0.93f, 1.0f); - palette.textMuted = UIColor(0.68f, 0.68f, 0.68f, 1.0f); - palette.textDisabled = UIColor(0.45f, 0.45f, 0.45f, 1.0f); - palette.glyphColor = UIColor(0.90f, 0.90f, 0.90f, 1.0f); + palette.popupColor = kSurfaceBase; + palette.borderColor = kBorderDefault; + palette.itemHoverColor = kSurfaceHover; + palette.itemOpenColor = kSurfaceActive; + palette.separatorColor = kBorderSubtle; + palette.textPrimary = kTextStrong; + palette.textMuted = kTextSecondary; + palette.textDisabled = kTextDisabled; + palette.glyphColor = kTextPrimary; return palette; } Widgets::UIEditorListViewPalette BuildListViewPalette() { Widgets::UIEditorListViewPalette palette = {}; - palette.surfaceColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.borderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.focusedBorderColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f); - palette.rowHoverColor = UIColor(0.13f, 0.13f, 0.13f, 1.0f); - palette.rowSelectedColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.rowSelectedFocusedColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f); - palette.primaryTextColor = UIColor(0.93f, 0.93f, 0.93f, 1.0f); - palette.secondaryTextColor = UIColor(0.67f, 0.67f, 0.67f, 1.0f); + palette.surfaceColor = kSurfaceBase; + palette.borderColor = kBorderDefault; + palette.focusedBorderColor = kBorderFocused; + palette.rowHoverColor = kSurfaceHover; + palette.rowSelectedColor = kSurfaceActive; + palette.rowSelectedFocusedColor = kSurfaceActiveStrong; + palette.primaryTextColor = kTextStrong; + palette.secondaryTextColor = kTextMuted; return palette; } Widgets::UIEditorTreeViewPalette BuildTreeViewPalette() { Widgets::UIEditorTreeViewPalette palette = {}; - palette.surfaceColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.borderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.focusedBorderColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f); - palette.rowHoverColor = UIColor(0.13f, 0.13f, 0.13f, 1.0f); - palette.rowSelectedColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.rowSelectedFocusedColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f); - palette.disclosureColor = UIColor(0.62f, 0.62f, 0.62f, 1.0f); - palette.textColor = UIColor(0.92f, 0.92f, 0.92f, 1.0f); + palette.surfaceColor = kSurfaceBase; + palette.borderColor = kBorderDefault; + palette.focusedBorderColor = kBorderFocused; + palette.rowHoverColor = kSurfaceHover; + palette.rowSelectedColor = kSurfaceActive; + palette.rowSelectedFocusedColor = kSurfaceActiveStrong; + palette.disclosureColor = kTextMuted; + palette.textColor = kTextPrimary; return palette; } Widgets::UIEditorScrollViewPalette BuildScrollViewPalette() { Widgets::UIEditorScrollViewPalette palette = {}; - palette.surfaceColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.borderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.focusedBorderColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f); - palette.scrollbarTrackColor = UIColor(0.08f, 0.08f, 0.08f, 1.0f); - palette.scrollbarThumbColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.scrollbarThumbHoverColor = UIColor(0.17f, 0.17f, 0.17f, 1.0f); + palette.surfaceColor = kSurfaceBase; + palette.borderColor = kBorderDefault; + palette.focusedBorderColor = kBorderFocused; + palette.scrollbarTrackColor = kSurfaceLower; + palette.scrollbarThumbColor = kSurfaceHover; + palette.scrollbarThumbHoverColor = kSurfaceActive; palette.scrollbarThumbActiveColor = UIColor(0.20f, 0.20f, 0.20f, 1.0f); return palette; } Widgets::UIEditorTabStripPalette BuildTabStripPalette() { Widgets::UIEditorTabStripPalette palette = {}; - palette.stripBackgroundColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.headerBackgroundColor = UIColor(0.11f, 0.11f, 0.11f, 1.0f); - palette.contentBackgroundColor = UIColor(0.12f, 0.12f, 0.12f, 1.0f); - palette.stripBorderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.headerContentSeparatorColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.focusedBorderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.tabColor = UIColor(0.12f, 0.12f, 0.12f, 1.0f); - palette.tabHoveredColor = UIColor(0.15f, 0.15f, 0.15f, 1.0f); - palette.tabSelectedColor = UIColor(0.12f, 0.12f, 0.12f, 1.0f); - palette.tabBorderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.tabHoveredBorderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.tabSelectedBorderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.textPrimary = UIColor(0.92f, 0.92f, 0.92f, 1.0f); - palette.textSecondary = UIColor(0.72f, 0.72f, 0.72f, 1.0f); - palette.textMuted = UIColor(0.58f, 0.58f, 0.58f, 1.0f); - palette.closeButtonColor = UIColor(0.12f, 0.12f, 0.12f, 1.0f); - palette.closeButtonHoveredColor = UIColor(0.15f, 0.15f, 0.15f, 1.0f); - palette.closeButtonBorderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.closeGlyphColor = UIColor(0.85f, 0.85f, 0.85f, 1.0f); + palette.stripBackgroundColor = kSurfaceBase; + palette.headerBackgroundColor = kSurfaceRaised; + palette.contentBackgroundColor = kSurfaceBase; + palette.stripBorderColor = kBorderDefault; + palette.headerContentSeparatorColor = kBorderDefault; + palette.focusedBorderColor = kBorderFocused; + palette.tabColor = kSurfaceRaised; + palette.tabHoveredColor = kSurfaceHover; + palette.tabSelectedColor = kSurfaceRaised; + palette.tabBorderColor = kBorderDefault; + palette.tabHoveredBorderColor = kBorderFocused; + palette.tabSelectedBorderColor = kBorderDefault; + palette.textPrimary = kTextPrimary; + palette.textSecondary = kTextSecondary; + palette.textMuted = kTextMuted; + palette.closeButtonColor = kSurfaceRaised; + palette.closeButtonHoveredColor = kSurfaceHover; + palette.closeButtonBorderColor = kBorderDefault; + palette.closeGlyphColor = UIColor(0.86f, 0.86f, 0.86f, 1.0f); palette.reorderPreviewColor = UIColor(0.92f, 0.92f, 0.92f, 0.90f); return palette; } Widgets::UIEditorStatusBarPalette BuildStatusBarPalette() { Widgets::UIEditorStatusBarPalette palette = {}; - palette.surfaceColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.borderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.focusedBorderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.segmentColor = UIColor(0.12f, 0.12f, 0.12f, 1.0f); - palette.segmentHoveredColor = UIColor(0.15f, 0.15f, 0.15f, 1.0f); - palette.segmentActiveColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f); - palette.segmentBorderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.separatorColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.textPrimary = UIColor(0.90f, 0.90f, 0.90f, 1.0f); - palette.textMuted = UIColor(0.64f, 0.64f, 0.64f, 1.0f); - palette.textAccent = UIColor(0.94f, 0.94f, 0.94f, 1.0f); + palette.surfaceColor = kSurfaceBase; + palette.borderColor = kBorderDefault; + palette.focusedBorderColor = kBorderFocused; + palette.segmentColor = kSurfacePanel; + palette.segmentHoveredColor = kSurfaceHover; + palette.segmentActiveColor = kSurfaceActive; + palette.segmentBorderColor = kBorderDefault; + palette.separatorColor = kBorderDefault; + palette.textPrimary = kTextPrimary; + palette.textMuted = kTextMuted; + palette.textAccent = kTextStrong; return palette; } Widgets::UIEditorPanelFramePalette BuildPanelFramePalette() { Widgets::UIEditorPanelFramePalette palette = {}; - palette.surfaceColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.headerColor = UIColor(0.11f, 0.11f, 0.11f, 1.0f); - palette.footerColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.borderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.hoveredBorderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.activeBorderColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f); - palette.focusedBorderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.textPrimary = UIColor(0.92f, 0.92f, 0.92f, 1.0f); - palette.textSecondary = UIColor(0.68f, 0.68f, 0.68f, 1.0f); - palette.textMuted = UIColor(0.58f, 0.58f, 0.58f, 1.0f); - palette.actionButtonColor = UIColor(0.12f, 0.12f, 0.12f, 1.0f); - palette.actionButtonHoveredColor = UIColor(0.15f, 0.15f, 0.15f, 1.0f); - palette.actionButtonSelectedColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f); - palette.actionButtonBorderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.actionGlyphColor = UIColor(0.84f, 0.84f, 0.84f, 1.0f); + palette.surfaceColor = kSurfaceBase; + palette.headerColor = kSurfaceRaised; + palette.footerColor = kSurfaceBase; + palette.borderColor = kBorderDefault; + palette.hoveredBorderColor = kSurfaceActive; + palette.activeBorderColor = kBorderFocused; + palette.focusedBorderColor = kBorderFocused; + palette.textPrimary = kTextPrimary; + palette.textSecondary = kTextSecondary; + palette.textMuted = kTextMuted; + palette.actionButtonColor = kSurfaceRaised; + palette.actionButtonHoveredColor = kSurfaceHover; + palette.actionButtonSelectedColor = kSurfaceActive; + palette.actionButtonBorderColor = kBorderDefault; + palette.actionGlyphColor = UIColor(0.86f, 0.86f, 0.86f, 1.0f); return palette; } @@ -144,39 +160,39 @@ Widgets::UIEditorDockHostPalette BuildDockHostPalette() { Widgets::UIEditorDockHostPalette palette = {}; palette.tabStripPalette = BuildTabStripPalette(); palette.panelFramePalette = BuildPanelFramePalette(); - palette.splitterColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.splitterHoveredColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.splitterActiveColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.placeholderTitleColor = UIColor(0.92f, 0.92f, 0.92f, 1.0f); - palette.placeholderTextColor = UIColor(0.70f, 0.70f, 0.70f, 1.0f); - palette.placeholderMutedColor = UIColor(0.58f, 0.58f, 0.58f, 1.0f); - palette.dropPreviewFillColor = UIColor(0.92f, 0.92f, 0.92f, 0.08f); - palette.dropPreviewBorderColor = UIColor(0.96f, 0.96f, 0.96f, 0.70f); + palette.splitterColor = kBorderSubtle; + palette.splitterHoveredColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); + palette.splitterActiveColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f); + palette.placeholderTitleColor = kTextPrimary; + palette.placeholderTextColor = kTextSecondary; + palette.placeholderMutedColor = kTextMuted; + palette.dropPreviewFillColor = UIColor(0.92f, 0.92f, 0.92f, 0.06f); + palette.dropPreviewBorderColor = UIColor(0.95f, 0.95f, 0.95f, 0.55f); return palette; } Widgets::UIEditorViewportSlotPalette BuildViewportSlotPalette() { Widgets::UIEditorViewportSlotPalette palette = {}; - palette.frameColor = UIColor(0.09f, 0.09f, 0.09f, 1.0f); - palette.topBarColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.surfaceColor = UIColor(0.08f, 0.08f, 0.08f, 1.0f); - palette.surfaceHoverOverlayColor = UIColor(0.18f, 0.18f, 0.18f, 0.12f); - palette.surfaceActiveOverlayColor = UIColor(0.22f, 0.22f, 0.22f, 0.10f); - palette.captureOverlayColor = UIColor(0.92f, 0.92f, 0.92f, 0.06f); - palette.borderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.focusedBorderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.surfaceBorderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.surfaceHoveredBorderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.surfaceActiveBorderColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f); - palette.surfaceCapturedBorderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.toolColor = UIColor(0.12f, 0.12f, 0.12f, 1.0f); - palette.toolHoveredColor = UIColor(0.15f, 0.15f, 0.15f, 1.0f); - palette.toolSelectedColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f); - palette.toolDisabledColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.toolBorderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); - palette.textPrimary = UIColor(0.92f, 0.92f, 0.92f, 1.0f); - palette.textSecondary = UIColor(0.68f, 0.68f, 0.68f, 1.0f); - palette.textMuted = UIColor(0.58f, 0.58f, 0.58f, 1.0f); + palette.frameColor = kSurfaceLower; + palette.topBarColor = kSurfaceBase; + palette.surfaceColor = kSurfaceLower; + palette.surfaceHoverOverlayColor = UIColor(0.18f, 0.18f, 0.18f, 0.08f); + palette.surfaceActiveOverlayColor = UIColor(0.22f, 0.22f, 0.22f, 0.08f); + palette.captureOverlayColor = UIColor(0.92f, 0.92f, 0.92f, 0.05f); + palette.borderColor = kBorderDefault; + palette.focusedBorderColor = kBorderFocused; + palette.surfaceBorderColor = kBorderDefault; + palette.surfaceHoveredBorderColor = kSurfaceActive; + palette.surfaceActiveBorderColor = kBorderFocused; + palette.surfaceCapturedBorderColor = kBorderFocused; + palette.toolColor = kSurfacePanel; + palette.toolHoveredColor = kSurfaceHover; + palette.toolSelectedColor = kSurfaceActive; + palette.toolDisabledColor = kSurfaceLower; + palette.toolBorderColor = kBorderDefault; + palette.textPrimary = kTextPrimary; + palette.textSecondary = kTextSecondary; + palette.textMuted = kTextMuted; palette.imageTint = UIColor(1.0f, 1.0f, 1.0f, 1.0f); palette.statusBarPalette = BuildStatusBarPalette(); return palette; @@ -184,14 +200,14 @@ Widgets::UIEditorViewportSlotPalette BuildViewportSlotPalette() { UIEditorShellComposePalette BuildShellComposePalette() { UIEditorShellComposePalette palette = {}; - palette.surfaceColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.surfaceBorderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); + palette.surfaceColor = kSurfaceBase; + palette.surfaceBorderColor = kBorderDefault; palette.menuBarPalette = BuildMenuBarPalette(); - palette.toolbarPalette.barColor = UIColor(0.10f, 0.10f, 0.10f, 1.0f); - palette.toolbarPalette.groupColor = UIColor(0.11f, 0.11f, 0.11f, 1.0f); - palette.toolbarPalette.groupBorderColor = UIColor(0.14f, 0.14f, 0.14f, 1.0f); - palette.toolbarPalette.buttonColor = UIColor(0.12f, 0.12f, 0.12f, 1.0f); - palette.toolbarPalette.buttonBorderColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f); + palette.toolbarPalette.barColor = kSurfaceBase; + palette.toolbarPalette.groupColor = kSurfaceRaised; + palette.toolbarPalette.groupBorderColor = kBorderDefault; + palette.toolbarPalette.buttonColor = kSurfacePanel; + palette.toolbarPalette.buttonBorderColor = kBorderDefault; palette.toolbarPalette.iconColor = UIColor(0.88f, 0.88f, 0.88f, 1.0f); palette.dockHostPalette = BuildDockHostPalette(); palette.viewportPalette = BuildViewportSlotPalette(); @@ -206,6 +222,31 @@ UIEditorShellInteractionPalette BuildShellInteractionPalette() { return palette; } +Widgets::UIEditorPropertyGridPalette BuildPropertyGridPalette() { + Widgets::UIEditorPropertyGridPalette palette = {}; + palette.surfaceColor = kSurfaceBase; + palette.borderColor = kBorderDefault; + palette.focusedBorderColor = kBorderFocused; + palette.sectionHeaderColor = kSurfaceRaised; + palette.sectionHeaderHoverColor = kSurfaceHover; + palette.fieldHoverColor = kSurfaceHover; + palette.fieldSelectedColor = kSurfaceActive; + palette.fieldSelectedFocusedColor = kSurfaceActiveStrong; + palette.valueBoxColor = kSurfacePanel; + palette.valueBoxHoverColor = kSurfaceHover; + palette.valueBoxEditingColor = kSurfaceActive; + palette.valueBoxReadOnlyColor = kSurfaceBase; + palette.valueBoxBorderColor = kBorderDefault; + palette.valueBoxEditingBorderColor = kBorderFocused; + palette.disclosureColor = kTextMuted; + palette.sectionTextColor = kTextPrimary; + palette.labelTextColor = kTextSecondary; + palette.valueTextColor = kTextPrimary; + palette.readOnlyValueTextColor = UIColor(0.60f, 0.60f, 0.60f, 1.0f); + palette.editTagColor = UIColor(0.80f, 0.80f, 0.80f, 1.0f); + return palette; +} + #define XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Name, Type) \ const Type& ResolveUIEditor##Name() { \ return GetDefaultValue(); \ @@ -246,7 +287,6 @@ XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ViewportSlotMetrics, Widgets::UIEditorViewpo XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ShellComposeMetrics, UIEditorShellComposeMetrics) XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ShellInteractionMetrics, UIEditorShellInteractionMetrics) XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(PropertyGridMetrics, Widgets::UIEditorPropertyGridMetrics) -XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(PropertyGridPalette, Widgets::UIEditorPropertyGridPalette) const Widgets::UIEditorMenuPopupPalette& ResolveUIEditorMenuPopupPalette() { static const Widgets::UIEditorMenuPopupPalette palette = BuildMenuPopupPalette(); @@ -308,6 +348,11 @@ const UIEditorShellInteractionPalette& ResolveUIEditorShellInteractionPalette() return palette; } +const Widgets::UIEditorPropertyGridPalette& ResolveUIEditorPropertyGridPalette() { + static const Widgets::UIEditorPropertyGridPalette palette = BuildPropertyGridPalette(); + return palette; +} + #undef XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS } // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Shell/UIEditorMenuBar.cpp b/new_editor/src/Menu/UIEditorMenuBar.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorMenuBar.cpp rename to new_editor/src/Menu/UIEditorMenuBar.cpp index 52a58dd5..03ee9710 100644 --- a/new_editor/src/Shell/UIEditorMenuBar.cpp +++ b/new_editor/src/Menu/UIEditorMenuBar.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Shell/UIEditorMenuModel.cpp b/new_editor/src/Menu/UIEditorMenuModel.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorMenuModel.cpp rename to new_editor/src/Menu/UIEditorMenuModel.cpp index 73551519..080908b8 100644 --- a/new_editor/src/Shell/UIEditorMenuModel.cpp +++ b/new_editor/src/Menu/UIEditorMenuModel.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Shell/UIEditorMenuPopup.cpp b/new_editor/src/Menu/UIEditorMenuPopup.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorMenuPopup.cpp rename to new_editor/src/Menu/UIEditorMenuPopup.cpp index 6ac9fd91..02fd4fbd 100644 --- a/new_editor/src/Shell/UIEditorMenuPopup.cpp +++ b/new_editor/src/Menu/UIEditorMenuPopup.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Shell/UIEditorMenuSession.cpp b/new_editor/src/Menu/UIEditorMenuSession.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorMenuSession.cpp rename to new_editor/src/Menu/UIEditorMenuSession.cpp index 4adbd3e3..a5da9533 100644 --- a/new_editor/src/Shell/UIEditorMenuSession.cpp +++ b/new_editor/src/Menu/UIEditorMenuSession.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorPanelContentHost.cpp b/new_editor/src/Panels/UIEditorPanelContentHost.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorPanelContentHost.cpp rename to new_editor/src/Panels/UIEditorPanelContentHost.cpp index 5dd0d853..3c9cf2e5 100644 --- a/new_editor/src/Shell/UIEditorPanelContentHost.cpp +++ b/new_editor/src/Panels/UIEditorPanelContentHost.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorPanelFrame.cpp b/new_editor/src/Panels/UIEditorPanelFrame.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorPanelFrame.cpp rename to new_editor/src/Panels/UIEditorPanelFrame.cpp index 0676d2fa..68732130 100644 --- a/new_editor/src/Shell/UIEditorPanelFrame.cpp +++ b/new_editor/src/Panels/UIEditorPanelFrame.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorPanelHostLifecycle.cpp b/new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorPanelHostLifecycle.cpp rename to new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp index c9ace212..6e76bf46 100644 --- a/new_editor/src/Shell/UIEditorPanelHostLifecycle.cpp +++ b/new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorPanelRegistry.cpp b/new_editor/src/Panels/UIEditorPanelRegistry.cpp similarity index 97% rename from new_editor/src/Shell/UIEditorPanelRegistry.cpp rename to new_editor/src/Panels/UIEditorPanelRegistry.cpp index 91ff8b99..c40906bd 100644 --- a/new_editor/src/Shell/UIEditorPanelRegistry.cpp +++ b/new_editor/src/Panels/UIEditorPanelRegistry.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorViewportInputBridge.cpp b/new_editor/src/Viewport/UIEditorViewportInputBridge.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorViewportInputBridge.cpp rename to new_editor/src/Viewport/UIEditorViewportInputBridge.cpp index e1c84c54..ebeccf1a 100644 --- a/new_editor/src/Shell/UIEditorViewportInputBridge.cpp +++ b/new_editor/src/Viewport/UIEditorViewportInputBridge.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Shell/UIEditorViewportShell.cpp b/new_editor/src/Viewport/UIEditorViewportShell.cpp similarity index 98% rename from new_editor/src/Shell/UIEditorViewportShell.cpp rename to new_editor/src/Viewport/UIEditorViewportShell.cpp index 2c7c3b46..dfe1f6ed 100644 --- a/new_editor/src/Shell/UIEditorViewportShell.cpp +++ b/new_editor/src/Viewport/UIEditorViewportShell.cpp @@ -1,4 +1,4 @@ -#include +#include namespace XCEngine::UI::Editor { diff --git a/new_editor/src/Shell/UIEditorViewportSlot.cpp b/new_editor/src/Viewport/UIEditorViewportSlot.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorViewportSlot.cpp rename to new_editor/src/Viewport/UIEditorViewportSlot.cpp index 5f0b9ec9..4295da40 100644 --- a/new_editor/src/Shell/UIEditorViewportSlot.cpp +++ b/new_editor/src/Viewport/UIEditorViewportSlot.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp b/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp index cdf4e6b5..03fc8afc 100644 --- a/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp +++ b/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp @@ -20,35 +20,35 @@ bool AreEqual(float lhs, float rhs) { const UIEditorInspectorFieldStyleTokens& GetUIEditorInspectorFieldStyleTokens() { static const UIEditorInspectorFieldStyleTokens kTokens = [] { UIEditorInspectorFieldStyleTokens tokens = {}; - tokens.rowHoverColor = ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); - tokens.rowActiveColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); - tokens.labelColor = ::XCEngine::UI::UIColor(0.78f, 0.78f, 0.78f, 1.0f); + tokens.rowHoverColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + tokens.rowActiveColor = ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + tokens.labelColor = ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); tokens.valueColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); tokens.readOnlyValueColor = ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); tokens.controlColor = ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); tokens.controlHoverColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); tokens.controlEditingColor = ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); tokens.controlReadOnlyColor = ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); - tokens.controlBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); - tokens.controlFocusedBorderColor = ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); - tokens.prefixColor = ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + tokens.controlBorderColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); + tokens.controlFocusedBorderColor = ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + tokens.prefixColor = ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); tokens.prefixBorderColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); tokens.popupColor = ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); - tokens.popupBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + tokens.popupBorderColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); tokens.popupHeaderColor = ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); - tokens.popupTitleColor = ::XCEngine::UI::UIColor(0.93f, 0.93f, 0.93f, 1.0f); - tokens.popupTextColor = ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); - tokens.popupTextMutedColor = ::XCEngine::UI::UIColor(0.68f, 0.68f, 0.68f, 1.0f); - tokens.previewBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + tokens.popupTitleColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); + tokens.popupTextColor = ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + tokens.popupTextMutedColor = ::XCEngine::UI::UIColor(0.66f, 0.66f, 0.66f, 1.0f); + tokens.previewBorderColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); tokens.previewBaseColor = ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); - tokens.checkerLightColor = ::XCEngine::UI::UIColor(0.26f, 0.26f, 0.26f, 1.0f); - tokens.checkerDarkColor = ::XCEngine::UI::UIColor(0.16f, 0.16f, 0.16f, 1.0f); - tokens.sliderBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + tokens.checkerLightColor = ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + tokens.checkerDarkColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + tokens.sliderBorderColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); tokens.numericBoxColor = ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); - tokens.numericBoxBorderColor = ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + tokens.numericBoxBorderColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); tokens.numericBoxTextColor = ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); tokens.closeButtonHoverColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); - tokens.closeGlyphColor = ::XCEngine::UI::UIColor(0.85f, 0.85f, 0.85f, 1.0f); + tokens.closeGlyphColor = ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); return tokens; }(); return kTokens; diff --git a/new_editor/src/Shell/UIEditorWindowWorkspaceController.cpp b/new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorWindowWorkspaceController.cpp rename to new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp index 28c39d0b..bb7da8c3 100644 --- a/new_editor/src/Shell/UIEditorWindowWorkspaceController.cpp +++ b/new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp @@ -1,6 +1,6 @@ -#include +#include -#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorWindowWorkspaceModel.cpp b/new_editor/src/Workspace/UIEditorWindowWorkspaceModel.cpp similarity index 98% rename from new_editor/src/Shell/UIEditorWindowWorkspaceModel.cpp rename to new_editor/src/Workspace/UIEditorWindowWorkspaceModel.cpp index d0e1e21c..1524769b 100644 --- a/new_editor/src/Shell/UIEditorWindowWorkspaceModel.cpp +++ b/new_editor/src/Workspace/UIEditorWindowWorkspaceModel.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorWorkspaceCompose.cpp b/new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorWorkspaceCompose.cpp rename to new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp index 793cdc30..ddb61e4f 100644 --- a/new_editor/src/Shell/UIEditorWorkspaceCompose.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorWorkspaceController.cpp b/new_editor/src/Workspace/UIEditorWorkspaceController.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorWorkspaceController.cpp rename to new_editor/src/Workspace/UIEditorWorkspaceController.cpp index ea7406e0..cd0cf30e 100644 --- a/new_editor/src/Shell/UIEditorWorkspaceController.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceController.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorWorkspaceInteraction.cpp b/new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp similarity index 98% rename from new_editor/src/Shell/UIEditorWorkspaceInteraction.cpp rename to new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp index 23f0b597..d30d7782 100644 --- a/new_editor/src/Shell/UIEditorWorkspaceInteraction.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Shell/UIEditorWorkspaceLayoutPersistence.cpp b/new_editor/src/Workspace/UIEditorWorkspaceLayoutPersistence.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorWorkspaceLayoutPersistence.cpp rename to new_editor/src/Workspace/UIEditorWorkspaceLayoutPersistence.cpp index 3cafdc78..d488e65f 100644 --- a/new_editor/src/Shell/UIEditorWorkspaceLayoutPersistence.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceLayoutPersistence.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorWorkspaceModel.cpp b/new_editor/src/Workspace/UIEditorWorkspaceModel.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorWorkspaceModel.cpp rename to new_editor/src/Workspace/UIEditorWorkspaceModel.cpp index 97e09229..6b3d9fe8 100644 --- a/new_editor/src/Shell/UIEditorWorkspaceModel.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceModel.cpp @@ -1,6 +1,6 @@ -#include -#include -#include +#include +#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorWorkspaceSession.cpp b/new_editor/src/Workspace/UIEditorWorkspaceSession.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorWorkspaceSession.cpp rename to new_editor/src/Workspace/UIEditorWorkspaceSession.cpp index bba005e3..ffbb41f5 100644 --- a/new_editor/src/Shell/UIEditorWorkspaceSession.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceSession.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorWorkspaceTransfer.cpp b/new_editor/src/Workspace/UIEditorWorkspaceTransfer.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorWorkspaceTransfer.cpp rename to new_editor/src/Workspace/UIEditorWorkspaceTransfer.cpp index 252f9610..d7bc0adc 100644 --- a/new_editor/src/Shell/UIEditorWorkspaceTransfer.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceTransfer.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include