diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 005d1b02..b4857db9 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -34,7 +34,7 @@ set(XCUI_EDITOR_FIELD_SOURCES src/Fields/UIEditorBoolFieldInteraction.cpp src/Fields/UIEditorColorField.cpp src/Fields/UIEditorColorFieldInteraction.cpp - src/Fields/UIEditorColorFieldRendering.cpp + src/Fields/ColorFieldRendering.cpp src/Fields/UIEditorEnumField.cpp src/Fields/UIEditorEnumFieldInteraction.cpp src/Fields/UIEditorFieldStyle.cpp @@ -44,8 +44,11 @@ set(XCUI_EDITOR_FIELD_SOURCES src/Fields/UIEditorObjectFieldInteraction.cpp src/Fields/UIEditorPropertyGrid.cpp src/Fields/UIEditorPropertyGridInteraction.cpp - src/Fields/UIEditorPropertyGridInteractionHelpers.cpp - src/Fields/UIEditorPropertyGridRendering.cpp + src/Fields/PropertyGridInteractionColor.cpp + src/Fields/PropertyGridInteractionEdit.cpp + src/Fields/PropertyGridInteractionHelpers.cpp + src/Fields/PropertyGridInteractionPopup.cpp + src/Fields/PropertyGridRendering.cpp src/Fields/UIEditorTextField.cpp src/Fields/UIEditorTextFieldInteraction.cpp src/Fields/UIEditorVector2Field.cpp @@ -69,11 +72,12 @@ set(XCUI_EDITOR_COLLECTION_SOURCES ) set(XCUI_EDITOR_DOCKING_SOURCES + src/Docking/DockHostMeasure.cpp src/Docking/UIEditorDockHost.cpp - src/Docking/UIEditorDockHostHitTest.cpp - src/Docking/UIEditorDockHostRendering.cpp + src/Docking/DockHostHitTest.cpp + src/Docking/DockHostRendering.cpp src/Docking/UIEditorDockHostInteraction.cpp - src/Docking/UIEditorDockHostInteractionHelpers.cpp + src/Docking/DockHostInteractionHelpers.cpp ) set(XCUI_EDITOR_MENU_SOURCES @@ -94,8 +98,8 @@ set(XCUI_EDITOR_SHELL_SOURCES src/Shell/UIEditorShellAsset.cpp src/Shell/UIEditorShellCompose.cpp src/Shell/UIEditorShellInteraction.cpp - src/Shell/UIEditorShellInteractionRequest.cpp - src/Shell/UIEditorShellInteractionRendering.cpp + src/Shell/ShellInteractionRequest.cpp + src/Shell/ShellInteractionRendering.cpp src/Shell/UIEditorStatusBar.cpp ) @@ -108,14 +112,16 @@ set(XCUI_EDITOR_VIEWPORT_SOURCES set(XCUI_EDITOR_WORKSPACE_SOURCES src/Workspace/UIEditorWorkspaceCompose.cpp src/Workspace/UIEditorWorkspaceController.cpp - src/Workspace/UIEditorWorkspaceControllerDispatch.cpp - src/Workspace/UIEditorWorkspaceControllerLayoutOps.cpp + src/Workspace/WorkspaceControllerDispatch.cpp + src/Workspace/WorkspaceControllerLayoutOps.cpp src/Workspace/UIEditorWorkspaceInteraction.cpp src/Workspace/UIEditorWorkspaceLayoutPersistence.cpp src/Workspace/UIEditorWorkspaceModel.cpp - src/Workspace/UIEditorWorkspaceModelMutation.cpp - src/Workspace/UIEditorWorkspaceModelQueries.cpp - src/Workspace/UIEditorWorkspaceModelValidation.cpp + src/Workspace/WorkspaceModelMutation.cpp + src/Workspace/WorkspaceModelQueries.cpp + src/Workspace/SplitterDragCorrection/Chain.cpp + src/Workspace/SplitterDragCorrection/Correction.cpp + src/Workspace/WorkspaceModelValidation.cpp src/Workspace/UIEditorWorkspaceSession.cpp src/Workspace/UIEditorWorkspaceTransfer.cpp src/Workspace/UIEditorWindowWorkspaceController.cpp @@ -182,11 +188,11 @@ set(XCUI_EDITOR_HOST_RENDERING_SOURCES 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/SwapChainPresenter/Presenter.cpp + app/Rendering/D3D12/SwapChainPresenter/BackBuffers.cpp + app/Rendering/D3D12/SwapChainPresenter/Lifecycle.cpp + app/Rendering/D3D12/SwapChainPresenter/Presentation.cpp + app/Rendering/D3D12/SwapChainPresenter/Resize.cpp app/Rendering/D3D12/D3D12WindowRenderLoop.cpp app/Rendering/Native/NativeRenderer.cpp app/Rendering/Native/NativeRendererCapture.cpp @@ -261,10 +267,12 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Features/Console/ConsolePanel.cpp app/Features/Hierarchy/HierarchyModel.cpp app/Features/Hierarchy/HierarchyPanel.cpp + app/Features/Hierarchy/HierarchyPanelCommands.cpp app/Features/Hierarchy/HierarchyPanelInteraction.cpp app/Features/Hierarchy/HierarchyPanelRendering.cpp app/Features/Inspector/InspectorPanel.cpp app/Features/Project/ProjectPanel.cpp + app/Features/Project/ProjectPanelCommands.cpp app/Features/Project/ProjectPanelInteraction.cpp app/Features/Project/ProjectPanelLayout.cpp app/Features/Project/ProjectPanelRendering.cpp @@ -278,10 +286,10 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Rendering/Viewport/ViewportHostService.cpp app/Rendering/Viewport/ViewportHostServiceFrame.cpp app/Rendering/Viewport/ViewportHostServiceLifecycle.cpp - app/Rendering/Viewport/ViewportRenderTargetManager.cpp - app/Rendering/Viewport/ViewportRenderTargetManagerCleanup.cpp - app/Rendering/Viewport/ViewportRenderTargetManagerResources.cpp - app/Rendering/Viewport/ViewportRenderTargetManagerSurfaces.cpp + app/Rendering/Viewport/RenderTargetManager/Manager.cpp + app/Rendering/Viewport/RenderTargetManager/Cleanup.cpp + app/Rendering/Viewport/RenderTargetManager/Resources.cpp + app/Rendering/Viewport/RenderTargetManager/Surfaces.cpp app/Rendering/Viewport/ViewportRenderTargets.cpp ) @@ -300,14 +308,14 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Platform/Win32/EditorWindowMetrics.cpp app/Platform/Win32/EditorWindowInput.cpp app/Platform/Win32/EditorWindowResizeLifecycle.cpp - app/Platform/Win32/EditorWindowManagerCreation.cpp - app/Platform/Win32/EditorWindowManagerLifecycle.cpp - app/Platform/Win32/EditorWindowManagerLookup.cpp - app/Platform/Win32/EditorWindowManagerWindowSet.cpp - app/Platform/Win32/EditorWindowManagerWindowSynchronization.cpp - app/Platform/Win32/EditorWindowManagerTabDrag.cpp - app/Platform/Win32/EditorWindowManagerDetach.cpp - app/Platform/Win32/EditorWindowManagerCrossWindowDrop.cpp + app/Platform/Win32/WindowManager/Creation.cpp + app/Platform/Win32/WindowManager/Lifecycle.cpp + app/Platform/Win32/WindowManager/Lookup.cpp + app/Platform/Win32/WindowManager/WindowSet.cpp + app/Platform/Win32/WindowManager/WindowSync.cpp + app/Platform/Win32/WindowManager/TabDrag.cpp + app/Platform/Win32/WindowManager/Detach.cpp + app/Platform/Win32/WindowManager/CrossWindowDrop.cpp ) add_executable(XCUIEditorApp WIN32 diff --git a/new_editor/app/Composition/EditorEditCommandRoute.h b/new_editor/app/Composition/EditorEditCommandRoute.h new file mode 100644 index 00000000..6c0741b2 --- /dev/null +++ b/new_editor/app/Composition/EditorEditCommandRoute.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +class EditorEditCommandRoute { +public: + virtual ~EditorEditCommandRoute() = default; + + virtual UIEditorHostCommandEvaluationResult EvaluateEditCommand( + std::string_view commandId) const = 0; + + virtual UIEditorHostCommandDispatchResult DispatchEditCommand( + std::string_view commandId) = 0; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Composition/EditorHostCommandBridge.cpp b/new_editor/app/Composition/EditorHostCommandBridge.cpp index 5d334130..7a83e900 100644 --- a/new_editor/app/Composition/EditorHostCommandBridge.cpp +++ b/new_editor/app/Composition/EditorHostCommandBridge.cpp @@ -1,6 +1,7 @@ #include "EditorHostCommandBridge.h" #include +#include namespace XCEngine::UI::Editor::App { @@ -8,44 +9,32 @@ void EditorHostCommandBridge::BindSession(EditorSession& session) { m_session = &session; } +void EditorHostCommandBridge::BindEditCommandRoutes( + EditorEditCommandRoute* hierarchyRoute, + EditorEditCommandRoute* projectRoute) { + m_hierarchyRoute = hierarchyRoute; + m_projectRoute = projectRoute; +} + void EditorHostCommandBridge::SetExitRequestHandler(std::function handler) { m_requestExit = std::move(handler); } UIEditorHostCommandEvaluationResult EditorHostCommandBridge::EvaluateHostCommand( std::string_view commandId) const { - UIEditorHostCommandEvaluationResult result = {}; - if (commandId == "file.exit") { - result.executable = true; - result.message = "Exit command is ready."; - return result; + return BuildExecutableResult("Exit editor."); } if (commandId == "help.about") { - result.executable = true; - result.message = "About placeholder is ready."; - return result; + return BuildDisabledResult("About dialog is unavailable in the current shell."); } if (commandId.rfind("edit.", 0u) == 0u) { return EvaluateEditCommand(commandId); } - if (commandId.rfind("file.", 0u) == 0u) { - return BuildDisabledResult("Document command bridge is not attached yet."); - } - if (commandId.rfind("assets.", 0u) == 0u) { - return BuildDisabledResult("Asset pipeline bridge is not attached yet."); - } - if (commandId.rfind("run.", 0u) == 0u) { - return BuildDisabledResult("Runtime bridge is not attached yet."); - } - if (commandId.rfind("scripts.", 0u) == 0u) { - return BuildDisabledResult("Script pipeline bridge is not attached yet."); - } - - return BuildDisabledResult("Host command is not attached yet."); + return EvaluateUnsupportedHostCommand(commandId); } UIEditorHostCommandDispatchResult EditorHostCommandBridge::DispatchHostCommand( @@ -62,8 +51,7 @@ UIEditorHostCommandDispatchResult EditorHostCommandBridge::DispatchHostCommand( } if (commandId == "help.about") { - result.commandExecuted = true; - result.message = "About dialog will be wired after modal layer lands."; + result.message = EvaluateHostCommand(commandId).message; return result; } @@ -83,34 +71,76 @@ UIEditorHostCommandEvaluationResult EditorHostCommandBridge::BuildDisabledResult return result; } +UIEditorHostCommandEvaluationResult EditorHostCommandBridge::BuildExecutableResult( + std::string_view message) const { + UIEditorHostCommandEvaluationResult result = {}; + result.executable = true; + result.message = std::string(message); + return result; +} + +UIEditorHostCommandEvaluationResult EditorHostCommandBridge::EvaluateFileCommand( + std::string_view commandId) const { + if (commandId == "file.exit") { + return BuildExecutableResult("Exit editor."); + } + + return BuildDisabledResult( + "Only file.exit has a bound host owner in the current shell."); +} + +UIEditorHostCommandEvaluationResult EditorHostCommandBridge::EvaluateAssetCommand( + std::string_view commandId) const { + (void)commandId; + return BuildDisabledResult( + "Asset commands have no bound Project/AssetDatabase owner in the current shell."); +} + +UIEditorHostCommandEvaluationResult EditorHostCommandBridge::EvaluateRunCommand( + std::string_view commandId) const { + (void)commandId; + return BuildDisabledResult( + "Run commands have no bound play-mode owner in the current shell."); +} + +UIEditorHostCommandEvaluationResult EditorHostCommandBridge::EvaluateScriptCommand( + std::string_view commandId) const { + (void)commandId; + return BuildDisabledResult( + "Script commands have no bound script-pipeline owner in the current shell."); +} + UIEditorHostCommandEvaluationResult EditorHostCommandBridge::EvaluateEditCommand( std::string_view commandId) const { if (m_session == nullptr) { - return BuildDisabledResult("Editor session is not attached yet."); + return BuildDisabledResult("Editor session is unavailable."); } - switch (m_session->activeRoute) { - case EditorActionRoute::Hierarchy: - if (SupportsHierarchyEditCommands(commandId)) { - return UIEditorHostCommandEvaluationResult{ - true, - "Hierarchy edit route placeholder is active." - }; - } - return BuildDisabledResult("Current hierarchy route does not expose this command yet."); - case EditorActionRoute::Project: - if (SupportsProjectEditCommands(commandId)) { - return UIEditorHostCommandEvaluationResult{ - true, - "Project edit route placeholder is active." - }; - } - return BuildDisabledResult("Current project route does not expose this command yet."); - case EditorActionRoute::None: + if (m_session->activeRoute == EditorActionRoute::None) { return BuildDisabledResult("No active edit route."); - default: - return BuildDisabledResult("Current panel does not expose edit commands yet."); } + + EditorEditCommandRoute* route = ResolveEditCommandRoute(m_session->activeRoute); + if (route == nullptr) { + switch (m_session->activeRoute) { + case EditorActionRoute::Hierarchy: + return BuildDisabledResult("Hierarchy command route is unavailable in the current window."); + case EditorActionRoute::Project: + return BuildDisabledResult("Project command route is unavailable in the current window."); + case EditorActionRoute::Inspector: + return BuildDisabledResult("Inspector does not expose edit commands yet."); + case EditorActionRoute::Console: + return BuildDisabledResult("Console does not expose edit commands yet."); + case EditorActionRoute::Scene: + case EditorActionRoute::Game: + return BuildDisabledResult("Viewport panels do not expose edit commands yet."); + case EditorActionRoute::None: + default: + return BuildDisabledResult("Current panel does not expose edit commands."); + } + } + + return route->EvaluateEditCommand(commandId); } UIEditorHostCommandDispatchResult EditorHostCommandBridge::DispatchEditCommand( @@ -122,35 +152,47 @@ UIEditorHostCommandDispatchResult EditorHostCommandBridge::DispatchEditCommand( return result; } - result.commandExecuted = true; - switch (m_session != nullptr ? m_session->activeRoute : EditorActionRoute::None) { - case EditorActionRoute::Hierarchy: - result.message = "Hierarchy edit command route reached."; - break; - case EditorActionRoute::Project: - result.message = "Project edit command route reached."; - break; - default: - result.message = "Edit command route reached."; - break; + EditorEditCommandRoute* route = + ResolveEditCommandRoute(m_session != nullptr ? m_session->activeRoute : EditorActionRoute::None); + if (route == nullptr) { + result.message = "Edit command route is unavailable."; + return result; } - return result; + + return route->DispatchEditCommand(commandId); } -bool EditorHostCommandBridge::SupportsHierarchyEditCommands( +UIEditorHostCommandEvaluationResult EditorHostCommandBridge::EvaluateUnsupportedHostCommand( std::string_view commandId) const { - return commandId == "edit.cut" || - commandId == "edit.copy" || - commandId == "edit.paste" || - commandId == "edit.duplicate" || - commandId == "edit.delete" || - commandId == "edit.rename"; + if (commandId.rfind("file.", 0u) == 0u) { + return EvaluateFileCommand(commandId); + } + + if (commandId.rfind("assets.", 0u) == 0u) { + return EvaluateAssetCommand(commandId); + } + + if (commandId.rfind("run.", 0u) == 0u) { + return EvaluateRunCommand(commandId); + } + + if (commandId.rfind("scripts.", 0u) == 0u) { + return EvaluateScriptCommand(commandId); + } + + return BuildDisabledResult("Host command has no owner in the current shell."); } -bool EditorHostCommandBridge::SupportsProjectEditCommands( - std::string_view commandId) const { - return commandId == "edit.delete" || - commandId == "edit.rename"; +EditorEditCommandRoute* EditorHostCommandBridge::ResolveEditCommandRoute( + EditorActionRoute route) const { + switch (route) { + case EditorActionRoute::Hierarchy: + return m_hierarchyRoute; + case EditorActionRoute::Project: + return m_projectRoute; + default: + return nullptr; + } } } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Composition/EditorHostCommandBridge.h b/new_editor/app/Composition/EditorHostCommandBridge.h index 8c8a6180..1ea7c3f9 100644 --- a/new_editor/app/Composition/EditorHostCommandBridge.h +++ b/new_editor/app/Composition/EditorHostCommandBridge.h @@ -1,5 +1,6 @@ #pragma once +#include "Composition/EditorEditCommandRoute.h" #include "State/EditorSession.h" #include @@ -12,6 +13,9 @@ namespace XCEngine::UI::Editor::App { class EditorHostCommandBridge : public UIEditorHostCommandHandler { public: void BindSession(EditorSession& session); + void BindEditCommandRoutes( + EditorEditCommandRoute* hierarchyRoute, + EditorEditCommandRoute* projectRoute); void SetExitRequestHandler(std::function handler); UIEditorHostCommandEvaluationResult EvaluateHostCommand( @@ -22,14 +26,27 @@ public: private: UIEditorHostCommandEvaluationResult BuildDisabledResult( std::string_view message) const; + UIEditorHostCommandEvaluationResult BuildExecutableResult( + std::string_view message) const; + UIEditorHostCommandEvaluationResult EvaluateFileCommand( + std::string_view commandId) const; + UIEditorHostCommandEvaluationResult EvaluateAssetCommand( + std::string_view commandId) const; + UIEditorHostCommandEvaluationResult EvaluateRunCommand( + std::string_view commandId) const; + UIEditorHostCommandEvaluationResult EvaluateScriptCommand( + std::string_view commandId) const; UIEditorHostCommandEvaluationResult EvaluateEditCommand( std::string_view commandId) const; UIEditorHostCommandDispatchResult DispatchEditCommand( std::string_view commandId); - bool SupportsHierarchyEditCommands(std::string_view commandId) const; - bool SupportsProjectEditCommands(std::string_view commandId) const; + UIEditorHostCommandEvaluationResult EvaluateUnsupportedHostCommand( + std::string_view commandId) const; + EditorEditCommandRoute* ResolveEditCommandRoute(EditorActionRoute route) const; EditorSession* m_session = nullptr; + EditorEditCommandRoute* m_hierarchyRoute = nullptr; + EditorEditCommandRoute* m_projectRoute = nullptr; std::function m_requestExit = {}; }; diff --git a/new_editor/app/Composition/EditorShellRuntime.cpp b/new_editor/app/Composition/EditorShellRuntime.cpp index bc4a4fc9..7dc6170d 100644 --- a/new_editor/app/Composition/EditorShellRuntime.cpp +++ b/new_editor/app/Composition/EditorShellRuntime.cpp @@ -28,6 +28,7 @@ void EditorShellRuntime::SetViewportSurfacePresentationEnabled(bool enabled) { void EditorShellRuntime::Shutdown() { m_shellFrame = {}; m_shellInteractionState = {}; + m_splitterDragCorrectionState = {}; m_traceEntries.clear(); m_viewportHostService.Shutdown(); m_builtInIcons.Shutdown(); @@ -36,6 +37,7 @@ void EditorShellRuntime::Shutdown() { void EditorShellRuntime::ResetInteractionState() { m_shellFrame = {}; m_shellInteractionState = {}; + m_splitterDragCorrectionState = {}; m_traceEntries.clear(); m_hierarchyPanel.ResetInteractionState(); m_projectPanel.ResetInteractionState(); diff --git a/new_editor/app/Composition/EditorShellRuntime.h b/new_editor/app/Composition/EditorShellRuntime.h index 766f1fb5..5d3140cb 100644 --- a/new_editor/app/Composition/EditorShellRuntime.h +++ b/new_editor/app/Composition/EditorShellRuntime.h @@ -13,6 +13,8 @@ #include #include +#include +#include #include #include @@ -75,6 +77,7 @@ private: UIEditorShellInteractionState m_shellInteractionState = {}; UIEditorShellInteractionFrame m_shellFrame = {}; std::vector m_traceEntries = {}; + UIEditorWorkspaceSplitterDragCorrectionState m_splitterDragCorrectionState = {}; }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Composition/EditorShellRuntimeRendering.cpp b/new_editor/app/Composition/EditorShellRuntimeRendering.cpp index 15e01402..db1ffdfa 100644 --- a/new_editor/app/Composition/EditorShellRuntimeRendering.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeRendering.cpp @@ -6,7 +6,6 @@ namespace XCEngine::UI::Editor::App::RuntimeSupport { -using ::XCEngine::UI::UIColor; using ::XCEngine::UI::UIDrawList; UIEditorShellComposeModel BuildShellComposeModelFromFrame( @@ -48,49 +47,6 @@ void AppendShellPopups( } } -UIEditorShellComposeFrame BuildShellComposeFrameWithoutDockPreview( - const UIEditorShellComposeFrame& frame) { - UIEditorShellComposeFrame copy = frame; - copy.workspaceFrame.dockHostLayout.dropPreview = {}; - return copy; -} - -UIRect ResolveDockPreviewOverlayRect(const UIEditorShellComposeFrame& frame) { - const auto& dropPreview = frame.workspaceFrame.dockHostLayout.dropPreview; - UIRect previewRect = dropPreview.previewRect; - if (dropPreview.placement != UIEditorWorkspaceDockPlacement::Center) { - return previewRect; - } - - for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : - frame.workspaceFrame.dockHostLayout.tabStacks) { - if (tabStack.nodeId == dropPreview.targetNodeId) { - return tabStack.bounds; - } - } - - return previewRect; -} - -void AppendDockPreviewOverlay( - UIDrawList& drawList, - const UIEditorShellComposeFrame& frame) { - const auto& dropPreview = frame.workspaceFrame.dockHostLayout.dropPreview; - if (!dropPreview.visible) { - return; - } - - const UIRect previewRect = ResolveDockPreviewOverlayRect(frame); - if (previewRect.width <= 0.0f || previewRect.height <= 0.0f) { - return; - } - - static const UIColor kDropPreviewFillColor(0.56f, 0.56f, 0.56f, 0.06f); - static const UIColor kDropPreviewBorderColor(0.68f, 0.68f, 0.68f, 0.26f); - drawList.AddFilledRect(previewRect, kDropPreviewFillColor); - drawList.AddRectOutline(previewRect, kDropPreviewBorderColor, 1.0f); -} - } // namespace XCEngine::UI::Editor::App::RuntimeSupport namespace XCEngine::UI::Editor::App { @@ -108,11 +64,9 @@ void EditorShellRuntime::Append(UIDrawList& drawList) const { const auto& palette = ResolveUIEditorShellInteractionPalette(); const UIEditorShellComposeModel shellComposeModel = BuildShellComposeModelFromFrame(m_shellFrame); - const UIEditorShellComposeFrame shellComposeFrame = - BuildShellComposeFrameWithoutDockPreview(m_shellFrame.shellFrame); AppendUIEditorShellCompose( drawList, - shellComposeFrame, + m_shellFrame.shellFrame, shellComposeModel, m_shellInteractionState.composeState, palette.shellPalette, @@ -121,7 +75,11 @@ void EditorShellRuntime::Append(UIDrawList& drawList) const { m_hierarchyPanel.Append(drawList); m_inspectorPanel.Append(drawList); m_projectPanel.Append(drawList); - AppendDockPreviewOverlay(drawList, m_shellFrame.shellFrame); + AppendUIEditorShellComposeOverlay( + drawList, + m_shellFrame.shellFrame, + palette.shellPalette, + metrics.shellMetrics); AppendShellPopups(drawList, m_shellFrame, palette, metrics); } diff --git a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp index 73baeb61..c771653c 100644 --- a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp @@ -114,6 +114,12 @@ void EditorShellRuntime::Update( detachedTitleBarTabHeight; } + const UIEditorWorkspaceLayoutSnapshot preUpdateSnapshot = + workspaceController.CaptureLayoutSnapshot(); + const Widgets::UIEditorDockHostLayout preUpdateDockLayout = + m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout; + + context.BindEditCommandRoutes(&m_hierarchyPanel, &m_projectPanel); context.SyncSessionFromWorkspace(workspaceController); UIEditorShellInteractionDefinition definition = context.BuildShellDefinition(workspaceController, captureText, shellVariant); @@ -131,6 +137,27 @@ void EditorShellRuntime::Update( shellEvents, context.GetShellServices(), metrics); + + if (TryApplyUIEditorWorkspaceSplitterDragCorrection( + m_splitterDragCorrectionState, + m_shellInteractionState.workspaceInteractionState.dockHostInteractionState, + preUpdateSnapshot, + preUpdateDockLayout, + workspaceController, + metrics.shellMetrics.dockHostMetrics)) { + context.SyncSessionFromWorkspace(workspaceController); + definition = + context.BuildShellDefinition(workspaceController, captureText, shellVariant); + m_shellFrame = UpdateUIEditorShellInteraction( + m_shellInteractionState, + workspaceController, + bounds, + definition, + {}, + context.GetShellServices(), + metrics); + } + const bool shellOwnsHostedContentPointerStream = ShouldHostedContentYieldPointerStream(m_shellFrame, HasShellInteractiveCapture()); const std::vector<::XCEngine::UI::UIInputEvent> hostedContentEvents = diff --git a/new_editor/app/Composition/WorkspaceEventSync.cpp b/new_editor/app/Composition/WorkspaceEventSync.cpp index e58df0bb..02e47125 100644 --- a/new_editor/app/Composition/WorkspaceEventSync.cpp +++ b/new_editor/app/Composition/WorkspaceEventSync.cpp @@ -27,6 +27,9 @@ std::string DescribeProjectPanelEvent(const ProjectPanel::Event& event) { case ProjectPanel::EventKind::AssetOpened: stream << "AssetOpened"; break; + case ProjectPanel::EventKind::RenameRequested: + stream << "RenameRequested"; + break; case ProjectPanel::EventKind::ContextMenuRequested: stream << "ContextMenuRequested"; break; @@ -144,6 +147,7 @@ void ApplyProjectSelection( } return; case ProjectPanel::EventKind::AssetOpened: + case ProjectPanel::EventKind::RenameRequested: case ProjectPanel::EventKind::ContextMenuRequested: case ProjectPanel::EventKind::None: default: diff --git a/new_editor/app/Features/Hierarchy/HierarchyModel.cpp b/new_editor/app/Features/Hierarchy/HierarchyModel.cpp index addf145a..3357c5a3 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyModel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyModel.cpp @@ -79,6 +79,46 @@ bool ExtractNodeRecursive( return false; } +HierarchyNode CloneNodeRecursive( + const HierarchyNode& source, + const std::function& allocateNodeId) { + HierarchyNode duplicate = {}; + duplicate.nodeId = allocateNodeId(); + duplicate.label = source.label; + duplicate.children.reserve(source.children.size()); + for (const HierarchyNode& child : source.children) { + duplicate.children.push_back(CloneNodeRecursive(child, allocateNodeId)); + } + return duplicate; +} + +bool DuplicateNodeRecursive( + std::vector& nodes, + std::string_view nodeId, + const std::function& allocateNodeId, + std::string& duplicatedNodeId) { + for (std::size_t index = 0u; index < nodes.size(); ++index) { + if (nodes[index].nodeId == nodeId) { + HierarchyNode duplicate = CloneNodeRecursive(nodes[index], allocateNodeId); + duplicatedNodeId = duplicate.nodeId; + nodes.insert( + nodes.begin() + static_cast(index + 1u), + std::move(duplicate)); + return true; + } + + if (DuplicateNodeRecursive( + nodes[index].children, + nodeId, + allocateNodeId, + duplicatedNodeId)) { + return true; + } + } + + return false; +} + void BuildTreeItemsRecursive( const std::vector& nodes, std::uint32_t depth, @@ -178,6 +218,22 @@ std::string HierarchyModel::CreateChild( return parent->children.back().nodeId; } +std::string HierarchyModel::DuplicateNode(std::string_view nodeId) { + if (nodeId.empty() || !ContainsNode(nodeId)) { + return {}; + } + + std::string duplicatedNodeId = {}; + const auto allocateNodeId = [this]() { + return AllocateNodeId(); + }; + if (!DuplicateNodeRecursive(m_roots, nodeId, allocateNodeId, duplicatedNodeId)) { + return {}; + } + + return duplicatedNodeId; +} + bool HierarchyModel::DeleteNode(std::string_view nodeId) { if (nodeId.empty()) { return false; diff --git a/new_editor/app/Features/Hierarchy/HierarchyModel.h b/new_editor/app/Features/Hierarchy/HierarchyModel.h index 06ba8318..daf871df 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyModel.h +++ b/new_editor/app/Features/Hierarchy/HierarchyModel.h @@ -29,6 +29,7 @@ public: std::optional GetParentId(std::string_view nodeId) const; bool RenameNode(std::string_view nodeId, std::string label); std::string CreateChild(std::string_view parentId, std::string_view label); + std::string DuplicateNode(std::string_view nodeId); bool DeleteNode(std::string_view nodeId); bool CanReparent(std::string_view sourceNodeId, std::string_view targetParentId) const; diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp index 43805387..a2a5ddc9 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp @@ -55,23 +55,27 @@ void HierarchyPanel::RebuildItems() { if (!m_treeItems.empty()) { m_selection.SetSelection(m_treeItems.front().itemId); + return; } + + m_selection.ClearSelection(); +} + +const HierarchyNode* HierarchyPanel::GetSelectedNode() const { + if (!m_selection.HasSelection()) { + return nullptr; + } + + return m_model.FindNode(m_selection.GetSelectedId()); } void HierarchyPanel::EmitSelectionEvent() { - if (!m_selection.HasSelection()) { - return; - } - - const HierarchyNode* node = m_model.FindNode(m_selection.GetSelectedId()); - if (node == nullptr) { - return; - } - Event event = {}; event.kind = EventKind::SelectionChanged; - event.itemId = node->nodeId; - event.label = node->label; + if (const HierarchyNode* node = GetSelectedNode(); node != nullptr) { + event.itemId = node->nodeId; + event.label = node->label; + } m_frameEvents.push_back(std::move(event)); } @@ -89,6 +93,16 @@ void HierarchyPanel::EmitReparentEvent( m_frameEvents.push_back(std::move(event)); } +void HierarchyPanel::EmitRenameRequestedEvent(std::string_view itemId) { + Event event = {}; + event.kind = EventKind::RenameRequested; + event.itemId = std::string(itemId); + if (const HierarchyNode* node = m_model.FindNode(itemId); node != nullptr) { + event.label = node->label; + } + m_frameEvents.push_back(std::move(event)); +} + bool HierarchyPanel::WantsHostPointerCapture() const { return m_dragState.requestPointerCapture; } diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.h b/new_editor/app/Features/Hierarchy/HierarchyPanel.h index b1ecfd79..d12ea18c 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.h +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.h @@ -1,5 +1,6 @@ #pragma once +#include "Composition/EditorEditCommandRoute.h" #include "HierarchyModel.h" #include @@ -17,7 +18,7 @@ namespace XCEngine::UI::Editor::App { class BuiltInIcons; -class HierarchyPanel { +class HierarchyPanel final : public EditorEditCommandRoute { public: enum class EventKind : std::uint8_t { None = 0, @@ -47,6 +48,10 @@ public: bool WantsHostPointerRelease() const; bool HasActivePointerCapture() const; const std::vector& GetFrameEvents() const; + UIEditorHostCommandEvaluationResult EvaluateEditCommand( + std::string_view commandId) const override; + UIEditorHostCommandDispatchResult DispatchEditCommand( + std::string_view commandId) override; private: struct DragState { @@ -71,11 +76,13 @@ private: const ::XCEngine::UI::UIRect& bounds, bool allowInteraction, bool panelActive); + const HierarchyNode* GetSelectedNode() const; void EmitSelectionEvent(); void EmitReparentEvent( EventKind kind, std::string itemId, std::string targetItemId); + void EmitRenameRequestedEvent(std::string_view itemId); std::vector<::XCEngine::UI::UIInputEvent> BuildInteractionInputEvents( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const ::XCEngine::UI::UIRect& bounds, diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelCommands.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanelCommands.cpp new file mode 100644 index 00000000..6d649ae8 --- /dev/null +++ b/new_editor/app/Features/Hierarchy/HierarchyPanelCommands.cpp @@ -0,0 +1,120 @@ +#include "HierarchyPanelSupport.h" + +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +UIEditorHostCommandEvaluationResult BuildEvaluationResult( + bool executable, + std::string message) { + UIEditorHostCommandEvaluationResult result = {}; + result.executable = executable; + result.message = std::move(message); + return result; +} + +UIEditorHostCommandDispatchResult BuildDispatchResult( + bool commandExecuted, + std::string message) { + UIEditorHostCommandDispatchResult result = {}; + result.commandExecuted = commandExecuted; + result.message = std::move(message); + return result; +} + +} // namespace + +UIEditorHostCommandEvaluationResult HierarchyPanel::EvaluateEditCommand( + std::string_view commandId) const { + const HierarchyNode* selectedNode = GetSelectedNode(); + if (selectedNode == nullptr) { + return BuildEvaluationResult(false, "Select a hierarchy object first."); + } + + if (commandId == "edit.rename") { + return BuildEvaluationResult( + true, + "Rename hierarchy object '" + selectedNode->label + "'."); + } + + if (commandId == "edit.delete") { + return BuildEvaluationResult( + true, + "Delete hierarchy object '" + selectedNode->label + "'."); + } + + if (commandId == "edit.duplicate") { + return BuildEvaluationResult( + true, + "Duplicate hierarchy object '" + selectedNode->label + "'."); + } + + if (commandId == "edit.cut" || + commandId == "edit.copy" || + commandId == "edit.paste") { + return BuildEvaluationResult( + false, + "Hierarchy clipboard has no bound transfer owner in the current shell."); + } + + return BuildEvaluationResult(false, "Hierarchy does not expose this edit command."); +} + +UIEditorHostCommandDispatchResult HierarchyPanel::DispatchEditCommand( + std::string_view commandId) { + const UIEditorHostCommandEvaluationResult evaluation = EvaluateEditCommand(commandId); + if (!evaluation.executable) { + return BuildDispatchResult(false, evaluation.message); + } + + const HierarchyNode* selectedNode = GetSelectedNode(); + if (selectedNode == nullptr) { + return BuildDispatchResult(false, "Select a hierarchy object first."); + } + + const std::string selectedNodeId = selectedNode->nodeId; + const std::string selectedNodeLabel = selectedNode->label; + + if (commandId == "edit.rename") { + EmitRenameRequestedEvent(selectedNodeId); + return BuildDispatchResult( + true, + "Hierarchy rename requested for '" + selectedNodeLabel + "'."); + } + + if (commandId == "edit.delete") { + if (!m_model.DeleteNode(selectedNodeId)) { + return BuildDispatchResult(false, "Failed to delete the selected hierarchy object."); + } + + RebuildItems(); + EmitSelectionEvent(); + return BuildDispatchResult( + true, + "Deleted hierarchy object '" + selectedNodeLabel + "'."); + } + + if (commandId == "edit.duplicate") { + const std::string duplicatedNodeId = m_model.DuplicateNode(selectedNodeId); + if (duplicatedNodeId.empty()) { + return BuildDispatchResult(false, "Failed to duplicate the selected hierarchy object."); + } + + RebuildItems(); + m_selection.SetSelection(duplicatedNodeId); + EmitSelectionEvent(); + + const HierarchyNode* duplicatedNode = m_model.FindNode(duplicatedNodeId); + const std::string duplicatedLabel = + duplicatedNode != nullptr ? duplicatedNode->label : selectedNodeLabel; + return BuildDispatchResult( + true, + "Duplicated hierarchy object '" + duplicatedLabel + "'."); + } + + return BuildDispatchResult(false, "Hierarchy does not expose this edit command."); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectPanel.cpp b/new_editor/app/Features/Project/ProjectPanel.cpp index 26dfcac8..da88f9f6 100644 --- a/new_editor/app/Features/Project/ProjectPanel.cpp +++ b/new_editor/app/Features/Project/ProjectPanel.cpp @@ -65,6 +65,37 @@ const ProjectPanel::AssetEntry* ProjectPanel::FindAssetEntry(std::string_view it return m_browserModel.FindAssetEntry(itemId); } +std::optional ProjectPanel::ResolveEditCommandTarget() const { + if (m_assetSelection.HasSelection()) { + const AssetEntry* asset = FindAssetEntry(m_assetSelection.GetSelectedId()); + if (asset != nullptr) { + EditCommandTarget target = {}; + target.itemId = asset->itemId; + target.absolutePath = asset->absolutePath; + target.displayName = asset->displayName; + target.directory = asset->directory; + return target; + } + } + + if (!m_folderSelection.HasSelection()) { + return std::nullopt; + } + + const FolderEntry* folder = FindFolderEntry(m_folderSelection.GetSelectedId()); + if (folder == nullptr) { + return std::nullopt; + } + + EditCommandTarget target = {}; + target.itemId = folder->itemId; + target.absolutePath = folder->absolutePath; + target.displayName = folder->label; + target.directory = true; + target.assetsRoot = folder->absolutePath == m_browserModel.GetAssetsRootPath(); + return target; +} + const UIEditorPanelContentHostPanelState* ProjectPanel::FindMountedProjectPanel( const UIEditorPanelContentHostFrame& contentHostFrame) const { for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) { diff --git a/new_editor/app/Features/Project/ProjectPanel.h b/new_editor/app/Features/Project/ProjectPanel.h index 0e172b94..bf8c3f24 100644 --- a/new_editor/app/Features/Project/ProjectPanel.h +++ b/new_editor/app/Features/Project/ProjectPanel.h @@ -1,5 +1,6 @@ #pragma once +#include "Composition/EditorEditCommandRoute.h" #include "ProjectBrowserModel.h" #include @@ -12,6 +13,7 @@ #include #include +#include #include #include #include @@ -20,7 +22,7 @@ namespace XCEngine::UI::Editor::App { class BuiltInIcons; -class ProjectPanel { +class ProjectPanel final : public EditorEditCommandRoute { public: enum class CursorKind : std::uint8_t { Arrow = 0, @@ -33,6 +35,7 @@ public: AssetSelectionCleared, FolderNavigated, AssetOpened, + RenameRequested, ContextMenuRequested }; @@ -71,11 +74,22 @@ public: bool WantsHostPointerRelease() const; bool HasActivePointerCapture() const; const std::vector& GetFrameEvents() const; + UIEditorHostCommandEvaluationResult EvaluateEditCommand( + std::string_view commandId) const override; + UIEditorHostCommandDispatchResult DispatchEditCommand( + std::string_view commandId) override; private: using BrowserModel = ::XCEngine::UI::Editor::App::ProjectBrowserModel; using FolderEntry = BrowserModel::FolderEntry; using AssetEntry = BrowserModel::AssetEntry; + struct EditCommandTarget { + std::string itemId = {}; + std::filesystem::path absolutePath = {}; + std::string displayName = {}; + bool directory = false; + bool assetsRoot = false; + }; struct BreadcrumbItemLayout { std::string label = {}; @@ -108,6 +122,7 @@ private: const FolderEntry* FindFolderEntry(std::string_view itemId) const; const AssetEntry* FindAssetEntry(std::string_view itemId) const; + std::optional ResolveEditCommandTarget() const; const UIEditorPanelContentHostPanelState* FindMountedProjectPanel( const UIEditorPanelContentHostFrame& contentHostFrame) const; Layout BuildLayout(const ::XCEngine::UI::UIRect& bounds) const; diff --git a/new_editor/app/Features/Project/ProjectPanelCommands.cpp b/new_editor/app/Features/Project/ProjectPanelCommands.cpp new file mode 100644 index 00000000..4b761195 --- /dev/null +++ b/new_editor/app/Features/Project/ProjectPanelCommands.cpp @@ -0,0 +1,104 @@ +#include "ProjectPanelSupport.h" + +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +UIEditorHostCommandEvaluationResult BuildEvaluationResult( + bool executable, + std::string message) { + UIEditorHostCommandEvaluationResult result = {}; + result.executable = executable; + result.message = std::move(message); + return result; +} + +UIEditorHostCommandDispatchResult BuildDispatchResult( + bool commandExecuted, + std::string message) { + UIEditorHostCommandDispatchResult result = {}; + result.commandExecuted = commandExecuted; + result.message = std::move(message); + return result; +} + +} // namespace + +UIEditorHostCommandEvaluationResult ProjectPanel::EvaluateEditCommand( + std::string_view commandId) const { + const std::optional target = ResolveEditCommandTarget(); + if (!target.has_value()) { + return BuildEvaluationResult(false, "Select an asset or folder in Project first."); + } + + if (target->assetsRoot && + (commandId == "edit.rename" || commandId == "edit.delete")) { + return BuildEvaluationResult(false, "The Assets root cannot be renamed or deleted."); + } + + if (commandId == "edit.rename") { + return BuildEvaluationResult( + true, + "Rename project item '" + target->displayName + "'."); + } + + if (commandId == "edit.delete") { + return BuildEvaluationResult( + false, + "Project delete is blocked until asset metadata ownership is wired."); + } + + if (commandId == "edit.duplicate") { + return BuildEvaluationResult( + false, + "Project duplicate is blocked until asset metadata rewrite is wired."); + } + + if (commandId == "edit.cut" || + commandId == "edit.copy" || + commandId == "edit.paste") { + return BuildEvaluationResult( + false, + "Project clipboard has no bound asset transfer owner in the current shell."); + } + + return BuildEvaluationResult(false, "Project does not expose this edit command."); +} + +UIEditorHostCommandDispatchResult ProjectPanel::DispatchEditCommand( + std::string_view commandId) { + const UIEditorHostCommandEvaluationResult evaluation = EvaluateEditCommand(commandId); + if (!evaluation.executable) { + return BuildDispatchResult(false, evaluation.message); + } + + if (commandId == "edit.rename") { + if (m_assetSelection.HasSelection()) { + if (const AssetEntry* asset = FindAssetEntry(m_assetSelection.GetSelectedId()); + asset != nullptr) { + EmitEvent(EventKind::RenameRequested, EventSource::None, asset); + return BuildDispatchResult( + true, + "Project rename requested for '" + asset->displayName + "'."); + } + } + + if (m_folderSelection.HasSelection()) { + if (const FolderEntry* folder = FindFolderEntry(m_folderSelection.GetSelectedId()); + folder != nullptr) { + EmitEvent(EventKind::RenameRequested, EventSource::None, folder); + return BuildDispatchResult( + true, + "Project rename requested for '" + folder->label + "'."); + } + } + + return BuildDispatchResult(false, "Select an asset or folder in Project first."); + } + + return BuildDispatchResult(false, "Project does not expose this edit command."); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerCreation.cpp b/new_editor/app/Platform/Win32/WindowManager/Creation.cpp similarity index 97% rename from new_editor/app/Platform/Win32/EditorWindowManagerCreation.cpp rename to new_editor/app/Platform/Win32/WindowManager/Creation.cpp index e7c4f0c5..49ba186c 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManagerCreation.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/Creation.cpp @@ -1,8 +1,8 @@ -#include "EditorWindowManager.h" +#include "Platform/Win32/EditorWindowManager.h" #include "State/EditorContext.h" #include "Bootstrap/EditorResources.h" -#include "EditorWindow.h" +#include "Platform/Win32/EditorWindow.h" #include diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerCrossWindowDrop.cpp b/new_editor/app/Platform/Win32/WindowManager/CrossWindowDrop.cpp similarity index 91% rename from new_editor/app/Platform/Win32/EditorWindowManagerCrossWindowDrop.cpp rename to new_editor/app/Platform/Win32/WindowManager/CrossWindowDrop.cpp index 57596274..382337af 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManagerCrossWindowDrop.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/CrossWindowDrop.cpp @@ -1,13 +1,13 @@ -#include "EditorWindowManager.h" +#include "Platform/Win32/EditorWindowManager.h" -#include "Platform/Win32/EditorWindowManagerCrossWindowDropSupport.h" +#include "Platform/Win32/WindowManager/CrossWindowDropInternal.h" #include "State/EditorContext.h" -#include "EditorWindow.h" +#include "Platform/Win32/EditorWindow.h" namespace XCEngine::UI::Editor::App { -using EditorWindowManagerCrossWindowDropSupport::CrossWindowDockDropTarget; -using EditorWindowManagerCrossWindowDropSupport::TryResolveCrossWindowDockDropTarget; +using Win32::Internal::CrossWindowDockDropTarget; +using Win32::Internal::TryResolveCrossWindowDockDropTarget; using ::XCEngine::UI::UIPoint; bool EditorWindowManager::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerCrossWindowDropSupport.h b/new_editor/app/Platform/Win32/WindowManager/CrossWindowDropInternal.h similarity index 96% rename from new_editor/app/Platform/Win32/EditorWindowManagerCrossWindowDropSupport.h rename to new_editor/app/Platform/Win32/WindowManager/CrossWindowDropInternal.h index 77b47d88..9316dd3f 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManagerCrossWindowDropSupport.h +++ b/new_editor/app/Platform/Win32/WindowManager/CrossWindowDropInternal.h @@ -10,7 +10,7 @@ namespace XCEngine::UI { struct UIPoint; } -namespace XCEngine::UI::Editor::App::EditorWindowManagerCrossWindowDropSupport { +namespace XCEngine::UI::Editor::App::Win32::Internal { struct CrossWindowDockDropTarget { bool valid = false; @@ -119,4 +119,4 @@ inline bool TryResolveCrossWindowDockDropTarget( return false; } -} // namespace XCEngine::UI::Editor::App::EditorWindowManagerCrossWindowDropSupport +} // namespace XCEngine::UI::Editor::App::Win32::Internal diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerDetach.cpp b/new_editor/app/Platform/Win32/WindowManager/Detach.cpp similarity index 96% rename from new_editor/app/Platform/Win32/EditorWindowManagerDetach.cpp rename to new_editor/app/Platform/Win32/WindowManager/Detach.cpp index 8d2a8e6a..7fedf530 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManagerDetach.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/Detach.cpp @@ -1,7 +1,7 @@ -#include "EditorWindowManager.h" +#include "Platform/Win32/EditorWindowManager.h" #include "State/EditorContext.h" -#include "EditorWindow.h" +#include "Platform/Win32/EditorWindow.h" namespace XCEngine::UI::Editor::App { diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerLifecycle.cpp b/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp similarity index 96% rename from new_editor/app/Platform/Win32/EditorWindowManagerLifecycle.cpp rename to new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp index 10f820ba..56b5b2ce 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManagerLifecycle.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp @@ -1,7 +1,7 @@ -#include "EditorWindowManager.h" +#include "Platform/Win32/EditorWindowManager.h" #include "State/EditorContext.h" -#include "EditorWindow.h" +#include "Platform/Win32/EditorWindow.h" #include diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerLookup.cpp b/new_editor/app/Platform/Win32/WindowManager/Lookup.cpp similarity index 96% rename from new_editor/app/Platform/Win32/EditorWindowManagerLookup.cpp rename to new_editor/app/Platform/Win32/WindowManager/Lookup.cpp index a5c0d673..e3bfaca9 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManagerLookup.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/Lookup.cpp @@ -1,6 +1,6 @@ -#include "EditorWindowManager.h" +#include "Platform/Win32/EditorWindowManager.h" -#include "EditorWindow.h" +#include "Platform/Win32/EditorWindow.h" namespace XCEngine::UI::Editor::App { diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerTabDrag.cpp b/new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp similarity index 97% rename from new_editor/app/Platform/Win32/EditorWindowManagerTabDrag.cpp rename to new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp index 90288bf0..78f597df 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManagerTabDrag.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp @@ -1,8 +1,8 @@ -#include "EditorWindowManager.h" +#include "Platform/Win32/EditorWindowManager.h" -#include "Platform/Win32/EditorWindowManagerCrossWindowDropSupport.h" +#include "Platform/Win32/WindowManager/CrossWindowDropInternal.h" #include "State/EditorContext.h" -#include "EditorWindow.h" +#include "Platform/Win32/EditorWindow.h" #include #include @@ -11,8 +11,8 @@ namespace XCEngine::UI::Editor::App { namespace { -using EditorWindowManagerCrossWindowDropSupport::CrossWindowDockDropTarget; -using EditorWindowManagerCrossWindowDropSupport::TryResolveCrossWindowDockDropTarget; +using Win32::Internal::CrossWindowDockDropTarget; +using Win32::Internal::TryResolveCrossWindowDockDropTarget; constexpr LONG kFallbackDragHotspotX = 40; constexpr LONG kFallbackDragHotspotY = 12; diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerWindowSet.cpp b/new_editor/app/Platform/Win32/WindowManager/WindowSet.cpp similarity index 95% rename from new_editor/app/Platform/Win32/EditorWindowManagerWindowSet.cpp rename to new_editor/app/Platform/Win32/WindowManager/WindowSet.cpp index 57075cf7..df7008e9 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManagerWindowSet.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/WindowSet.cpp @@ -1,7 +1,7 @@ -#include "EditorWindowManager.h" +#include "Platform/Win32/EditorWindowManager.h" #include "State/EditorContext.h" -#include "EditorWindow.h" +#include "Platform/Win32/EditorWindow.h" namespace XCEngine::UI::Editor::App { diff --git a/new_editor/app/Platform/Win32/EditorWindowManagerWindowSynchronization.cpp b/new_editor/app/Platform/Win32/WindowManager/WindowSync.cpp similarity index 98% rename from new_editor/app/Platform/Win32/EditorWindowManagerWindowSynchronization.cpp rename to new_editor/app/Platform/Win32/WindowManager/WindowSync.cpp index 1b07b833..90a3bd77 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManagerWindowSynchronization.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/WindowSync.cpp @@ -1,7 +1,7 @@ -#include "EditorWindowManager.h" +#include "Platform/Win32/EditorWindowManager.h" #include "State/EditorContext.h" -#include "EditorWindow.h" +#include "Platform/Win32/EditorWindow.h" #include diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterBackBuffers.cpp b/new_editor/app/Rendering/D3D12/SwapChainPresenter/BackBuffers.cpp similarity index 98% rename from new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterBackBuffers.cpp rename to new_editor/app/Rendering/D3D12/SwapChainPresenter/BackBuffers.cpp index 21578b3a..f5d7afe6 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterBackBuffers.cpp +++ b/new_editor/app/Rendering/D3D12/SwapChainPresenter/BackBuffers.cpp @@ -1,4 +1,4 @@ -#include "D3D12WindowSwapChainPresenter.h" +#include "Rendering/D3D12/D3D12WindowSwapChainPresenter.h" namespace XCEngine::UI::Editor::Host { diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterLifecycle.cpp b/new_editor/app/Rendering/D3D12/SwapChainPresenter/Lifecycle.cpp similarity index 98% rename from new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterLifecycle.cpp rename to new_editor/app/Rendering/D3D12/SwapChainPresenter/Lifecycle.cpp index 4af405a4..9e17bc2f 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterLifecycle.cpp +++ b/new_editor/app/Rendering/D3D12/SwapChainPresenter/Lifecycle.cpp @@ -1,4 +1,4 @@ -#include "D3D12WindowSwapChainPresenter.h" +#include "Rendering/D3D12/D3D12WindowSwapChainPresenter.h" namespace XCEngine::UI::Editor::Host { diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterPresentation.cpp b/new_editor/app/Rendering/D3D12/SwapChainPresenter/Presentation.cpp similarity index 96% rename from new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterPresentation.cpp rename to new_editor/app/Rendering/D3D12/SwapChainPresenter/Presentation.cpp index db9a0c0f..e8ab4723 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterPresentation.cpp +++ b/new_editor/app/Rendering/D3D12/SwapChainPresenter/Presentation.cpp @@ -1,4 +1,4 @@ -#include "D3D12WindowSwapChainPresenter.h" +#include "Rendering/D3D12/D3D12WindowSwapChainPresenter.h" #include diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp b/new_editor/app/Rendering/D3D12/SwapChainPresenter/Presenter.cpp similarity index 80% rename from new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp rename to new_editor/app/Rendering/D3D12/SwapChainPresenter/Presenter.cpp index 06c8ccf2..0bc1dd0d 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp +++ b/new_editor/app/Rendering/D3D12/SwapChainPresenter/Presenter.cpp @@ -1,4 +1,4 @@ -#include "D3D12WindowSwapChainPresenter.h" +#include "Rendering/D3D12/D3D12WindowSwapChainPresenter.h" namespace XCEngine::UI::Editor::Host { diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterResize.cpp b/new_editor/app/Rendering/D3D12/SwapChainPresenter/Resize.cpp similarity index 97% rename from new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterResize.cpp rename to new_editor/app/Rendering/D3D12/SwapChainPresenter/Resize.cpp index c6e1700f..7b25aa87 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenterResize.cpp +++ b/new_editor/app/Rendering/D3D12/SwapChainPresenter/Resize.cpp @@ -1,4 +1,4 @@ -#include "D3D12WindowSwapChainPresenter.h" +#include "Rendering/D3D12/D3D12WindowSwapChainPresenter.h" #include diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerCleanup.cpp b/new_editor/app/Rendering/Viewport/RenderTargetManager/Cleanup.cpp similarity index 97% rename from new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerCleanup.cpp rename to new_editor/app/Rendering/Viewport/RenderTargetManager/Cleanup.cpp index 9637e0aa..31d2b0bc 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerCleanup.cpp +++ b/new_editor/app/Rendering/Viewport/RenderTargetManager/Cleanup.cpp @@ -1,4 +1,4 @@ -#include "ViewportRenderTargets.h" +#include "Rendering/Viewport/ViewportRenderTargets.h" namespace XCEngine::UI::Editor::App { diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargetManager.cpp b/new_editor/app/Rendering/Viewport/RenderTargetManager/Manager.cpp similarity index 81% rename from new_editor/app/Rendering/Viewport/ViewportRenderTargetManager.cpp rename to new_editor/app/Rendering/Viewport/RenderTargetManager/Manager.cpp index 464213cc..5f9cc038 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargetManager.cpp +++ b/new_editor/app/Rendering/Viewport/RenderTargetManager/Manager.cpp @@ -1,4 +1,4 @@ -#include "ViewportRenderTargets.h" +#include "Rendering/Viewport/ViewportRenderTargets.h" namespace XCEngine::UI::Editor::App { diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerResources.cpp b/new_editor/app/Rendering/Viewport/RenderTargetManager/Resources.cpp similarity index 99% rename from new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerResources.cpp rename to new_editor/app/Rendering/Viewport/RenderTargetManager/Resources.cpp index 053ffbdb..27820d1e 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerResources.cpp +++ b/new_editor/app/Rendering/Viewport/RenderTargetManager/Resources.cpp @@ -1,4 +1,4 @@ -#include "ViewportRenderTargets.h" +#include "Rendering/Viewport/ViewportRenderTargets.h" namespace XCEngine::UI::Editor::App { diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerSurfaces.cpp b/new_editor/app/Rendering/Viewport/RenderTargetManager/Surfaces.cpp similarity index 97% rename from new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerSurfaces.cpp rename to new_editor/app/Rendering/Viewport/RenderTargetManager/Surfaces.cpp index 84bc3984..64cfd0d8 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargetManagerSurfaces.cpp +++ b/new_editor/app/Rendering/Viewport/RenderTargetManager/Surfaces.cpp @@ -1,4 +1,4 @@ -#include "ViewportRenderTargets.h" +#include "Rendering/Viewport/ViewportRenderTargets.h" namespace XCEngine::UI::Editor::App { diff --git a/new_editor/app/State/EditorContext.cpp b/new_editor/app/State/EditorContext.cpp index 9891c3bf..3c9c7286 100644 --- a/new_editor/app/State/EditorContext.cpp +++ b/new_editor/app/State/EditorContext.cpp @@ -52,6 +52,12 @@ void EditorContext::AttachTextMeasurer( m_shellServices.textMeasurer = &textMeasurer; } +void EditorContext::BindEditCommandRoutes( + EditorEditCommandRoute* hierarchyRoute, + EditorEditCommandRoute* projectRoute) { + m_hostCommandBridge.BindEditCommandRoutes(hierarchyRoute, projectRoute); +} + void EditorContext::SetExitRequestHandler(std::function handler) { m_hostCommandBridge.SetExitRequestHandler(std::move(handler)); } diff --git a/new_editor/app/State/EditorContext.h b/new_editor/app/State/EditorContext.h index b48b4370..943ddbbb 100644 --- a/new_editor/app/State/EditorContext.h +++ b/new_editor/app/State/EditorContext.h @@ -16,10 +16,15 @@ namespace XCEngine::UI::Editor::App { +class EditorEditCommandRoute; + class EditorContext { public: bool Initialize(const std::filesystem::path& repoRoot); void AttachTextMeasurer(const UIEditorTextMeasurer& textMeasurer); + void BindEditCommandRoutes( + EditorEditCommandRoute* hierarchyRoute, + EditorEditCommandRoute* projectRoute); void SetExitRequestHandler(std::function handler); void SyncSessionFromWorkspace(const UIEditorWorkspaceController& workspaceController); diff --git a/new_editor/include/XCEditor/Docking/UIEditorDockHost.h b/new_editor/include/XCEditor/Docking/UIEditorDockHost.h index 857472ab..af9fc9ce 100644 --- a/new_editor/include/XCEditor/Docking/UIEditorDockHost.h +++ b/new_editor/include/XCEditor/Docking/UIEditorDockHost.h @@ -148,6 +148,7 @@ enum class UIEditorDockHostCursorKind : std::uint8_t { // keeps drawing the surrounding chrome/frame. struct UIEditorDockHostForegroundOptions { std::vector externalBodyPanelIds = {}; + bool deferDropPreviewOverlay = false; }; const UIEditorDockHostSplitterLayout* FindUIEditorDockHostSplitterLayout( @@ -188,6 +189,12 @@ void AppendUIEditorDockHostForeground( const UIEditorDockHostPalette& palette, const UIEditorDockHostMetrics& metrics = {}); +void AppendUIEditorDockHostOverlay( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorDockHostLayout& layout, + const UIEditorDockHostPalette& palette = {}, + const UIEditorDockHostMetrics& metrics = {}); + void AppendUIEditorDockHost( ::XCEngine::UI::UIDrawList& drawList, const ::XCEngine::UI::UIRect& bounds, diff --git a/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h b/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h index 934f99c0..098a6b8f 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h +++ b/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h @@ -152,4 +152,10 @@ void AppendUIEditorShellCompose( const UIEditorShellComposePalette& palette = {}, const UIEditorShellComposeMetrics& metrics = {}); +void AppendUIEditorShellComposeOverlay( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorShellComposeFrame& frame, + const UIEditorShellComposePalette& palette = {}, + const UIEditorShellComposeMetrics& metrics = {}); + } // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceCompose.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceCompose.h index 848436a1..54f680ee 100644 --- a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceCompose.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceCompose.h @@ -55,6 +55,10 @@ struct UIEditorWorkspaceComposeFrame { std::vector viewportFrames = {}; }; +struct UIEditorWorkspaceComposeAppendOptions { + bool deferDockPreviewOverlay = false; +}; + const UIEditorWorkspacePanelPresentationModel* FindUIEditorWorkspacePanelPresentationModel( const std::vector& presentations, std::string_view panelId); @@ -102,6 +106,13 @@ void AppendUIEditorWorkspaceCompose( const Widgets::UIEditorDockHostPalette& dockHostPalette = {}, const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {}, const Widgets::UIEditorViewportSlotPalette& viewportPalette = {}, - const Widgets::UIEditorViewportSlotMetrics& viewportMetrics = {}); + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics = {}, + const UIEditorWorkspaceComposeAppendOptions& options = {}); + +void AppendUIEditorWorkspaceComposeOverlay( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorWorkspaceComposeFrame& frame, + const Widgets::UIEditorDockHostPalette& dockHostPalette = {}, + const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {}); } // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceSplitterDragCorrection.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceSplitterDragCorrection.h new file mode 100644 index 00000000..8b446c6c --- /dev/null +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceSplitterDragCorrection.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorWorkspaceSplitterDragCorrectionState { + std::string splitNodeId = {}; + UIEditorWorkspaceLayoutSnapshot baselineSnapshot = {}; + Widgets::UIEditorDockHostLayout baselineDockLayout = {}; +}; + +[[nodiscard]] bool IsUIEditorWorkspaceSplitterDragCorrectionActive( + const UIEditorWorkspaceSplitterDragCorrectionState& state); + +void ResetUIEditorWorkspaceSplitterDragCorrectionState( + UIEditorWorkspaceSplitterDragCorrectionState& state); + +void BeginUIEditorWorkspaceSplitterDragCorrection( + UIEditorWorkspaceSplitterDragCorrectionState& state, + std::string_view splitNodeId, + const UIEditorWorkspaceLayoutSnapshot& baselineSnapshot, + const Widgets::UIEditorDockHostLayout& baselineDockLayout); + +bool TryBuildUIEditorWorkspaceSplitterDragCorrectedSnapshot( + const UIEditorWorkspaceSplitterDragCorrectionState& state, + const ::XCEngine::UI::UIPoint& pointerPosition, + bool hasPointerPosition, + const UIEditorPanelRegistry& panelRegistry, + const Widgets::UIEditorDockHostMetrics& metrics, + UIEditorWorkspaceLayoutSnapshot& outSnapshot); + +bool TryApplyUIEditorWorkspaceSplitterDragCorrection( + UIEditorWorkspaceSplitterDragCorrectionState& state, + const UIEditorDockHostInteractionState& dockHostInteractionState, + const UIEditorWorkspaceLayoutSnapshot& preUpdateSnapshot, + const Widgets::UIEditorDockHostLayout& preUpdateDockLayout, + UIEditorWorkspaceController& workspaceController, + const Widgets::UIEditorDockHostMetrics& metrics); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Docking/UIEditorDockHostHitTest.cpp b/new_editor/src/Docking/DockHostHitTest.cpp similarity index 100% rename from new_editor/src/Docking/UIEditorDockHostHitTest.cpp rename to new_editor/src/Docking/DockHostHitTest.cpp diff --git a/new_editor/src/Docking/UIEditorDockHostInteractionHelpers.cpp b/new_editor/src/Docking/DockHostInteractionHelpers.cpp similarity index 98% rename from new_editor/src/Docking/UIEditorDockHostInteractionHelpers.cpp rename to new_editor/src/Docking/DockHostInteractionHelpers.cpp index 3a4324e9..7ee267c6 100644 --- a/new_editor/src/Docking/UIEditorDockHostInteractionHelpers.cpp +++ b/new_editor/src/Docking/DockHostInteractionHelpers.cpp @@ -1,11 +1,10 @@ -#include "Docking/UIEditorDockHostInteractionInternal.h" - +#include "Docking/DockHostInteractionInternal.h" #include #include #include #include -namespace XCEngine::UI::Editor::Detail { +namespace XCEngine::UI::Editor::Internal { using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; @@ -19,7 +18,7 @@ using Widgets::UIEditorDockHostTabStackLayout; using Widgets::UIEditorTabStripHitTargetKind; using Widgets::UIEditorTabStripItem; -bool ShouldUsePointerPosition(const UIInputEvent& event) { +bool ShouldUseDockHostPointerPosition(const UIInputEvent& event) { switch (event.type) { case UIInputEventType::PointerMove: case UIInputEventType::PointerEnter: @@ -457,4 +456,4 @@ void SyncHoverTarget( HitTestUIEditorDockHost(layout, state.pointerPosition); } -} // namespace XCEngine::UI::Editor::Detail +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Docking/UIEditorDockHostInteractionInternal.h b/new_editor/src/Docking/DockHostInteractionInternal.h similarity index 94% rename from new_editor/src/Docking/UIEditorDockHostInteractionInternal.h rename to new_editor/src/Docking/DockHostInteractionInternal.h index 59a0a590..3a01e836 100644 --- a/new_editor/src/Docking/UIEditorDockHostInteractionInternal.h +++ b/new_editor/src/Docking/DockHostInteractionInternal.h @@ -2,7 +2,7 @@ #include -namespace XCEngine::UI::Editor::Detail { +namespace XCEngine::UI::Editor::Internal { struct DockHostTabStripEventResult { bool consumed = false; @@ -22,7 +22,7 @@ struct DockHostTabStripEventResult { int priority = 0; }; -bool ShouldUsePointerPosition(const ::XCEngine::UI::UIInputEvent& event); +bool ShouldUseDockHostPointerPosition(const ::XCEngine::UI::UIInputEvent& event); bool ShouldDispatchTabStripEvent( const ::XCEngine::UI::UIInputEvent& event, bool splitterActive); @@ -74,4 +74,4 @@ void SyncHoverTarget( UIEditorDockHostInteractionState& state, const Widgets::UIEditorDockHostLayout& layout); -} // namespace XCEngine::UI::Editor::Detail +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Docking/DockHostMeasure.cpp b/new_editor/src/Docking/DockHostMeasure.cpp new file mode 100644 index 00000000..197c30f0 --- /dev/null +++ b/new_editor/src/Docking/DockHostMeasure.cpp @@ -0,0 +1,162 @@ +#include "Docking/DockHostMeasureInternal.h" + +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::Internal { + +namespace { + +using ::XCEngine::UI::Layout::MeasureSplitterDesiredSize; +using ::XCEngine::UI::Layout::MeasureUITabStrip; +using ::XCEngine::UI::Layout::UITabStripMeasureItem; +using Widgets::UIEditorTabStripItem; + +DockHostNodeMeasureResult MeasureDockHostTabStackNode( + const UIEditorWorkspaceNode& node, + const UIEditorPanelRegistry& panelRegistry, + const UIEditorWorkspaceSession& session, + const Widgets::UIEditorDockHostMetrics& metrics) { + std::vector measureItems = {}; + for (const UIEditorWorkspaceNode& child : node.children) { + if (child.kind != UIEditorWorkspaceNodeKind::Panel || + !IsWorkspacePanelOpenAndVisible(session, child.panel.panelId)) { + continue; + } + + UIEditorTabStripItem item = {}; + item.tabId = child.panel.panelId; + item.title = child.panel.title; + if (const UIEditorPanelDescriptor* descriptor = + FindUIEditorPanelDescriptor(panelRegistry, child.panel.panelId); + descriptor != nullptr) { + item.closable = descriptor->canClose; + } + + UITabStripMeasureItem measureItem = {}; + measureItem.desiredHeaderLabelWidth = + ResolveUIEditorTabStripDesiredHeaderLabelWidth(item, metrics.tabStripMetrics); + measureItem.desiredContentSize = MeasureDockTabContentMinimumSize(metrics); + measureItem.minimumContentSize = MeasureDockTabContentMinimumSize(metrics); + measureItems.push_back(std::move(measureItem)); + } + + if (measureItems.empty()) { + return {}; + } + + const auto measured = + MeasureUITabStrip(measureItems, metrics.tabStripMetrics.layoutMetrics); + return { + true, + measured.minimumSize + }; +} + +DockHostNodeMeasureResult MeasureDockHostSplitNode( + const UIEditorWorkspaceNode& node, + const UIEditorPanelRegistry& panelRegistry, + const UIEditorWorkspaceSession& session, + const Widgets::UIEditorDockHostMetrics& metrics) { + const DockHostNodeMeasureResult primary = + MeasureDockHostNode(node.children[0], panelRegistry, session, metrics); + const DockHostNodeMeasureResult secondary = + MeasureDockHostNode(node.children[1], panelRegistry, session, metrics); + + if (!primary.visible && !secondary.visible) { + return {}; + } + if (!primary.visible) { + return secondary; + } + if (!secondary.visible) { + return primary; + } + + return { + true, + MeasureSplitterDesiredSize( + ToUILayoutAxis(node.splitAxis), + primary.minimumSize, + secondary.minimumSize, + metrics.splitterMetrics.thickness) + }; +} + +} // namespace + +float ClampNonNegative(float value) { + return (std::max)(value, 0.0f); +} + +::XCEngine::UI::Layout::UILayoutAxis ToUILayoutAxis(UIEditorWorkspaceSplitAxis axis) { + return axis == UIEditorWorkspaceSplitAxis::Horizontal + ? ::XCEngine::UI::Layout::UILayoutAxis::Horizontal + : ::XCEngine::UI::Layout::UILayoutAxis::Vertical; +} + +float GetMainExtent( + const ::XCEngine::UI::UISize& size, + UIEditorWorkspaceSplitAxis axis) { + return axis == UIEditorWorkspaceSplitAxis::Horizontal ? size.width : size.height; +} + +float GetMainExtent( + const ::XCEngine::UI::UIRect& rect, + UIEditorWorkspaceSplitAxis axis) { + return axis == UIEditorWorkspaceSplitAxis::Horizontal ? rect.width : rect.height; +} + +bool IsWorkspacePanelOpenAndVisible( + const UIEditorWorkspaceSession& session, + std::string_view panelId) { + const UIEditorPanelSessionState* panelState = + FindUIEditorPanelSessionState(session, panelId); + return panelState != nullptr && panelState->open && panelState->visible; +} + +Widgets::UIEditorPanelFrameMetrics BuildDockTabContentFrameMetrics( + const Widgets::UIEditorDockHostMetrics& metrics) { + Widgets::UIEditorPanelFrameMetrics frameMetrics = metrics.panelFrameMetrics; + frameMetrics.headerHeight = 0.0f; + frameMetrics.footerHeight = 0.0f; + frameMetrics.actionButtonExtent = 0.0f; + frameMetrics.actionInsetX = 0.0f; + frameMetrics.actionGap = 0.0f; + frameMetrics.contentPadding = 0.0f; + frameMetrics.cornerRounding = 0.0f; + return frameMetrics; +} + +::XCEngine::UI::UISize MeasureDockTabContentMinimumSize( + const Widgets::UIEditorDockHostMetrics& metrics) { + const Widgets::UIEditorPanelFrameMetrics frameMetrics = + BuildDockTabContentFrameMetrics(metrics); + return ::XCEngine::UI::UISize( + metrics.minimumTabContentBodySize.width + + ClampNonNegative(frameMetrics.contentPadding) * 2.0f, + metrics.minimumTabContentBodySize.height + + ClampNonNegative(frameMetrics.contentPadding) * 2.0f); +} + +DockHostNodeMeasureResult MeasureDockHostNode( + const UIEditorWorkspaceNode& node, + const UIEditorPanelRegistry& panelRegistry, + const UIEditorWorkspaceSession& session, + const Widgets::UIEditorDockHostMetrics& metrics) { + switch (node.kind) { + case UIEditorWorkspaceNodeKind::Panel: + return {}; + case UIEditorWorkspaceNodeKind::TabStack: + return MeasureDockHostTabStackNode(node, panelRegistry, session, metrics); + case UIEditorWorkspaceNodeKind::Split: + return MeasureDockHostSplitNode(node, panelRegistry, session, metrics); + } + + return {}; +} + +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Docking/DockHostMeasureInternal.h b/new_editor/src/Docking/DockHostMeasureInternal.h new file mode 100644 index 00000000..0e1f4059 --- /dev/null +++ b/new_editor/src/Docking/DockHostMeasureInternal.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include + +namespace XCEngine::UI::Editor::Internal { + +struct DockHostNodeMeasureResult { + bool visible = false; + ::XCEngine::UI::UISize minimumSize = {}; +}; + +float ClampNonNegative(float value); +::XCEngine::UI::Layout::UILayoutAxis ToUILayoutAxis(UIEditorWorkspaceSplitAxis axis); +float GetMainExtent( + const ::XCEngine::UI::UISize& size, + UIEditorWorkspaceSplitAxis axis); +float GetMainExtent( + const ::XCEngine::UI::UIRect& rect, + UIEditorWorkspaceSplitAxis axis); +bool IsWorkspacePanelOpenAndVisible( + const UIEditorWorkspaceSession& session, + std::string_view panelId); +Widgets::UIEditorPanelFrameMetrics BuildDockTabContentFrameMetrics( + const Widgets::UIEditorDockHostMetrics& metrics); +::XCEngine::UI::UISize MeasureDockTabContentMinimumSize( + const Widgets::UIEditorDockHostMetrics& metrics); +DockHostNodeMeasureResult MeasureDockHostNode( + const UIEditorWorkspaceNode& node, + const UIEditorPanelRegistry& panelRegistry, + const UIEditorWorkspaceSession& session, + const Widgets::UIEditorDockHostMetrics& metrics); + +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Docking/UIEditorDockHostRendering.cpp b/new_editor/src/Docking/DockHostRendering.cpp similarity index 86% rename from new_editor/src/Docking/UIEditorDockHostRendering.cpp rename to new_editor/src/Docking/DockHostRendering.cpp index 6fc89134..1e9778b0 100644 --- a/new_editor/src/Docking/UIEditorDockHostRendering.cpp +++ b/new_editor/src/Docking/DockHostRendering.cpp @@ -33,6 +33,23 @@ UIColor ResolveSplitterColor( return palette.splitterColor; } +void AppendDropPreviewOverlay( + UIDrawList& drawList, + const UIEditorDockHostLayout& layout, + const UIEditorDockHostPalette& palette) { + if (!layout.dropPreview.visible) { + return; + } + + drawList.AddFilledRect( + layout.dropPreview.previewRect, + palette.dropPreviewFillColor); + drawList.AddRectOutline( + layout.dropPreview.previewRect, + palette.dropPreviewBorderColor, + 1.0f); +} + } // namespace void AppendUIEditorDockHostBackground( @@ -97,14 +114,8 @@ void AppendUIEditorDockHostForeground( } } - if (layout.dropPreview.visible) { - drawList.AddFilledRect( - layout.dropPreview.previewRect, - palette.dropPreviewFillColor); - drawList.AddRectOutline( - layout.dropPreview.previewRect, - palette.dropPreviewBorderColor, - 1.0f); + if (!options.deferDropPreviewOverlay) { + AppendDropPreviewOverlay(drawList, layout, palette); } } @@ -121,6 +132,14 @@ void AppendUIEditorDockHostForeground( metrics); } +void AppendUIEditorDockHostOverlay( + UIDrawList& drawList, + const UIEditorDockHostLayout& layout, + const UIEditorDockHostPalette& palette, + const UIEditorDockHostMetrics&) { + AppendDropPreviewOverlay(drawList, layout, palette); +} + void AppendUIEditorDockHost( UIDrawList& drawList, const ::XCEngine::UI::UIRect& bounds, diff --git a/new_editor/src/Docking/UIEditorDockHost.cpp b/new_editor/src/Docking/UIEditorDockHost.cpp index dca8ed20..264ff520 100644 --- a/new_editor/src/Docking/UIEditorDockHost.cpp +++ b/new_editor/src/Docking/UIEditorDockHost.cpp @@ -1,5 +1,7 @@ #include +#include "Docking/DockHostMeasureInternal.h" + #include #include @@ -11,39 +13,14 @@ namespace XCEngine::UI::Editor::Widgets { namespace { using ::XCEngine::UI::UIRect; -using ::XCEngine::UI::UISize; using ::XCEngine::UI::Layout::ArrangeUISplitter; -using ::XCEngine::UI::Layout::MeasureSplitterDesiredSize; -using ::XCEngine::UI::Layout::MeasureUITabStrip; -using ::XCEngine::UI::Layout::UITabStripMeasureItem; -using ::XCEngine::UI::Layout::UILayoutAxis; using ::XCEngine::UI::Widgets::ExpandUISplitterHandleHitRect; - -struct DockMeasureResult { - bool visible = false; - UISize minimumSize = {}; -}; - -float ClampNonNegative(float value) { - return (std::max)(value, 0.0f); -} - -UILayoutAxis ToLayoutAxis(UIEditorWorkspaceSplitAxis axis) { - return axis == UIEditorWorkspaceSplitAxis::Horizontal - ? UILayoutAxis::Horizontal - : UILayoutAxis::Vertical; -} - -float GetMainExtent(const UISize& size, UIEditorWorkspaceSplitAxis axis) { - return axis == UIEditorWorkspaceSplitAxis::Horizontal ? size.width : size.height; -} +namespace Internal = ::XCEngine::UI::Editor::Internal; bool IsPanelOpenAndVisible( const UIEditorWorkspaceSession& session, std::string_view panelId) { - const UIEditorPanelSessionState* panelState = - FindUIEditorPanelSessionState(session, panelId); - return panelState != nullptr && panelState->open && panelState->visible; + return Internal::IsWorkspacePanelOpenAndVisible(session, panelId); } const UIEditorPanelDescriptor* FindPanelDescriptor( @@ -52,123 +29,6 @@ const UIEditorPanelDescriptor* FindPanelDescriptor( return FindUIEditorPanelDescriptor(panelRegistry, panelId); } -UIEditorPanelFrameMetrics BuildTabContentFrameMetrics( - const UIEditorDockHostMetrics& metrics) { - UIEditorPanelFrameMetrics frameMetrics = metrics.panelFrameMetrics; - frameMetrics.headerHeight = 0.0f; - frameMetrics.footerHeight = 0.0f; - frameMetrics.actionButtonExtent = 0.0f; - frameMetrics.actionInsetX = 0.0f; - frameMetrics.actionGap = 0.0f; - frameMetrics.contentPadding = 0.0f; - frameMetrics.cornerRounding = 0.0f; - return frameMetrics; -} - -UISize MeasureTabContentMinimumSize( - const UIEditorDockHostMetrics& metrics) { - const UIEditorPanelFrameMetrics frameMetrics = BuildTabContentFrameMetrics(metrics); - return UISize( - metrics.minimumTabContentBodySize.width + - ClampNonNegative(frameMetrics.contentPadding) * 2.0f, - metrics.minimumTabContentBodySize.height + - ClampNonNegative(frameMetrics.contentPadding) * 2.0f); -} - -DockMeasureResult MeasureNodeRecursive( - const UIEditorWorkspaceNode& node, - const UIEditorPanelRegistry& panelRegistry, - const UIEditorWorkspaceSession& session, - const UIEditorDockHostMetrics& metrics); - -DockMeasureResult MeasureTabStackNode( - const UIEditorWorkspaceNode& node, - const UIEditorPanelRegistry& panelRegistry, - const UIEditorWorkspaceSession& session, - const UIEditorDockHostMetrics& metrics) { - std::vector measureItems = {}; - for (const UIEditorWorkspaceNode& child : node.children) { - if (child.kind != UIEditorWorkspaceNodeKind::Panel || - !IsPanelOpenAndVisible(session, child.panel.panelId)) { - continue; - } - - UIEditorTabStripItem item = {}; - item.tabId = child.panel.panelId; - item.title = child.panel.title; - const UIEditorPanelDescriptor* descriptor = - FindPanelDescriptor(panelRegistry, child.panel.panelId); - item.closable = descriptor != nullptr ? descriptor->canClose : true; - - UITabStripMeasureItem measureItem = {}; - measureItem.desiredHeaderLabelWidth = - ResolveUIEditorTabStripDesiredHeaderLabelWidth(item, metrics.tabStripMetrics); - measureItem.desiredContentSize = MeasureTabContentMinimumSize(metrics); - measureItem.minimumContentSize = MeasureTabContentMinimumSize(metrics); - measureItems.push_back(std::move(measureItem)); - } - - if (measureItems.empty()) { - return {}; - } - - const auto measured = - MeasureUITabStrip(measureItems, metrics.tabStripMetrics.layoutMetrics); - DockMeasureResult result = {}; - result.visible = true; - result.minimumSize = measured.minimumSize; - return result; -} - -DockMeasureResult MeasureSplitNode( - const UIEditorWorkspaceNode& node, - const UIEditorPanelRegistry& panelRegistry, - const UIEditorWorkspaceSession& session, - const UIEditorDockHostMetrics& metrics) { - const DockMeasureResult primary = - MeasureNodeRecursive(node.children[0], panelRegistry, session, metrics); - const DockMeasureResult secondary = - MeasureNodeRecursive(node.children[1], panelRegistry, session, metrics); - - if (!primary.visible && !secondary.visible) { - return {}; - } - - if (!primary.visible) { - return secondary; - } - - if (!secondary.visible) { - return primary; - } - - DockMeasureResult result = {}; - result.visible = true; - result.minimumSize = MeasureSplitterDesiredSize( - ToLayoutAxis(node.splitAxis), - primary.minimumSize, - secondary.minimumSize, - metrics.splitterMetrics.thickness); - return result; -} - -DockMeasureResult MeasureNodeRecursive( - const UIEditorWorkspaceNode& node, - const UIEditorPanelRegistry& panelRegistry, - const UIEditorWorkspaceSession& session, - const UIEditorDockHostMetrics& metrics) { - switch (node.kind) { - case UIEditorWorkspaceNodeKind::Panel: - return {}; - case UIEditorWorkspaceNodeKind::TabStack: - return MeasureTabStackNode(node, panelRegistry, session, metrics); - case UIEditorWorkspaceNodeKind::Split: - return MeasureSplitNode(node, panelRegistry, session, metrics); - } - - return {}; -} - std::size_t ResolveSelectedVisibleTabIndex( const UIEditorWorkspaceNode& node, const std::vector& visibleChildIndices) { @@ -330,7 +190,7 @@ void LayoutTabStackNode( tabStackLayout.contentFrameLayout = BuildUIEditorPanelFrameLayout( tabStackLayout.tabStripLayout.contentRect, tabStackLayout.contentFrameState, - BuildTabContentFrameMetrics(metrics)); + Internal::BuildDockTabContentFrameMetrics(metrics)); layout.tabStacks.push_back(std::move(tabStackLayout)); } @@ -344,10 +204,10 @@ void LayoutSplitNode( const UIEditorDockHostState& state, const UIEditorDockHostMetrics& metrics, UIEditorDockHostLayout& layout) { - const DockMeasureResult primaryMeasure = - MeasureNodeRecursive(node.children[0], panelRegistry, session, metrics); - const DockMeasureResult secondaryMeasure = - MeasureNodeRecursive(node.children[1], panelRegistry, session, metrics); + const auto primaryMeasure = + Internal::MeasureDockHostNode(node.children[0], panelRegistry, session, metrics); + const auto secondaryMeasure = + Internal::MeasureDockHostNode(node.children[1], panelRegistry, session, metrics); if (!primaryMeasure.visible && !secondaryMeasure.visible) { return; @@ -385,18 +245,18 @@ void LayoutSplitNode( splitterLayout.bounds = bounds; splitterLayout.metrics = metrics.splitterMetrics; splitterLayout.constraints.primaryMin = - GetMainExtent(primaryMeasure.minimumSize, node.splitAxis); + Internal::GetMainExtent(primaryMeasure.minimumSize, node.splitAxis); splitterLayout.constraints.secondaryMin = - GetMainExtent(secondaryMeasure.minimumSize, node.splitAxis); + Internal::GetMainExtent(secondaryMeasure.minimumSize, node.splitAxis); splitterLayout.splitterLayout = ArrangeUISplitter( bounds, - ToLayoutAxis(node.splitAxis), + Internal::ToUILayoutAxis(node.splitAxis), node.splitRatio, splitterLayout.constraints, splitterLayout.metrics); splitterLayout.handleHitRect = ExpandUISplitterHandleHitRect( splitterLayout.splitterLayout.handleRect, - ToLayoutAxis(node.splitAxis), + Internal::ToUILayoutAxis(node.splitAxis), (std::max)( 0.0f, (splitterLayout.metrics.hitThickness - @@ -477,7 +337,7 @@ const UIEditorDockHostTabStackLayout* FindTabStackLayoutByNodeId( } UIRect InsetRect(const UIRect& rect, float inset) { - const float clampedInset = ClampNonNegative(inset); + const float clampedInset = Internal::ClampNonNegative(inset); const float insetX = (std::min)(clampedInset, rect.width * 0.5f); const float insetY = (std::min)(clampedInset, rect.height * 0.5f); return UIRect( @@ -510,7 +370,7 @@ UIRect ResolveDropPreviewRect( bounds.height * 0.35f); case UIEditorWorkspaceDockPlacement::Center: default: - return InsetRect(bounds, 4.0f); + return bounds; } } @@ -591,8 +451,8 @@ UIEditorDockHostLayout BuildUIEditorDockHostLayout( layout.bounds = UIRect( bounds.x, bounds.y, - ClampNonNegative(bounds.width), - ClampNonNegative(bounds.height)); + Internal::ClampNonNegative(bounds.width), + Internal::ClampNonNegative(bounds.height)); const UIEditorWorkspaceModel canonicalWorkspace = CanonicalizeUIEditorWorkspaceModel(workspace); LayoutNodeRecursive( diff --git a/new_editor/src/Docking/UIEditorDockHostInteraction.cpp b/new_editor/src/Docking/UIEditorDockHostInteraction.cpp index 3357aa70..e119a171 100644 --- a/new_editor/src/Docking/UIEditorDockHostInteraction.cpp +++ b/new_editor/src/Docking/UIEditorDockHostInteraction.cpp @@ -1,4 +1,4 @@ -#include "Docking/UIEditorDockHostInteractionInternal.h" +#include "Docking/DockHostInteractionInternal.h" #include @@ -29,7 +29,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( const ::XCEngine::UI::UIRect& bounds, const std::vector& inputEvents, const Widgets::UIEditorDockHostMetrics& metrics) { - Detail::SyncDockHostTabStripVisualStates(state); + Internal::SyncDockHostTabStripVisualStates(state); Widgets::UIEditorDockHostLayout layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -37,8 +37,8 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - Detail::PruneTabStripInteractionEntries(state, layout); - Detail::SyncDockHostTabStripVisualStates(state); + Internal::PruneTabStripInteractionEntries(state, layout); + Internal::SyncDockHostTabStripVisualStates(state); layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -46,11 +46,11 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - Detail::SyncHoverTarget(state, layout); + Internal::SyncHoverTarget(state, layout); UIEditorDockHostInteractionResult interactionResult = {}; for (const UIInputEvent& event : inputEvents) { - if (Detail::ShouldUsePointerPosition(event)) { + if (Internal::ShouldUseDockHostPointerPosition(event)) { state.pointerPosition = event.position; state.hasPointerPosition = true; } else if (event.type == UIInputEventType::PointerLeave) { @@ -67,11 +67,11 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( pointerHitTarget.kind == UIEditorDockHostHitTargetKind::SplitterHandle; UIEditorDockHostInteractionResult eventResult = {}; - const Detail::DockHostTabStripEventResult tabStripResult = + const Internal::DockHostTabStripEventResult tabStripResult = !splitterOwnsPointerDown && - Detail::ShouldDispatchTabStripEvent(event, state.splitterDragState.active) - ? Detail::ProcessTabStripEvent(state, layout, event, metrics) - : Detail::DockHostTabStripEventResult {}; + Internal::ShouldDispatchTabStripEvent(event, state.splitterDragState.active) + ? Internal::ProcessTabStripEvent(state, layout, event, metrics) + : Internal::DockHostTabStripEventResult {}; eventResult.requestPointerCapture = tabStripResult.requestPointerCapture; eventResult.releasePointerCapture = tabStripResult.releasePointerCapture; if (!tabStripResult.draggedTabId.empty() && @@ -81,7 +81,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (!state.activeTabDragNodeId.empty() && !state.activeTabDragPanelId.empty() && !state.splitterDragState.active) { - Detail::SyncDockPreview(state, layout); + Internal::SyncDockPreview(state, layout); } else if (event.type == UIInputEventType::PointerLeave || state.activeTabDragNodeId.empty()) { state.dockHostState.dropPreview = {}; @@ -98,12 +98,12 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (state.splitterDragState.active) { EndUISplitterDrag(state.splitterDragState); state.dockHostState.activeSplitterNodeId.clear(); - Detail::ClearAllTabStripTransientInteractions(state); + Internal::ClearAllTabStripTransientInteractions(state); eventResult.consumed = true; eventResult.releasePointerCapture = true; } if (!state.activeTabDragNodeId.empty() || tabStripResult.dragCanceled) { - Detail::ClearTabDockDragState(state); + Internal::ClearTabDockDragState(state); eventResult.consumed = true; eventResult.releasePointerCapture = eventResult.releasePointerCapture || tabStripResult.releasePointerCapture; @@ -122,7 +122,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( state.splitterDragState, state.pointerPosition, draggedLayout)) { - eventResult.layoutResult = Detail::ApplySplitRatio( + eventResult.layoutResult = Internal::ApplySplitRatio( controller, state.dockHostState.activeSplitterNodeId, draggedLayout.splitRatio); @@ -140,10 +140,10 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (tabStripResult.dragStarted) { state.activeTabDragNodeId = tabStripResult.nodeId; state.activeTabDragPanelId = tabStripResult.draggedTabId; - Detail::SyncDockPreview(state, layout); + Internal::SyncDockPreview(state, layout); } if (tabStripResult.dragEnded || tabStripResult.dragCanceled) { - Detail::ClearTabDockDragState(state); + Internal::ClearTabDockDragState(state); } if (eventResult.consumed || eventResult.hitTarget.kind != UIEditorDockHostHitTargetKind::None) { @@ -157,7 +157,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( state.dockHostState.hoveredTarget = {}; } state.dockHostState.dropPreview = {}; - if (!Detail::HasFocusedTabStrip(state)) { + if (!Internal::HasFocusedTabStrip(state)) { state.dockHostState.focused = false; } break; @@ -184,7 +184,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( splitter->metrics, state.pointerPosition, state.splitterDragState)) { - Detail::ClearAllTabStripTransientInteractions(state); + Internal::ClearAllTabStripTransientInteractions(state); state.dockHostState.activeSplitterNodeId = splitter->nodeId; state.dockHostState.focused = true; eventResult.consumed = true; @@ -221,7 +221,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( state.splitterDragState, state.pointerPosition, draggedLayout)) { - eventResult.layoutResult = Detail::ApplySplitRatio( + eventResult.layoutResult = Internal::ApplySplitRatio( controller, state.dockHostState.activeSplitterNodeId, draggedLayout.splitRatio); @@ -230,7 +230,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( UIEditorWorkspaceLayoutOperationStatus::Changed; } EndUISplitterDrag(state.splitterDragState); - Detail::ClearAllTabStripTransientInteractions(state); + Internal::ClearAllTabStripTransientInteractions(state); eventResult.consumed = true; eventResult.releasePointerCapture = true; eventResult.activeSplitterNodeId = state.dockHostState.activeSplitterNodeId; @@ -258,7 +258,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( UIEditorWorkspaceLayoutOperationStatus::Changed; eventResult.consumed = true; eventResult.hitTarget = tabStripResult.hitTarget; - Detail::ClearTabDockDragState(state); + Internal::ClearTabDockDragState(state); state.dockHostState.focused = true; break; } @@ -281,7 +281,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( std::size_t insertionIndex = preview.insertionIndex; if (insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { if (const auto* targetTabStack = - Detail::FindTabStackLayoutByNodeId(layout, preview.targetNodeId); + Internal::FindTabStackLayoutByNodeId(layout, preview.targetNodeId); targetTabStack != nullptr) { insertionIndex = targetTabStack->items.size(); } else { @@ -314,7 +314,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( eventResult.hitTarget.nodeId = preview.targetNodeId; eventResult.releasePointerCapture = eventResult.releasePointerCapture || tabStripResult.releasePointerCapture; - Detail::ClearTabDockDragState(state); + Internal::ClearTabDockDragState(state); state.dockHostState.focused = true; break; } @@ -322,14 +322,14 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (!state.activeTabDragNodeId.empty() && !state.activeTabDragPanelId.empty() && state.hasPointerPosition && - !Detail::IsPointInsideRect(layout.bounds, state.pointerPosition)) { + !Internal::IsPointInsideRect(layout.bounds, state.pointerPosition)) { eventResult.detachRequested = true; eventResult.consumed = true; eventResult.detachedNodeId = state.activeTabDragNodeId; eventResult.detachedPanelId = state.activeTabDragPanelId; eventResult.releasePointerCapture = eventResult.releasePointerCapture || tabStripResult.releasePointerCapture; - Detail::ClearTabDockDragState(state); + Internal::ClearTabDockDragState(state); state.dockHostState.focused = true; break; } @@ -337,13 +337,13 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (tabStripResult.dragEnded || tabStripResult.dragCanceled) { eventResult.consumed = tabStripResult.consumed; eventResult.hitTarget = tabStripResult.hitTarget; - Detail::ClearTabDockDragState(state); + Internal::ClearTabDockDragState(state); state.dockHostState.focused = true; break; } if (tabStripResult.commandRequested && !tabStripResult.panelId.empty()) { - eventResult.commandResult = Detail::DispatchPanelCommand( + eventResult.commandResult = Internal::DispatchPanelCommand( controller, tabStripResult.commandKind, tabStripResult.panelId); @@ -370,7 +370,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( case UIEditorDockHostHitTargetKind::PanelHeader: case UIEditorDockHostHitTargetKind::PanelBody: case UIEditorDockHostHitTargetKind::PanelFooter: - eventResult.commandResult = Detail::DispatchPanelCommand( + eventResult.commandResult = Internal::DispatchPanelCommand( controller, UIEditorWorkspaceCommandKind::ActivatePanel, state.dockHostState.hoveredTarget.panelId); @@ -382,7 +382,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( break; case UIEditorDockHostHitTargetKind::PanelCloseButton: - eventResult.commandResult = Detail::DispatchPanelCommand( + eventResult.commandResult = Internal::DispatchPanelCommand( controller, UIEditorWorkspaceCommandKind::ClosePanel, state.dockHostState.hoveredTarget.panelId); @@ -403,7 +403,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( case UIEditorDockHostHitTargetKind::None: default: - if (!Detail::HasFocusedTabStrip(state)) { + if (!Internal::HasFocusedTabStrip(state)) { state.dockHostState.focused = false; } break; @@ -414,13 +414,13 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (tabStripResult.dragCanceled) { eventResult.consumed = true; eventResult.hitTarget = tabStripResult.hitTarget; - Detail::ClearTabDockDragState(state); + Internal::ClearTabDockDragState(state); state.dockHostState.focused = true; break; } if (tabStripResult.commandRequested && !tabStripResult.panelId.empty()) { - eventResult.commandResult = Detail::DispatchPanelCommand( + eventResult.commandResult = Internal::DispatchPanelCommand( controller, tabStripResult.commandKind, tabStripResult.panelId); @@ -440,7 +440,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( break; } - Detail::SyncDockHostTabStripVisualStates(state); + Internal::SyncDockHostTabStripVisualStates(state); layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -448,8 +448,8 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - Detail::PruneTabStripInteractionEntries(state, layout); - Detail::SyncDockHostTabStripVisualStates(state); + Internal::PruneTabStripInteractionEntries(state, layout); + Internal::SyncDockHostTabStripVisualStates(state); layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -457,7 +457,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - Detail::SyncHoverTarget(state, layout); + Internal::SyncHoverTarget(state, layout); if (eventResult.hitTarget.kind == UIEditorDockHostHitTargetKind::None) { eventResult.hitTarget = state.dockHostState.hoveredTarget; } @@ -475,7 +475,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( } } - Detail::SyncDockHostTabStripVisualStates(state); + Internal::SyncDockHostTabStripVisualStates(state); layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -483,8 +483,8 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - Detail::PruneTabStripInteractionEntries(state, layout); - Detail::SyncDockHostTabStripVisualStates(state); + Internal::PruneTabStripInteractionEntries(state, layout); + Internal::SyncDockHostTabStripVisualStates(state); layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -492,7 +492,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - Detail::SyncHoverTarget(state, layout); + Internal::SyncHoverTarget(state, layout); if (interactionResult.hitTarget.kind == UIEditorDockHostHitTargetKind::None) { interactionResult.hitTarget = state.dockHostState.hoveredTarget; } diff --git a/new_editor/src/Fields/UIEditorColorFieldInternal.h b/new_editor/src/Fields/ColorFieldInternal.h similarity index 64% rename from new_editor/src/Fields/UIEditorColorFieldInternal.h rename to new_editor/src/Fields/ColorFieldInternal.h index f5ccdb22..69bac430 100644 --- a/new_editor/src/Fields/UIEditorColorFieldInternal.h +++ b/new_editor/src/Fields/ColorFieldInternal.h @@ -1,16 +1,15 @@ #pragma once #include - #include -namespace XCEngine::UI::Editor::Widgets::Detail { +namespace XCEngine::UI::Editor::Widgets::Internal { -inline float ClampNonNegative(float value) { +inline float ClampColorFieldNonNegative(float value) { return (std::max)(0.0f, value); } UIEditorColorFieldMetrics ResolveMetrics(const UIEditorColorFieldMetrics& metrics); UIEditorColorFieldPalette ResolvePalette(const UIEditorColorFieldPalette& palette); -} // namespace XCEngine::UI::Editor::Widgets::Detail +} // namespace XCEngine::UI::Editor::Widgets::Internal diff --git a/new_editor/src/Fields/UIEditorColorFieldRendering.cpp b/new_editor/src/Fields/ColorFieldRendering.cpp similarity index 91% rename from new_editor/src/Fields/UIEditorColorFieldRendering.cpp rename to new_editor/src/Fields/ColorFieldRendering.cpp index 2252f4a8..9a8987a0 100644 --- a/new_editor/src/Fields/UIEditorColorFieldRendering.cpp +++ b/new_editor/src/Fields/ColorFieldRendering.cpp @@ -1,5 +1,6 @@ -#include "Fields/UIEditorColorFieldInternal.h" +#include "Fields/ColorFieldInternal.h" +#include #include #include @@ -9,7 +10,7 @@ namespace XCEngine::UI::Editor::Widgets { -namespace Detail { +namespace Internal { using ::XCEngine::UI::UIColor; using ::XCEngine::UI::UIDrawList; @@ -379,7 +380,7 @@ void AppendHueWheel( const float outerRadius = layout.hueWheelOuterRadius; const float innerRadius = layout.hueWheelInnerRadius; const float ringRadius = (outerRadius + innerRadius) * 0.5f; - const float ringThickness = ClampNonNegative(outerRadius - innerRadius); + const float ringThickness = ClampColorFieldNonNegative(outerRadius - innerRadius); const int segmentCount = (std::max)(192, static_cast(outerRadius * 2.4f)); for (int segment = 0; segment < segmentCount; ++segment) { const float t0 = static_cast(segment) / static_cast(segmentCount); @@ -430,7 +431,7 @@ UIPoint ResolveSaturationValueHandleCenter( layout.saturationValueRect.height); } -} // namespace Detail +} // namespace Internal void AppendUIEditorColorFieldBackground( UIDrawList& drawList, @@ -439,8 +440,8 @@ void AppendUIEditorColorFieldBackground( const UIEditorColorFieldState& state, const UIEditorColorFieldPalette& palette, const UIEditorColorFieldMetrics& metrics) { - const UIEditorColorFieldPalette resolvedPalette = Detail::ResolvePalette(palette); - const UIEditorColorFieldMetrics resolvedMetrics = Detail::ResolveMetrics(metrics); + const UIEditorColorFieldPalette resolvedPalette = Internal::ResolvePalette(palette); + const UIEditorColorFieldMetrics resolvedMetrics = Internal::ResolveMetrics(metrics); if (state.activeTarget != UIEditorColorFieldHitTargetKind::None && resolvedPalette.rowActiveColor.a > 0.0f) { drawList.AddFilledRect(layout.bounds, resolvedPalette.rowActiveColor); @@ -456,7 +457,7 @@ void AppendUIEditorColorFieldBackground( state.popupOpen || state.focused || state.hoveredTarget == UIEditorColorFieldHitTargetKind::Swatch; - Detail::AppendColorSample( + Internal::AppendColorSample( drawList, layout.swatchRect, spec.value, @@ -509,7 +510,7 @@ void AppendUIEditorColorFieldBackground( : resolvedPalette.closeButtonColor, 0.0f); - Detail::AppendColorSample( + Internal::AppendColorSample( drawList, layout.popupPreviewRect, spec.value, @@ -520,49 +521,49 @@ void AppendUIEditorColorFieldBackground( resolvedMetrics.borderThickness, resolvedMetrics.swatchRounding); - const UIEditorHsvColor hsv = Detail::ResolveDisplayHsv(spec, state); - Detail::AppendHueWheel(drawList, layout, resolvedPalette, resolvedMetrics); - Detail::AppendSaturationValueSquare( + const UIEditorHsvColor hsv = Internal::ResolveDisplayHsv(spec, state); + Internal::AppendHueWheel(drawList, layout, resolvedPalette, resolvedMetrics); + Internal::AppendSaturationValueSquare( drawList, layout.saturationValueRect, hsv, resolvedPalette, resolvedMetrics); - const UIColor rgbColor = Detail::ClampColor(spec.value); - Detail::AppendPopupChannelRow( + const UIColor rgbColor = Internal::ClampColor(spec.value); + Internal::AppendPopupChannelRow( drawList, layout.redLabelRect, layout.redSliderRect, layout.redValueRect, "R", - Detail::FormatChannelValue(spec.value.r), + Internal::FormatChannelValue(spec.value.r), UIColor(0.0f, rgbColor.g, rgbColor.b, 1.0f), UIColor(1.0f, rgbColor.g, rgbColor.b, 1.0f), spec.value.r, false, resolvedPalette, resolvedMetrics); - Detail::AppendPopupChannelRow( + Internal::AppendPopupChannelRow( drawList, layout.greenLabelRect, layout.greenSliderRect, layout.greenValueRect, "G", - Detail::FormatChannelValue(spec.value.g), + Internal::FormatChannelValue(spec.value.g), UIColor(rgbColor.r, 0.0f, rgbColor.b, 1.0f), UIColor(rgbColor.r, 1.0f, rgbColor.b, 1.0f), spec.value.g, false, resolvedPalette, resolvedMetrics); - Detail::AppendPopupChannelRow( + Internal::AppendPopupChannelRow( drawList, layout.blueLabelRect, layout.blueSliderRect, layout.blueValueRect, "B", - Detail::FormatChannelValue(spec.value.b), + Internal::FormatChannelValue(spec.value.b), UIColor(rgbColor.r, rgbColor.g, 0.0f, 1.0f), UIColor(rgbColor.r, rgbColor.g, 1.0f, 1.0f), spec.value.b, @@ -570,13 +571,13 @@ void AppendUIEditorColorFieldBackground( resolvedPalette, resolvedMetrics); if (spec.showAlpha && layout.alphaSliderRect.width > 0.0f) { - Detail::AppendPopupChannelRow( + Internal::AppendPopupChannelRow( drawList, layout.alphaLabelRect, layout.alphaSliderRect, layout.alphaValueRect, "A", - Detail::FormatChannelValue(spec.value.a), + Internal::FormatChannelValue(spec.value.a), UIColor(rgbColor.r, rgbColor.g, rgbColor.b, 0.0f), UIColor(rgbColor.r, rgbColor.g, rgbColor.b, 1.0f), spec.value.a, @@ -585,10 +586,10 @@ void AppendUIEditorColorFieldBackground( resolvedMetrics); } - Detail::AppendNumericValueBox( + Internal::AppendNumericValueBox( drawList, layout.hexValueRect, - Detail::FormatPopupHexText(spec), + Internal::FormatPopupHexText(spec), resolvedPalette, resolvedMetrics); } @@ -600,9 +601,9 @@ void AppendUIEditorColorFieldForeground( const UIEditorColorFieldState& state, const UIEditorColorFieldPalette& palette, const UIEditorColorFieldMetrics& metrics) { - const UIEditorColorFieldPalette resolvedPalette = Detail::ResolvePalette(palette); - const UIEditorColorFieldMetrics resolvedMetrics = Detail::ResolveMetrics(metrics); - Detail::AppendPopupText( + const UIEditorColorFieldPalette resolvedPalette = Internal::ResolvePalette(palette); + const UIEditorColorFieldMetrics resolvedMetrics = Internal::ResolveMetrics(metrics); + Internal::AppendPopupText( drawList, layout.labelRect, spec.label, @@ -615,7 +616,7 @@ void AppendUIEditorColorFieldForeground( return; } - Detail::AppendPopupText( + Internal::AppendPopupText( drawList, layout.popupTitleRect, "Color", @@ -623,24 +624,24 @@ void AppendUIEditorColorFieldForeground( resolvedMetrics.titleFontSize, 0.0f, 0.0f); - Detail::AppendCloseGlyph( + Internal::AppendCloseGlyph( drawList, layout.popupCloseButtonRect, resolvedPalette.closeGlyphColor); - const UIEditorHsvColor hsv = Detail::ResolveDisplayHsv(spec, state); - Detail::AppendWheelHandle( + const UIEditorHsvColor hsv = Internal::ResolveDisplayHsv(spec, state); + Internal::AppendWheelHandle( drawList, - Detail::ResolveHueWheelHandleCenter(layout, hsv.hue), + Internal::ResolveHueWheelHandleCenter(layout, hsv.hue), resolvedMetrics.ringHandleRadius, resolvedPalette); - Detail::AppendWheelHandle( + Internal::AppendWheelHandle( drawList, - Detail::ResolveSaturationValueHandleCenter(layout, hsv), + Internal::ResolveSaturationValueHandleCenter(layout, hsv), resolvedMetrics.handleRadius, resolvedPalette); - Detail::AppendPopupText( + Internal::AppendPopupText( drawList, layout.hexLabelRect, "Hexadecimal", @@ -658,8 +659,8 @@ void AppendUIEditorColorField( const UIEditorColorFieldPalette& palette, const UIEditorColorFieldMetrics& metrics, const UIRect& viewportRect) { - const UIEditorColorFieldPalette resolvedPalette = Detail::ResolvePalette(palette); - const UIEditorColorFieldMetrics resolvedMetrics = Detail::ResolveMetrics(metrics); + const UIEditorColorFieldPalette resolvedPalette = Internal::ResolvePalette(palette); + const UIEditorColorFieldMetrics resolvedMetrics = Internal::ResolveMetrics(metrics); const UIEditorColorFieldLayout layout = BuildUIEditorColorFieldLayout(bounds, spec, resolvedMetrics, viewportRect); AppendUIEditorColorFieldBackground( diff --git a/new_editor/src/Fields/PropertyGridInteractionColor.cpp b/new_editor/src/Fields/PropertyGridInteractionColor.cpp new file mode 100644 index 00000000..719729ec --- /dev/null +++ b/new_editor/src/Fields/PropertyGridInteractionColor.cpp @@ -0,0 +1,228 @@ +#include "Fields/PropertyGridInteractionInternal.h" +#include +#include +#include + +namespace XCEngine::UI::Editor::Internal { + +using Widgets::FindUIEditorPropertyGridVisibleFieldIndex; +using Widgets::UIEditorColorFieldHitTargetKind; +using Widgets::UIEditorColorFieldSpec; +using Widgets::UIEditorPropertyGridColorFieldVisualState; +using Widgets::UIEditorPropertyGridField; +using Widgets::UIEditorPropertyGridFieldKind; +using Widgets::UIEditorPropertyGridHitTargetKind; +using Widgets::UIEditorPropertyGridInvalidIndex; +using Widgets::UIEditorPropertyGridLayout; +using Widgets::UIEditorPropertyGridSection; + +UIEditorPropertyGridColorFieldVisualState* FindMutableColorFieldVisualState( + UIEditorPropertyGridInteractionState& state, + std::string_view fieldId) { + for (UIEditorPropertyGridColorFieldVisualState& entry : + state.propertyGridState.colorFieldStates) { + if (entry.fieldId == fieldId) { + return &entry; + } + } + + return nullptr; +} + +const UIEditorPropertyGridColorFieldVisualState* FindColorFieldVisualState( + const UIEditorPropertyGridInteractionState& state, + std::string_view fieldId) { + for (const UIEditorPropertyGridColorFieldVisualState& entry : + state.propertyGridState.colorFieldStates) { + if (entry.fieldId == fieldId) { + return &entry; + } + } + + return nullptr; +} + +UIEditorColorFieldInteractionState BuildColorFieldInteractionState( + const UIEditorPropertyGridInteractionState& state, + std::string_view fieldId) { + UIEditorColorFieldInteractionState interactionState = {}; + if (const UIEditorPropertyGridColorFieldVisualState* entry = + FindColorFieldVisualState(state, fieldId); + entry != nullptr) { + interactionState.colorFieldState = entry->state; + } + + interactionState.pointerPosition = state.pointerPosition; + interactionState.hasPointerPosition = state.hasPointerPosition; + return interactionState; +} + +void StoreColorFieldVisualState( + UIEditorPropertyGridInteractionState& state, + std::string_view fieldId, + const UIEditorColorFieldInteractionState& interactionState) { + if (UIEditorPropertyGridColorFieldVisualState* entry = + FindMutableColorFieldVisualState(state, fieldId); + entry != nullptr) { + entry->state = interactionState.colorFieldState; + return; + } + + UIEditorPropertyGridColorFieldVisualState entry = {}; + entry.fieldId = std::string(fieldId); + entry.state = interactionState.colorFieldState; + state.propertyGridState.colorFieldStates.push_back(std::move(entry)); +} + +void PruneColorFieldVisualStates( + UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections) { + const auto isVisibleColorFieldId = [&layout, §ions](std::string_view fieldId) { + const std::size_t visibleFieldIndex = + FindUIEditorPropertyGridVisibleFieldIndex(layout, fieldId, sections); + if (visibleFieldIndex == UIEditorPropertyGridInvalidIndex || + visibleFieldIndex >= layout.visibleFieldSectionIndices.size() || + visibleFieldIndex >= layout.visibleFieldIndices.size()) { + return false; + } + + const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; + const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; + return sectionIndex < sections.size() && + fieldIndex < sections[sectionIndex].fields.size() && + sections[sectionIndex].fields[fieldIndex].kind == + UIEditorPropertyGridFieldKind::Color; + }; + + state.propertyGridState.colorFieldStates.erase( + std::remove_if( + state.propertyGridState.colorFieldStates.begin(), + state.propertyGridState.colorFieldStates.end(), + [&isVisibleColorFieldId]( + const UIEditorPropertyGridColorFieldVisualState& entry) { + return !isVisibleColorFieldId(entry.fieldId); + }), + state.propertyGridState.colorFieldStates.end()); +} + +void CloseOtherColorFieldPopups( + UIEditorPropertyGridInteractionState& state, + std::string_view keepFieldId) { + for (UIEditorPropertyGridColorFieldVisualState& entry : + state.propertyGridState.colorFieldStates) { + if (entry.fieldId == keepFieldId) { + continue; + } + + entry.state.popupOpen = false; + entry.state.activeTarget = UIEditorColorFieldHitTargetKind::None; + } +} + +bool ProcessColorFieldEvent( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + const UIEditorPropertyGridLayout& layout, + std::vector& sections, + const Widgets::UIEditorPropertyGridMetrics& metrics, + const ::XCEngine::UI::UIInputEvent& event, + UIEditorPropertyGridInteractionResult& result) { + const Widgets::UIEditorColorFieldMetrics colorMetrics = + BuildUIEditorPropertyGridColorFieldMetrics(metrics); + const ::XCEngine::UI::UIRect popupViewportRect = + ResolvePopupViewportRect(layout.bounds); + bool handled = false; + + for (std::size_t visibleFieldIndex = 0u; + visibleFieldIndex < layout.visibleFieldIndices.size(); + ++visibleFieldIndex) { + const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; + const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; + if (sectionIndex >= sections.size() || + fieldIndex >= sections[sectionIndex].fields.size()) { + continue; + } + + UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; + if (field.kind != UIEditorPropertyGridFieldKind::Color) { + continue; + } + + UIEditorColorFieldInteractionState colorState = + BuildColorFieldInteractionState(state, field.fieldId); + const bool popupWasOpen = colorState.colorFieldState.popupOpen; + UIEditorColorFieldSpec spec = Widgets::Internal::BuildColorFieldSpec(field); + const UIEditorColorFieldInteractionFrame frame = + UpdateUIEditorColorFieldInteraction( + colorState, + spec, + layout.fieldRowRects[visibleFieldIndex], + { event }, + colorMetrics, + popupViewportRect); + + if (!frame.result.consumed && + frame.result.hitTarget.kind == UIEditorColorFieldHitTargetKind::None && + !popupWasOpen && + !colorState.colorFieldState.popupOpen) { + continue; + } + + handled = true; + field.colorValue.value = spec.value; + field.colorValue.showAlpha = spec.showAlpha; + StoreColorFieldVisualState(state, field.fieldId, colorState); + + if (frame.result.popupOpened) { + ClosePopup(state, result); + CloseOtherColorFieldPopups(state, field.fieldId); + } + + state.propertyGridState.focused = colorState.colorFieldState.focused; + state.propertyGridState.pressedFieldId.clear(); + + if (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None || + frame.result.popupOpened || + frame.result.colorChanged || + popupWasOpen || + colorState.colorFieldState.popupOpen) { + result.selectionChanged = + selectionModel.SetSelection(field.fieldId) || result.selectionChanged; + result.selectedFieldId = field.fieldId; + result.activeFieldId = field.fieldId; + state.keyboardNavigation.SetCurrentIndex(visibleFieldIndex); + } + + if (propertyEditModel.HasActiveEdit() && + propertyEditModel.GetActiveFieldId() != field.fieldId && + (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None || + frame.result.popupOpened)) { + CommitActiveEdit(state, propertyEditModel, sections, result); + } + + result.popupOpened = result.popupOpened || frame.result.popupOpened; + result.popupClosed = result.popupClosed || frame.result.popupClosed; + result.consumed = result.consumed || frame.result.consumed; + if (frame.result.colorChanged) { + SetChangedValueResult(field, result); + } + + if (frame.result.hitTarget.kind == UIEditorColorFieldHitTargetKind::Row) { + result.hitTarget.kind = UIEditorPropertyGridHitTargetKind::FieldRow; + result.hitTarget.sectionIndex = sectionIndex; + result.hitTarget.fieldIndex = fieldIndex; + result.hitTarget.visibleFieldIndex = visibleFieldIndex; + } else if (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None) { + result.hitTarget.kind = UIEditorPropertyGridHitTargetKind::ValueBox; + result.hitTarget.sectionIndex = sectionIndex; + result.hitTarget.fieldIndex = fieldIndex; + result.hitTarget.visibleFieldIndex = visibleFieldIndex; + } + } + + return handled; +} + +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Fields/PropertyGridInteractionEdit.cpp b/new_editor/src/Fields/PropertyGridInteractionEdit.cpp new file mode 100644 index 00000000..70e8660d --- /dev/null +++ b/new_editor/src/Fields/PropertyGridInteractionEdit.cpp @@ -0,0 +1,180 @@ +#include "Fields/PropertyGridInteractionInternal.h" +namespace XCEngine::UI::Editor::Internal { + +using Widgets::ResolveUIEditorPropertyGridFieldValueText; +using Widgets::UIEditorPropertyGridField; +using Widgets::UIEditorPropertyGridFieldKind; +using Widgets::UIEditorPropertyGridLayout; +using Widgets::UIEditorPropertyGridSection; + +bool IsInlineEditable(const UIEditorPropertyGridField& field) { + return field.kind == UIEditorPropertyGridFieldKind::Text || + field.kind == UIEditorPropertyGridFieldKind::Number; +} + +bool IsNumberEditCharacter( + const UIEditorPropertyGridField& field, + std::uint32_t character) { + if (field.kind != UIEditorPropertyGridFieldKind::Number) { + return true; + } + + if (character >= static_cast('0') && + character <= static_cast('9')) { + return true; + } + + if (character == static_cast('-') || + character == static_cast('+')) { + return true; + } + + return !field.numberValue.integerMode && + character == static_cast('.'); +} + +bool SelectVisibleField( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + std::size_t visibleFieldIndex, + UIEditorPropertyGridInteractionResult& result) { + if (visibleFieldIndex >= layout.visibleFieldIndices.size() || + visibleFieldIndex >= layout.visibleFieldSectionIndices.size()) { + return false; + } + + const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; + const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; + if (sectionIndex >= sections.size() || + fieldIndex >= sections[sectionIndex].fields.size()) { + return false; + } + + const UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; + result.selectionChanged = selectionModel.SetSelection(field.fieldId); + result.selectedFieldId = field.fieldId; + result.consumed = true; + state.keyboardNavigation.SetCurrentIndex(visibleFieldIndex); + return true; +} + +bool BeginFieldEdit( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + const UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result) { + if (field.readOnly || !IsInlineEditable(field)) { + return false; + } + + const std::string initialValue = + field.kind == UIEditorPropertyGridFieldKind::Text + ? field.valueText + : ResolveUIEditorPropertyGridFieldValueText(field); + const bool changed = propertyEditModel.BeginEdit(field.fieldId, initialValue); + if (!changed && + (!propertyEditModel.HasActiveEdit() || + propertyEditModel.GetActiveFieldId() != field.fieldId)) { + return false; + } + + state.textInputState.value = propertyEditModel.GetStagedValue(); + state.textInputState.caret = state.textInputState.value.size(); + result.editStarted = changed; + result.activeFieldId = field.fieldId; + result.consumed = true; + return true; +} + +bool CommitActiveEdit( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + std::vector& sections, + UIEditorPropertyGridInteractionResult& result) { + if (!propertyEditModel.HasActiveEdit()) { + return false; + } + + UIEditorPropertyGridField* field = + FindMutableField(sections, propertyEditModel.GetActiveFieldId()); + if (field == nullptr) { + propertyEditModel.CancelEdit(); + state.textInputState = {}; + return false; + } + + result.activeFieldId = field->fieldId; + if (field->kind == UIEditorPropertyGridFieldKind::Number) { + Widgets::UIEditorNumberFieldSpec spec = {}; + spec.fieldId = field->fieldId; + spec.label = field->label; + spec.value = field->numberValue.value; + spec.step = field->numberValue.step; + spec.minValue = field->numberValue.minValue; + spec.maxValue = field->numberValue.maxValue; + spec.integerMode = field->numberValue.integerMode; + spec.readOnly = field->readOnly; + + double parsedValue = field->numberValue.value; + if (!Widgets::TryParseUIEditorNumberFieldValue( + spec, + propertyEditModel.GetStagedValue(), + parsedValue)) { + result.editCommitRejected = true; + result.consumed = true; + return false; + } + + field->numberValue.value = parsedValue; + result.committedFieldId = field->fieldId; + result.committedValue = ResolveUIEditorPropertyGridFieldValueText(*field); + SetChangedValueResult(*field, result); + } else { + field->valueText = propertyEditModel.GetStagedValue(); + result.committedFieldId = field->fieldId; + result.committedValue = field->valueText; + SetChangedValueResult(*field, result); + } + + propertyEditModel.CommitEdit(); + state.textInputState = {}; + result.editCommitted = true; + result.consumed = true; + return true; +} + +bool CancelActiveEdit( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + UIEditorPropertyGridInteractionResult& result) { + if (!propertyEditModel.HasActiveEdit()) { + return false; + } + + result.activeFieldId = propertyEditModel.GetActiveFieldId(); + if (!propertyEditModel.CancelEdit()) { + return false; + } + + state.textInputState = {}; + result.editCanceled = true; + result.consumed = true; + return true; +} + +bool ToggleBoolField( + UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result) { + if (field.kind != UIEditorPropertyGridFieldKind::Bool || field.readOnly) { + return false; + } + + field.boolValue = !field.boolValue; + SetChangedValueResult(field, result); + result.consumed = true; + return true; +} + +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Fields/PropertyGridInteractionHelpers.cpp b/new_editor/src/Fields/PropertyGridInteractionHelpers.cpp new file mode 100644 index 00000000..461325e6 --- /dev/null +++ b/new_editor/src/Fields/PropertyGridInteractionHelpers.cpp @@ -0,0 +1,214 @@ +#include "Fields/PropertyGridInteractionInternal.h" +#include +#include + +namespace XCEngine::UI::Editor::Internal { + +using ::XCEngine::Input::KeyCode; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using Widgets::FindUIEditorPropertyGridFieldLocation; +using Widgets::FindUIEditorPropertyGridVisibleFieldIndex; +using Widgets::HitTestUIEditorPropertyGrid; +using Widgets::ResolveUIEditorPropertyGridFieldValueText; +using Widgets::UIEditorMenuPopupHitTarget; +using Widgets::UIEditorMenuPopupHitTargetKind; +using Widgets::UIEditorPropertyGridField; +using Widgets::UIEditorPropertyGridHitTarget; +using Widgets::UIEditorPropertyGridHitTargetKind; +using Widgets::UIEditorPropertyGridInvalidIndex; +using Widgets::UIEditorPropertyGridLayout; +using Widgets::UIEditorPropertyGridSection; + +bool ShouldUsePropertyGridPointerPosition(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + return true; + default: + return false; + } +} + +bool HasSystemModifiers(const ::XCEngine::UI::UIInputModifiers& modifiers) { + return modifiers.control || modifiers.alt || modifiers.super; +} + +bool ApplyKeyboardNavigation( + UIEditorPropertyGridInteractionState& state, + std::int32_t keyCode) { + switch (static_cast(keyCode)) { + case KeyCode::Up: + return state.keyboardNavigation.MovePrevious(); + case KeyCode::Down: + return state.keyboardNavigation.MoveNext(); + case KeyCode::Home: + return state.keyboardNavigation.MoveHome(); + case KeyCode::End: + return state.keyboardNavigation.MoveEnd(); + default: + return false; + } +} + +void ClearHoverState(UIEditorPropertyGridInteractionState& state) { + state.propertyGridState.hoveredSectionId.clear(); + state.propertyGridState.hoveredFieldId.clear(); + state.propertyGridState.hoveredHitTarget = UIEditorPropertyGridHitTargetKind::None; +} + +void ClearPopupState(UIEditorPropertyGridInteractionState& state) { + state.propertyGridState.popupFieldId.clear(); + state.propertyGridState.popupHighlightedIndex = UIEditorPropertyGridInvalidIndex; + state.pressedPopupIndex = UIEditorPropertyGridInvalidIndex; +} + +UIEditorPropertyGridField* FindMutableField( + std::vector& sections, + std::string_view fieldId) { + const auto location = FindUIEditorPropertyGridFieldLocation(sections, fieldId); + if (!location.IsValid() || + location.sectionIndex >= sections.size() || + location.fieldIndex >= sections[location.sectionIndex].fields.size()) { + return nullptr; + } + + return §ions[location.sectionIndex].fields[location.fieldIndex]; +} + +const UIEditorPropertyGridField* FindField( + const std::vector& sections, + std::string_view fieldId) { + const auto location = FindUIEditorPropertyGridFieldLocation(sections, fieldId); + if (!location.IsValid() || + location.sectionIndex >= sections.size() || + location.fieldIndex >= sections[location.sectionIndex].fields.size()) { + return nullptr; + } + + return §ions[location.sectionIndex].fields[location.fieldIndex]; +} + +void SetChangedValueResult( + const UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result) { + result.fieldValueChanged = true; + result.changedFieldId = field.fieldId; + result.changedValue = ResolveUIEditorPropertyGridFieldValueText(field); +} + +void MergeInteractionResult( + UIEditorPropertyGridInteractionResult& accumulated, + const UIEditorPropertyGridInteractionResult& current) { + accumulated.consumed = accumulated.consumed || current.consumed; + accumulated.sectionToggled = accumulated.sectionToggled || current.sectionToggled; + accumulated.selectionChanged = accumulated.selectionChanged || current.selectionChanged; + accumulated.keyboardNavigated = accumulated.keyboardNavigated || current.keyboardNavigated; + accumulated.editStarted = accumulated.editStarted || current.editStarted; + accumulated.editValueChanged = accumulated.editValueChanged || current.editValueChanged; + accumulated.editCommitted = accumulated.editCommitted || current.editCommitted; + accumulated.editCommitRejected = + accumulated.editCommitRejected || current.editCommitRejected; + accumulated.editCanceled = accumulated.editCanceled || current.editCanceled; + accumulated.popupOpened = accumulated.popupOpened || current.popupOpened; + accumulated.popupClosed = accumulated.popupClosed || current.popupClosed; + accumulated.fieldValueChanged = + accumulated.fieldValueChanged || current.fieldValueChanged; + accumulated.secondaryClicked = + accumulated.secondaryClicked || current.secondaryClicked; + + if (current.hitTarget.kind != UIEditorPropertyGridHitTargetKind::None) { + accumulated.hitTarget = current.hitTarget; + } + if (!current.toggledSectionId.empty()) { + accumulated.toggledSectionId = current.toggledSectionId; + } + if (!current.selectedFieldId.empty()) { + accumulated.selectedFieldId = current.selectedFieldId; + } + if (!current.activeFieldId.empty()) { + accumulated.activeFieldId = current.activeFieldId; + } + if (!current.committedFieldId.empty()) { + accumulated.committedFieldId = current.committedFieldId; + } + if (!current.committedValue.empty()) { + accumulated.committedValue = current.committedValue; + } + if (!current.changedFieldId.empty()) { + accumulated.changedFieldId = current.changedFieldId; + } + if (!current.changedValue.empty()) { + accumulated.changedValue = current.changedValue; + } +} + +void SyncHoverTarget( + UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const Widgets::UIEditorMenuPopupMetrics& popupMetrics) { + ClearHoverState(state); + if (!state.hasPointerPosition) { + return; + } + + const UIEditorMenuPopupHitTarget popupHit = + ResolvePopupHit(state, layout, sections, popupMetrics); + if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item) { + state.propertyGridState.popupHighlightedIndex = popupHit.index; + return; + } + if (popupHit.kind != UIEditorMenuPopupHitTargetKind::None) { + return; + } + + const UIEditorPropertyGridHitTarget hitTarget = + HitTestUIEditorPropertyGrid(layout, state.pointerPosition); + state.propertyGridState.hoveredHitTarget = hitTarget.kind; + if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::SectionHeader && + hitTarget.sectionIndex < sections.size()) { + state.propertyGridState.hoveredSectionId = + sections[hitTarget.sectionIndex].sectionId; + return; + } + + if ((hitTarget.kind == UIEditorPropertyGridHitTargetKind::FieldRow || + hitTarget.kind == UIEditorPropertyGridHitTargetKind::ValueBox) && + hitTarget.sectionIndex < sections.size() && + hitTarget.fieldIndex < sections[hitTarget.sectionIndex].fields.size()) { + state.propertyGridState.hoveredFieldId = + sections[hitTarget.sectionIndex].fields[hitTarget.fieldIndex].fieldId; + } +} + +void SyncKeyboardNavigation( + UIEditorPropertyGridInteractionState& state, + const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections) { + state.keyboardNavigation.SetItemCount(layout.visibleFieldIndices.size()); + state.keyboardNavigation.ClampToItemCount(); + + if (!selectionModel.HasSelection()) { + return; + } + + const std::size_t selectedVisibleIndex = + FindUIEditorPropertyGridVisibleFieldIndex( + layout, + selectionModel.GetSelectedId(), + sections); + if (selectedVisibleIndex == UIEditorPropertyGridInvalidIndex) { + return; + } + + if (!state.keyboardNavigation.HasCurrentIndex() || + state.keyboardNavigation.GetCurrentIndex() != selectedVisibleIndex) { + state.keyboardNavigation.SetCurrentIndex(selectedVisibleIndex); + } +} + +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Fields/UIEditorPropertyGridInteractionInternal.h b/new_editor/src/Fields/PropertyGridInteractionInternal.h similarity index 95% rename from new_editor/src/Fields/UIEditorPropertyGridInteractionInternal.h rename to new_editor/src/Fields/PropertyGridInteractionInternal.h index e978faa0..ce224175 100644 --- a/new_editor/src/Fields/UIEditorPropertyGridInteractionInternal.h +++ b/new_editor/src/Fields/PropertyGridInteractionInternal.h @@ -1,12 +1,12 @@ #pragma once -#include "Fields/UIEditorPropertyGridInternal.h" - +#include "Fields/PropertyGridInternal.h" +#include #include -namespace XCEngine::UI::Editor::Detail { +namespace XCEngine::UI::Editor::Internal { -bool ShouldUsePointerPosition(const ::XCEngine::UI::UIInputEvent& event); +bool ShouldUsePropertyGridPointerPosition(const ::XCEngine::UI::UIInputEvent& event); bool HasSystemModifiers(const ::XCEngine::UI::UIInputModifiers& modifiers); bool ApplyKeyboardNavigation( UIEditorPropertyGridInteractionState& state, @@ -128,4 +128,4 @@ bool ProcessColorFieldEvent( const ::XCEngine::UI::UIInputEvent& event, UIEditorPropertyGridInteractionResult& result); -} // namespace XCEngine::UI::Editor::Detail +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Fields/PropertyGridInteractionPopup.cpp b/new_editor/src/Fields/PropertyGridInteractionPopup.cpp new file mode 100644 index 00000000..d315af9b --- /dev/null +++ b/new_editor/src/Fields/PropertyGridInteractionPopup.cpp @@ -0,0 +1,229 @@ +#include "Fields/PropertyGridInteractionInternal.h" +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::Internal { + +using ::XCEngine::UI::UISize; +using ::XCEngine::UI::Widgets::ResolvePopupPlacementRect; +using ::XCEngine::UI::Widgets::UIPopupPlacement; +using Widgets::FindUIEditorPropertyGridVisibleFieldIndex; +using Widgets::UIEditorMenuPopupHitTarget; +using Widgets::UIEditorMenuPopupItem; +using Widgets::UIEditorMenuPopupLayout; +using Widgets::UIEditorPropertyGridField; +using Widgets::UIEditorPropertyGridFieldKind; +using Widgets::UIEditorPropertyGridInvalidIndex; +using Widgets::UIEditorPropertyGridLayout; +using Widgets::UIEditorPropertyGridSection; + +std::vector BuildPopupItems( + const UIEditorPropertyGridField& field) { + std::vector items = {}; + if (field.kind != UIEditorPropertyGridFieldKind::Enum) { + return items; + } + + items.reserve(field.enumValue.options.size()); + const std::size_t selectedIndex = + field.enumValue.options.empty() + ? 0u + : (std::min)( + field.enumValue.selectedIndex, + field.enumValue.options.size() - 1u); + for (std::size_t index = 0u; index < field.enumValue.options.size(); ++index) { + UIEditorMenuPopupItem item = {}; + item.itemId = field.fieldId + "." + std::to_string(index); + item.kind = UIEditorMenuItemKind::Command; + item.label = field.enumValue.options[index]; + item.enabled = !field.readOnly; + item.checked = index == selectedIndex; + items.push_back(std::move(item)); + } + + return items; +} + +::XCEngine::UI::UIRect ResolvePopupViewportRect( + const ::XCEngine::UI::UIRect& bounds) { + return ::XCEngine::UI::UIRect( + bounds.x - 4096.0f, + bounds.y - 4096.0f, + 8192.0f, + 8192.0f); +} + +bool BuildPopupLayout( + const UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const Widgets::UIEditorMenuPopupMetrics& popupMetrics, + UIEditorMenuPopupLayout& popupLayout, + std::vector& popupItems) { + if (state.propertyGridState.popupFieldId.empty()) { + return false; + } + + const std::size_t visibleFieldIndex = + FindUIEditorPropertyGridVisibleFieldIndex( + layout, + state.propertyGridState.popupFieldId, + sections); + if (visibleFieldIndex == UIEditorPropertyGridInvalidIndex || + visibleFieldIndex >= layout.visibleFieldSectionIndices.size() || + visibleFieldIndex >= layout.visibleFieldIndices.size()) { + return false; + } + + const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; + const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; + if (sectionIndex >= sections.size() || + fieldIndex >= sections[sectionIndex].fields.size()) { + return false; + } + + const UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; + popupItems = BuildPopupItems(field); + if (popupItems.empty()) { + return false; + } + + const float popupWidth = (std::max)( + layout.fieldValueRects[visibleFieldIndex].width, + Widgets::ResolveUIEditorMenuPopupDesiredWidth(popupItems, popupMetrics)); + const float popupHeight = + Widgets::MeasureUIEditorMenuPopupHeight(popupItems, popupMetrics); + const auto placement = ResolvePopupPlacementRect( + layout.fieldValueRects[visibleFieldIndex], + UISize(popupWidth, popupHeight), + ResolvePopupViewportRect(layout.bounds), + UIPopupPlacement::BottomStart); + popupLayout = + Widgets::BuildUIEditorMenuPopupLayout(placement.rect, popupItems, popupMetrics); + return true; +} + +UIEditorMenuPopupHitTarget ResolvePopupHit( + const UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections, + const Widgets::UIEditorMenuPopupMetrics& popupMetrics) { + if (!state.hasPointerPosition) { + return {}; + } + + UIEditorMenuPopupLayout popupLayout = {}; + std::vector popupItems = {}; + if (!BuildPopupLayout(state, layout, sections, popupMetrics, popupLayout, popupItems)) { + return {}; + } + + return Widgets::HitTestUIEditorMenuPopup( + popupLayout, + popupItems, + state.pointerPosition); +} + +void ClosePopup( + UIEditorPropertyGridInteractionState& state, + UIEditorPropertyGridInteractionResult& result) { + if (state.propertyGridState.popupFieldId.empty()) { + return; + } + + ClearPopupState(state); + result.popupClosed = true; + result.consumed = true; +} + +void OpenPopup( + UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridField& field, + UIEditorPropertyGridInteractionResult& result) { + if (field.kind != UIEditorPropertyGridFieldKind::Enum || + field.readOnly || + field.enumValue.options.empty()) { + return; + } + + if (state.propertyGridState.popupFieldId == field.fieldId) { + return; + } + + state.propertyGridState.popupFieldId = field.fieldId; + state.propertyGridState.popupHighlightedIndex = + (std::min)(field.enumValue.selectedIndex, field.enumValue.options.size() - 1u); + result.popupOpened = true; + result.consumed = true; +} + +void MovePopupHighlight( + UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridField& field, + int delta) { + if (field.kind != UIEditorPropertyGridFieldKind::Enum || + field.enumValue.options.empty()) { + state.propertyGridState.popupHighlightedIndex = + UIEditorPropertyGridInvalidIndex; + return; + } + + if (state.propertyGridState.popupHighlightedIndex == UIEditorPropertyGridInvalidIndex || + state.propertyGridState.popupHighlightedIndex >= field.enumValue.options.size()) { + state.propertyGridState.popupHighlightedIndex = + (std::min)(field.enumValue.selectedIndex, field.enumValue.options.size() - 1u); + } + + const std::size_t currentIndex = state.propertyGridState.popupHighlightedIndex; + if (delta < 0) { + state.propertyGridState.popupHighlightedIndex = + currentIndex == 0u ? 0u : currentIndex - 1u; + } else { + state.propertyGridState.popupHighlightedIndex = + currentIndex + 1u >= field.enumValue.options.size() + ? field.enumValue.options.size() - 1u + : currentIndex + 1u; + } +} + +void JumpPopupHighlightToEdge( + UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridField& field, + bool toEnd) { + if (field.kind != UIEditorPropertyGridFieldKind::Enum || + field.enumValue.options.empty()) { + state.propertyGridState.popupHighlightedIndex = + UIEditorPropertyGridInvalidIndex; + return; + } + + state.propertyGridState.popupHighlightedIndex = + toEnd ? field.enumValue.options.size() - 1u : 0u; +} + +bool CommitPopupSelection( + UIEditorPropertyGridInteractionState& state, + std::vector& sections, + UIEditorPropertyGridInteractionResult& result) { + UIEditorPropertyGridField* field = + FindMutableField(sections, state.propertyGridState.popupFieldId); + if (field == nullptr || + field->kind != UIEditorPropertyGridFieldKind::Enum || + field->readOnly || + field->enumValue.options.empty() || + state.propertyGridState.popupHighlightedIndex == + UIEditorPropertyGridInvalidIndex || + state.propertyGridState.popupHighlightedIndex >= + field->enumValue.options.size()) { + return false; + } + + field->enumValue.selectedIndex = state.propertyGridState.popupHighlightedIndex; + SetChangedValueResult(*field, result); + ClosePopup(state, result); + return true; +} + +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Fields/UIEditorPropertyGridInternal.h b/new_editor/src/Fields/PropertyGridInternal.h similarity index 97% rename from new_editor/src/Fields/UIEditorPropertyGridInternal.h rename to new_editor/src/Fields/PropertyGridInternal.h index 90b11d4b..f05b5827 100644 --- a/new_editor/src/Fields/UIEditorPropertyGridInternal.h +++ b/new_editor/src/Fields/PropertyGridInternal.h @@ -10,7 +10,7 @@ #include #include -namespace XCEngine::UI::Editor::Widgets::Detail { +namespace XCEngine::UI::Editor::Widgets::Internal { float ClampNonNegative(float value); float ResolveSectionHeaderHeight( @@ -92,4 +92,4 @@ std::string FormatColorValueText(const UIEditorPropertyGridField& field); std::string FormatVector3ValueText(const UIEditorPropertyGridField& field); std::string FormatVector4ValueText(const UIEditorPropertyGridField& field); -} // namespace XCEngine::UI::Editor::Widgets::Detail +} // namespace XCEngine::UI::Editor::Widgets::Internal diff --git a/new_editor/src/Fields/UIEditorPropertyGridRendering.cpp b/new_editor/src/Fields/PropertyGridRendering.cpp similarity index 95% rename from new_editor/src/Fields/UIEditorPropertyGridRendering.cpp rename to new_editor/src/Fields/PropertyGridRendering.cpp index 8246c736..71a51a33 100644 --- a/new_editor/src/Fields/UIEditorPropertyGridRendering.cpp +++ b/new_editor/src/Fields/PropertyGridRendering.cpp @@ -1,5 +1,4 @@ -#include "Fields/UIEditorPropertyGridInternal.h" - +#include "Fields/PropertyGridInternal.h" #include #include #include @@ -9,7 +8,7 @@ #include #include -namespace XCEngine::UI::Editor::Widgets::Detail { +namespace XCEngine::UI::Editor::Widgets::Internal { using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIRect; @@ -438,7 +437,7 @@ std::string FormatVector4ValueText(const UIEditorPropertyGridField& field) { FormatUIEditorVector4FieldComponentValue(spec, 3u); } -} // namespace XCEngine::UI::Editor::Widgets::Detail +} // namespace XCEngine::UI::Editor::Widgets::Internal namespace XCEngine::UI::Editor::Widgets { @@ -511,7 +510,7 @@ void AppendUIEditorPropertyGridForeground( const UIEditorPropertyGridSection& section = sections[layout.sectionIndices[sectionVisibleIndex]]; drawList.AddText( - Detail::ResolveDisclosureGlyphPosition( + Internal::ResolveDisclosureGlyphPosition( layout.sectionDisclosureRects[sectionVisibleIndex], metrics), layout.sectionExpanded[sectionVisibleIndex] ? "v" : ">", @@ -559,7 +558,7 @@ void AppendUIEditorPropertyGridForeground( const UIEditorColorFieldPalette colorPalette = ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldPalette(palette); const ::XCEngine::UI::UIRect popupViewportRect = - Detail::ResolvePopupViewportRect(layout.bounds); + Internal::ResolvePopupViewportRect(layout.bounds); for (std::size_t visibleFieldIndex = 0u; visibleFieldIndex < layout.fieldRowRects.size(); @@ -574,13 +573,13 @@ void AppendUIEditorPropertyGridForeground( switch (field.kind) { case UIEditorPropertyGridFieldKind::Bool: { UIEditorBoolFieldState fieldState = {}; - fieldState.hoveredTarget = Detail::ResolveBoolHoveredTarget(state, field); + fieldState.hoveredTarget = Internal::ResolveBoolHoveredTarget(state, field); fieldState.focused = state.focused; fieldState.active = state.pressedFieldId == field.fieldId; AppendUIEditorBoolField( drawList, layout.fieldRowRects[visibleFieldIndex], - Detail::BuildBoolFieldSpec(field), + Internal::BuildBoolFieldSpec(field), fieldState, boolPalette, boolMetrics); @@ -589,7 +588,7 @@ void AppendUIEditorPropertyGridForeground( case UIEditorPropertyGridFieldKind::Number: { UIEditorNumberFieldState fieldState = {}; - fieldState.hoveredTarget = Detail::ResolveNumberHoveredTarget(state, field); + fieldState.hoveredTarget = Internal::ResolveNumberHoveredTarget(state, field); fieldState.activeTarget = state.pressedFieldId == field.fieldId ? UIEditorNumberFieldHitTargetKind::ValueBox @@ -602,7 +601,7 @@ void AppendUIEditorPropertyGridForeground( AppendUIEditorNumberField( drawList, layout.fieldRowRects[visibleFieldIndex], - Detail::BuildNumberFieldSpec(field), + Internal::BuildNumberFieldSpec(field), fieldState, numberPalette, numberMetrics); @@ -611,14 +610,14 @@ void AppendUIEditorPropertyGridForeground( case UIEditorPropertyGridFieldKind::Enum: { UIEditorEnumFieldState fieldState = {}; - fieldState.hoveredTarget = Detail::ResolveEnumHoveredTarget(state, field); + fieldState.hoveredTarget = Internal::ResolveEnumHoveredTarget(state, field); fieldState.focused = state.focused; fieldState.active = state.pressedFieldId == field.fieldId; fieldState.popupOpen = state.popupFieldId == field.fieldId; AppendUIEditorEnumField( drawList, layout.fieldRowRects[visibleFieldIndex], - Detail::BuildEnumFieldSpec(field), + Internal::BuildEnumFieldSpec(field), fieldState, enumPalette, enumMetrics); @@ -628,18 +627,18 @@ void AppendUIEditorPropertyGridForeground( case UIEditorPropertyGridFieldKind::Color: { UIEditorColorFieldState fieldState = {}; if (const auto* visualState = - Detail::FindColorFieldVisualState(state, field.fieldId); + Internal::FindColorFieldVisualState(state, field.fieldId); visualState != nullptr) { fieldState = visualState->state; } else { - fieldState.hoveredTarget = Detail::ResolveColorHoveredTarget(state, field); + fieldState.hoveredTarget = Internal::ResolveColorHoveredTarget(state, field); fieldState.focused = state.focused; } AppendUIEditorColorField( drawList, layout.fieldRowRects[visibleFieldIndex], - Detail::BuildColorFieldSpec(field), + Internal::BuildColorFieldSpec(field), fieldState, colorPalette, colorMetrics, @@ -649,7 +648,7 @@ void AppendUIEditorPropertyGridForeground( case UIEditorPropertyGridFieldKind::Vector2: { UIEditorVector2FieldState fieldState = {}; - fieldState.hoveredTarget = Detail::ResolveVector2HoveredTarget(state, field); + fieldState.hoveredTarget = Internal::ResolveVector2HoveredTarget(state, field); fieldState.activeTarget = state.pressedFieldId == field.fieldId ? UIEditorVector2FieldHitTargetKind::Row @@ -658,7 +657,7 @@ void AppendUIEditorPropertyGridForeground( AppendUIEditorVector2Field( drawList, layout.fieldRowRects[visibleFieldIndex], - Detail::BuildVector2FieldSpec(field), + Internal::BuildVector2FieldSpec(field), fieldState, vector2Palette, vector2Metrics); @@ -667,7 +666,7 @@ void AppendUIEditorPropertyGridForeground( case UIEditorPropertyGridFieldKind::Vector3: { UIEditorVector3FieldState fieldState = {}; - fieldState.hoveredTarget = Detail::ResolveVector3HoveredTarget(state, field); + fieldState.hoveredTarget = Internal::ResolveVector3HoveredTarget(state, field); fieldState.activeTarget = state.pressedFieldId == field.fieldId ? UIEditorVector3FieldHitTargetKind::Row @@ -676,7 +675,7 @@ void AppendUIEditorPropertyGridForeground( AppendUIEditorVector3Field( drawList, layout.fieldRowRects[visibleFieldIndex], - Detail::BuildVector3FieldSpec(field), + Internal::BuildVector3FieldSpec(field), fieldState, vector3Palette, vector3Metrics); @@ -685,7 +684,7 @@ void AppendUIEditorPropertyGridForeground( case UIEditorPropertyGridFieldKind::Vector4: { UIEditorVector4FieldState fieldState = {}; - fieldState.hoveredTarget = Detail::ResolveVector4HoveredTarget(state, field); + fieldState.hoveredTarget = Internal::ResolveVector4HoveredTarget(state, field); fieldState.activeTarget = state.pressedFieldId == field.fieldId ? UIEditorVector4FieldHitTargetKind::Row @@ -694,7 +693,7 @@ void AppendUIEditorPropertyGridForeground( AppendUIEditorVector4Field( drawList, layout.fieldRowRects[visibleFieldIndex], - Detail::BuildVector4FieldSpec(field), + Internal::BuildVector4FieldSpec(field), fieldState, vector4Palette, vector4Metrics); @@ -704,9 +703,9 @@ void AppendUIEditorPropertyGridForeground( case UIEditorPropertyGridFieldKind::Text: default: { const std::string& displayedValue = - Detail::ResolveDisplayedTextFieldValue(field, propertyEditModel); + Internal::ResolveDisplayedTextFieldValue(field, propertyEditModel); UIEditorTextFieldState fieldState = {}; - fieldState.hoveredTarget = Detail::ResolveTextHoveredTarget(state, field); + fieldState.hoveredTarget = Internal::ResolveTextHoveredTarget(state, field); fieldState.activeTarget = state.pressedFieldId == field.fieldId ? (state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox @@ -717,7 +716,7 @@ void AppendUIEditorPropertyGridForeground( fieldState.editing = editing; fieldState.displayText = displayedValue; - UIEditorTextFieldSpec fieldSpec = Detail::BuildTextFieldSpec(field); + UIEditorTextFieldSpec fieldSpec = Internal::BuildTextFieldSpec(field); fieldSpec.value = editing ? std::string() : displayedValue; AppendUIEditorTextField( drawList, @@ -757,7 +756,7 @@ void AppendUIEditorPropertyGridForeground( UIEditorMenuPopupLayout popupLayout = {}; UIEditorMenuPopupState popupState = {}; std::vector popupItems = {}; - if (Detail::BuildEnumPopupRuntime( + if (Internal::BuildEnumPopupRuntime( layout, sections, state, diff --git a/new_editor/src/Fields/UIEditorColorField.cpp b/new_editor/src/Fields/UIEditorColorField.cpp index a1c143d1..8703099a 100644 --- a/new_editor/src/Fields/UIEditorColorField.cpp +++ b/new_editor/src/Fields/UIEditorColorField.cpp @@ -1,4 +1,4 @@ -#include "Fields/UIEditorColorFieldInternal.h" +#include "Fields/ColorFieldInternal.h" #include #include @@ -93,7 +93,7 @@ UIEditorColorFieldLayout BuildUIEditorColorFieldLayout( const UIEditorColorFieldSpec& spec, const UIEditorColorFieldMetrics& metrics, const UIRect& viewportRect) { - const UIEditorColorFieldMetrics resolvedMetrics = Detail::ResolveMetrics(metrics); + const UIEditorColorFieldMetrics resolvedMetrics = Internal::ResolveMetrics(metrics); const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( bounds, resolvedMetrics.swatchWidth, @@ -114,7 +114,7 @@ UIEditorColorFieldLayout BuildUIEditorColorFieldLayout( hostLayout.controlRect.x, hostLayout.controlRect.y + resolvedMetrics.swatchInsetY, (std::min)(resolvedMetrics.swatchWidth, hostLayout.controlRect.width), - Detail::ClampNonNegative( + Internal::ClampColorFieldNonNegative( hostLayout.controlRect.height - resolvedMetrics.swatchInsetY * 2.0f)); const auto placement = ResolvePopupPlacementRect( @@ -133,7 +133,7 @@ UIEditorColorFieldLayout BuildUIEditorColorFieldLayout( layout.popupTitleRect = UIRect( layout.popupRect.x + resolvedMetrics.popupPadding, layout.popupRect.y, - Detail::ClampNonNegative( + Internal::ClampColorFieldNonNegative( layout.popupRect.width - resolvedMetrics.popupPadding * 3.0f - resolvedMetrics.popupCloseButtonSize), @@ -152,9 +152,9 @@ UIEditorColorFieldLayout BuildUIEditorColorFieldLayout( layout.popupRect.x + resolvedMetrics.popupPadding, layout.popupRect.y + resolvedMetrics.popupHeaderHeight + resolvedMetrics.popupPadding, - Detail::ClampNonNegative( + Internal::ClampColorFieldNonNegative( layout.popupRect.width - resolvedMetrics.popupPadding * 2.0f), - Detail::ClampNonNegative( + Internal::ClampColorFieldNonNegative( layout.popupRect.height - resolvedMetrics.popupHeaderHeight - resolvedMetrics.popupPadding * 2.0f)); layout.popupTopRowRect = UIRect( @@ -190,9 +190,9 @@ UIEditorColorFieldLayout BuildUIEditorColorFieldLayout( resolvedMetrics.saturationValueSize); const float controlsX = layout.popupBodyRect.x + resolvedMetrics.popupFieldInset; - const float controlsWidth = Detail::ClampNonNegative( + const float controlsWidth = Internal::ClampColorFieldNonNegative( layout.popupBodyRect.width - resolvedMetrics.popupFieldInset * 2.0f); - const float sliderWidth = Detail::ClampNonNegative( + const float sliderWidth = Internal::ClampColorFieldNonNegative( controlsWidth - resolvedMetrics.channelLabelWidth - resolvedMetrics.controlRowSpacing - @@ -245,7 +245,7 @@ UIEditorColorFieldLayout BuildUIEditorColorFieldLayout( layout.hexLabelRect.x + layout.hexLabelRect.width + resolvedMetrics.controlRowSpacing, rowY, - Detail::ClampNonNegative( + Internal::ClampColorFieldNonNegative( controlsWidth - resolvedMetrics.hexLabelWidth - resolvedMetrics.controlRowSpacing), resolvedMetrics.channelRowHeight); diff --git a/new_editor/src/Fields/UIEditorPropertyGrid.cpp b/new_editor/src/Fields/UIEditorPropertyGrid.cpp index 7b7d53ce..e3a52c7b 100644 --- a/new_editor/src/Fields/UIEditorPropertyGrid.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGrid.cpp @@ -1,4 +1,4 @@ -#include "Fields/UIEditorPropertyGridInternal.h" +#include "Fields/PropertyGridInternal.h" #include @@ -47,22 +47,22 @@ std::string ResolveUIEditorPropertyGridFieldValueText( return field.boolValue ? "true" : "false"; case UIEditorPropertyGridFieldKind::Number: - return FormatUIEditorNumberFieldValue(Detail::BuildNumberFieldSpec(field)); + return FormatUIEditorNumberFieldValue(Internal::BuildNumberFieldSpec(field)); case UIEditorPropertyGridFieldKind::Enum: - return ResolveUIEditorEnumFieldValueText(Detail::BuildEnumFieldSpec(field)); + return ResolveUIEditorEnumFieldValueText(Internal::BuildEnumFieldSpec(field)); case UIEditorPropertyGridFieldKind::Color: - return Detail::FormatColorValueText(field); + return Internal::FormatColorValueText(field); case UIEditorPropertyGridFieldKind::Vector2: - return Detail::FormatVector2ValueText(field); + return Internal::FormatVector2ValueText(field); case UIEditorPropertyGridFieldKind::Vector3: - return Detail::FormatVector3ValueText(field); + return Internal::FormatVector3ValueText(field); case UIEditorPropertyGridFieldKind::Vector4: - return Detail::FormatVector4ValueText(field); + return Internal::FormatVector4ValueText(field); case UIEditorPropertyGridFieldKind::Text: default: @@ -101,10 +101,10 @@ UIEditorPropertyGridLayout BuildUIEditorPropertyGridLayout( layout.bounds = ::XCEngine::UI::UIRect( bounds.x, bounds.y, - Detail::ClampNonNegative(bounds.width), - Detail::ClampNonNegative(bounds.height)); + Internal::ClampNonNegative(bounds.width), + Internal::ClampNonNegative(bounds.height)); - const float inset = Detail::ClampNonNegative(metrics.contentInset); + const float inset = Internal::ClampNonNegative(metrics.contentInset); const float contentX = layout.bounds.x + inset; const float contentY = layout.bounds.y + inset; const float contentWidth = @@ -114,7 +114,7 @@ UIEditorPropertyGridLayout BuildUIEditorPropertyGridLayout( for (std::size_t sectionIndex = 0u; sectionIndex < sections.size(); ++sectionIndex) { const UIEditorPropertyGridSection& section = sections[sectionIndex]; const float headerHeight = - Detail::ResolveSectionHeaderHeight(section, metrics); + Internal::ResolveSectionHeaderHeight(section, metrics); const bool expanded = expansionModel.IsExpanded(section.sectionId); const ::XCEngine::UI::UIRect headerRect( @@ -148,14 +148,14 @@ UIEditorPropertyGridLayout BuildUIEditorPropertyGridLayout( for (std::size_t fieldIndex = 0u; fieldIndex < section.fields.size(); ++fieldIndex) { const UIEditorPropertyGridField& field = section.fields[fieldIndex]; const float rowHeight = - Detail::ResolveFieldRowHeight(field, metrics); + Internal::ResolveFieldRowHeight(field, metrics); const ::XCEngine::UI::UIRect rowRect( contentX, cursorY, contentWidth, rowHeight); - const Detail::UIEditorPropertyGridFieldRects fieldRects = - Detail::ResolveFieldRects(rowRect, field, metrics); + const Internal::UIEditorPropertyGridFieldRects fieldRects = + Internal::ResolveFieldRects(rowRect, field, metrics); layout.visibleFieldSectionIndices.push_back(sectionIndex); layout.visibleFieldIndices.push_back(fieldIndex); diff --git a/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp index 9854bc56..0ba32a43 100644 --- a/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp @@ -1,4 +1,4 @@ -#include "Fields/UIEditorPropertyGridInteractionInternal.h" +#include "Fields/PropertyGridInteractionInternal.h" #include @@ -40,13 +40,13 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( const Widgets::UIEditorMenuPopupMetrics& popupMetrics) { UIEditorPropertyGridLayout layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); - Detail::PruneColorFieldVisualStates(state, layout, sections); - Detail::SyncKeyboardNavigation(state, selectionModel, layout, sections); - Detail::SyncHoverTarget(state, layout, sections, popupMetrics); + Internal::PruneColorFieldVisualStates(state, layout, sections); + Internal::SyncKeyboardNavigation(state, selectionModel, layout, sections); + Internal::SyncHoverTarget(state, layout, sections, popupMetrics); UIEditorPropertyGridInteractionResult interactionResult = {}; for (const UIInputEvent& event : inputEvents) { - if (Detail::ShouldUsePointerPosition(event)) { + if (Internal::ShouldUsePropertyGridPointerPosition(event)) { state.pointerPosition = event.position; state.hasPointerPosition = true; } else if (event.type == UIInputEventType::PointerLeave) { @@ -55,14 +55,14 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( UIEditorPropertyGridInteractionResult eventResult = {}; const UIEditorMenuPopupHitTarget popupHit = - Detail::ResolvePopupHit(state, layout, sections, popupMetrics); + Internal::ResolvePopupHit(state, layout, sections, popupMetrics); const UIEditorPropertyGridHitTarget hitTarget = state.hasPointerPosition ? HitTestUIEditorPropertyGrid(layout, state.pointerPosition) : UIEditorPropertyGridHitTarget {}; eventResult.hitTarget = hitTarget; - if (Detail::ProcessColorFieldEvent( + if (Internal::ProcessColorFieldEvent( state, selectionModel, propertyEditModel, @@ -71,11 +71,11 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( metrics, event, eventResult)) { - Detail::MergeInteractionResult(interactionResult, eventResult); + Internal::MergeInteractionResult(interactionResult, eventResult); layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); - Detail::PruneColorFieldVisualStates(state, layout, sections); - Detail::SyncKeyboardNavigation(state, selectionModel, layout, sections); - Detail::SyncHoverTarget(state, layout, sections, popupMetrics); + Internal::PruneColorFieldVisualStates(state, layout, sections); + Internal::SyncKeyboardNavigation(state, selectionModel, layout, sections); + Internal::SyncHoverTarget(state, layout, sections, popupMetrics); continue; } @@ -85,12 +85,12 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( break; case UIInputEventType::FocusLost: - Detail::CommitActiveEdit(state, propertyEditModel, sections, eventResult); - Detail::ClosePopup(state, eventResult); + Internal::CommitActiveEdit(state, propertyEditModel, sections, eventResult); + Internal::ClosePopup(state, eventResult); state.propertyGridState.focused = false; state.propertyGridState.pressedFieldId.clear(); state.hasPointerPosition = false; - Detail::ClearHoverState(state); + Internal::ClearHoverState(state); break; case UIInputEventType::PointerMove: @@ -144,9 +144,9 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item && popupHit.index == state.pressedPopupIndex) { eventResult.fieldValueChanged = - Detail::CommitPopupSelection(state, sections, eventResult); + Internal::CommitPopupSelection(state, sections, eventResult); } else if (popupHit.kind == UIEditorMenuPopupHitTargetKind::None) { - Detail::ClosePopup(state, eventResult); + Internal::ClosePopup(state, eventResult); } state.pressedPopupIndex = UIEditorPropertyGridInvalidIndex; state.propertyGridState.pressedFieldId.clear(); @@ -161,8 +161,8 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::None) { - Detail::CommitActiveEdit(state, propertyEditModel, sections, eventResult); - Detail::ClosePopup(state, eventResult); + Internal::CommitActiveEdit(state, propertyEditModel, sections, eventResult); + Internal::ClosePopup(state, eventResult); state.propertyGridState.focused = insideGrid; state.propertyGridState.pressedFieldId.clear(); if (insideGrid) { @@ -173,8 +173,8 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::SectionHeader && hitTarget.sectionIndex < sections.size()) { - Detail::CommitActiveEdit(state, propertyEditModel, sections, eventResult); - Detail::ClosePopup(state, eventResult); + Internal::CommitActiveEdit(state, propertyEditModel, sections, eventResult); + Internal::ClosePopup(state, eventResult); const std::string& sectionId = sections[hitTarget.sectionIndex].sectionId; eventResult.sectionToggled = @@ -193,7 +193,7 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( UIEditorPropertyGridField& field = sections[hitTarget.sectionIndex].fields[hitTarget.fieldIndex]; state.propertyGridState.focused = true; - Detail::SelectVisibleField( + Internal::SelectVisibleField( state, selectionModel, layout, @@ -203,36 +203,36 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::ValueBox) { if (field.kind == UIEditorPropertyGridFieldKind::Bool) { - Detail::CommitActiveEdit( + Internal::CommitActiveEdit( state, propertyEditModel, sections, eventResult); - Detail::ClosePopup(state, eventResult); - Detail::ToggleBoolField(field, eventResult); + Internal::ClosePopup(state, eventResult); + Internal::ToggleBoolField(field, eventResult); } else if (field.kind == UIEditorPropertyGridFieldKind::Enum) { - Detail::CommitActiveEdit( + Internal::CommitActiveEdit( state, propertyEditModel, sections, eventResult); if (state.propertyGridState.popupFieldId == field.fieldId) { - Detail::ClosePopup(state, eventResult); + Internal::ClosePopup(state, eventResult); } else { - Detail::ClearPopupState(state); - Detail::OpenPopup(state, field, eventResult); + Internal::ClearPopupState(state); + Internal::OpenPopup(state, field, eventResult); } } else { - Detail::ClosePopup(state, eventResult); + Internal::ClosePopup(state, eventResult); if (!(propertyEditModel.HasActiveEdit() && propertyEditModel.GetActiveFieldId() == field.fieldId)) { - Detail::CommitActiveEdit( + Internal::CommitActiveEdit( state, propertyEditModel, sections, eventResult); - Detail::BeginFieldEdit( + Internal::BeginFieldEdit( state, propertyEditModel, field, @@ -240,7 +240,7 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } } } else if (state.propertyGridState.popupFieldId != field.fieldId) { - Detail::ClosePopup(state, eventResult); + Internal::ClosePopup(state, eventResult); } } @@ -275,28 +275,28 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } if (!state.propertyGridState.popupFieldId.empty()) { - const UIEditorPropertyGridField* popupField = Detail::FindField( + const UIEditorPropertyGridField* popupField = Internal::FindField( sections, state.propertyGridState.popupFieldId); if (popupField != nullptr) { switch (static_cast(event.keyCode)) { case KeyCode::Up: - Detail::MovePopupHighlight(state, *popupField, -1); + Internal::MovePopupHighlight(state, *popupField, -1); eventResult.consumed = true; break; case KeyCode::Down: - Detail::MovePopupHighlight(state, *popupField, 1); + Internal::MovePopupHighlight(state, *popupField, 1); eventResult.consumed = true; break; case KeyCode::Home: - Detail::JumpPopupHighlightToEdge( + Internal::JumpPopupHighlightToEdge( state, *popupField, false); eventResult.consumed = true; break; case KeyCode::End: - Detail::JumpPopupHighlightToEdge( + Internal::JumpPopupHighlightToEdge( state, *popupField, true); @@ -304,11 +304,11 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( break; case KeyCode::Enter: case KeyCode::Space: - Detail::CommitPopupSelection(state, sections, eventResult); + Internal::CommitPopupSelection(state, sections, eventResult); eventResult.consumed = true; break; case KeyCode::Escape: - Detail::ClosePopup(state, eventResult); + Internal::ClosePopup(state, eventResult); break; default: break; @@ -319,7 +319,7 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( if (propertyEditModel.HasActiveEdit()) { if (static_cast(event.keyCode) == KeyCode::Escape) { - Detail::CancelActiveEdit(state, propertyEditModel, eventResult); + Internal::CancelActiveEdit(state, propertyEditModel, eventResult); break; } @@ -336,7 +336,7 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( eventResult.editValueChanged = true; } if (editResult.submitRequested) { - Detail::CommitActiveEdit( + Internal::CommitActiveEdit( state, propertyEditModel, sections, @@ -349,18 +349,18 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( if ((static_cast(event.keyCode) == KeyCode::Enter || static_cast(event.keyCode) == KeyCode::Space) && selectionModel.HasSelection()) { - UIEditorPropertyGridField* field = Detail::FindMutableField( + UIEditorPropertyGridField* field = Internal::FindMutableField( sections, selectionModel.GetSelectedId()); if (field != nullptr) { if (field->kind == UIEditorPropertyGridFieldKind::Bool) { - Detail::ToggleBoolField(*field, eventResult); + Internal::ToggleBoolField(*field, eventResult); } else if (field->kind == UIEditorPropertyGridFieldKind::Enum) { - Detail::OpenPopup(state, *field, eventResult); + Internal::OpenPopup(state, *field, eventResult); } else if (static_cast(event.keyCode) == KeyCode::Enter) { - Detail::BeginFieldEdit( + Internal::BeginFieldEdit( state, propertyEditModel, *field, @@ -371,12 +371,12 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } if (!event.modifiers.shift && - !Detail::HasSystemModifiers(event.modifiers) && - Detail::ApplyKeyboardNavigation(state, event.keyCode) && + !Internal::HasSystemModifiers(event.modifiers) && + Internal::ApplyKeyboardNavigation(state, event.keyCode) && state.keyboardNavigation.HasCurrentIndex()) { const std::size_t currentIndex = state.keyboardNavigation.GetCurrentIndex(); - if (Detail::SelectVisibleField( + if (Internal::SelectVisibleField( state, selectionModel, layout, @@ -391,15 +391,15 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( case UIInputEventType::Character: if (!state.propertyGridState.focused || !propertyEditModel.HasActiveEdit() || - Detail::HasSystemModifiers(event.modifiers)) { + Internal::HasSystemModifiers(event.modifiers)) { break; } - if (const UIEditorPropertyGridField* field = Detail::FindField( + if (const UIEditorPropertyGridField* field = Internal::FindField( sections, propertyEditModel.GetActiveFieldId()); field == nullptr || - !Detail::IsNumberEditCharacter(*field, event.character)) { + !Internal::IsNumberEditCharacter(*field, event.character)) { break; } @@ -416,9 +416,9 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); - Detail::PruneColorFieldVisualStates(state, layout, sections); - Detail::SyncKeyboardNavigation(state, selectionModel, layout, sections); - Detail::SyncHoverTarget(state, layout, sections, popupMetrics); + Internal::PruneColorFieldVisualStates(state, layout, sections); + Internal::SyncKeyboardNavigation(state, selectionModel, layout, sections); + Internal::SyncHoverTarget(state, layout, sections, popupMetrics); if (eventResult.hitTarget.kind == UIEditorPropertyGridHitTargetKind::None && state.hasPointerPosition) { eventResult.hitTarget = @@ -449,9 +449,9 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); - Detail::PruneColorFieldVisualStates(state, layout, sections); - Detail::SyncKeyboardNavigation(state, selectionModel, layout, sections); - Detail::SyncHoverTarget(state, layout, sections, popupMetrics); + Internal::PruneColorFieldVisualStates(state, layout, sections); + Internal::SyncKeyboardNavigation(state, selectionModel, layout, sections); + Internal::SyncHoverTarget(state, layout, sections, popupMetrics); if (interactionResult.hitTarget.kind == UIEditorPropertyGridHitTargetKind::None && state.hasPointerPosition) { interactionResult.hitTarget = diff --git a/new_editor/src/Fields/UIEditorPropertyGridInteractionHelpers.cpp b/new_editor/src/Fields/UIEditorPropertyGridInteractionHelpers.cpp deleted file mode 100644 index 4432ed98..00000000 --- a/new_editor/src/Fields/UIEditorPropertyGridInteractionHelpers.cpp +++ /dev/null @@ -1,823 +0,0 @@ -#include "Fields/UIEditorPropertyGridInteractionInternal.h" - -#include -#include -#include - -#include -#include - -#include -#include - -namespace XCEngine::UI::Editor::Detail { - -using ::XCEngine::Input::KeyCode; -using ::XCEngine::UI::Text::HandleKeyDown; -using ::XCEngine::UI::Text::InsertCharacter; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPointerButton; -using ::XCEngine::UI::UISize; -using ::XCEngine::UI::Widgets::ResolvePopupPlacementRect; -using ::XCEngine::UI::Widgets::UIPopupPlacement; -using Widgets::BuildUIEditorPropertyGridLayout; -using Widgets::FindUIEditorPropertyGridFieldLocation; -using Widgets::FindUIEditorPropertyGridVisibleFieldIndex; -using Widgets::HitTestUIEditorPropertyGrid; -using Widgets::IsUIEditorPropertyGridPointInside; -using Widgets::ResolveUIEditorPropertyGridFieldValueText; -using Widgets::UIEditorColorFieldHitTargetKind; -using Widgets::UIEditorColorFieldSpec; -using Widgets::UIEditorMenuPopupHitTarget; -using Widgets::UIEditorMenuPopupHitTargetKind; -using Widgets::UIEditorMenuPopupInvalidIndex; -using Widgets::UIEditorMenuPopupItem; -using Widgets::UIEditorMenuPopupLayout; -using Widgets::UIEditorPropertyGridColorFieldVisualState; -using Widgets::UIEditorPropertyGridField; -using Widgets::UIEditorPropertyGridFieldKind; -using Widgets::UIEditorPropertyGridHitTarget; -using Widgets::UIEditorPropertyGridHitTargetKind; -using Widgets::UIEditorPropertyGridInvalidIndex; -using Widgets::UIEditorPropertyGridLayout; -using Widgets::UIEditorPropertyGridSection; - -bool ShouldUsePointerPosition(const UIInputEvent& event) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - return true; - default: - return false; - } -} - -bool HasSystemModifiers(const ::XCEngine::UI::UIInputModifiers& modifiers) { - return modifiers.control || modifiers.alt || modifiers.super; -} - -bool ApplyKeyboardNavigation( - UIEditorPropertyGridInteractionState& state, - std::int32_t keyCode) { - switch (static_cast(keyCode)) { - case KeyCode::Up: - return state.keyboardNavigation.MovePrevious(); - case KeyCode::Down: - return state.keyboardNavigation.MoveNext(); - case KeyCode::Home: - return state.keyboardNavigation.MoveHome(); - case KeyCode::End: - return state.keyboardNavigation.MoveEnd(); - default: - return false; - } -} - -void ClearHoverState(UIEditorPropertyGridInteractionState& state) { - state.propertyGridState.hoveredSectionId.clear(); - state.propertyGridState.hoveredFieldId.clear(); - state.propertyGridState.hoveredHitTarget = UIEditorPropertyGridHitTargetKind::None; -} - -void ClearPopupState(UIEditorPropertyGridInteractionState& state) { - state.propertyGridState.popupFieldId.clear(); - state.propertyGridState.popupHighlightedIndex = UIEditorPropertyGridInvalidIndex; - state.pressedPopupIndex = UIEditorPropertyGridInvalidIndex; -} - -bool IsInlineEditable(const UIEditorPropertyGridField& field) { - return field.kind == UIEditorPropertyGridFieldKind::Text || - field.kind == UIEditorPropertyGridFieldKind::Number; -} - -bool IsNumberEditCharacter( - const UIEditorPropertyGridField& field, - std::uint32_t character) { - if (field.kind != UIEditorPropertyGridFieldKind::Number) { - return true; - } - - if (character >= static_cast('0') && - character <= static_cast('9')) { - return true; - } - - if (character == static_cast('-') || - character == static_cast('+')) { - return true; - } - - return !field.numberValue.integerMode && - character == static_cast('.'); -} - -UIEditorPropertyGridField* FindMutableField( - std::vector& sections, - std::string_view fieldId) { - const auto location = FindUIEditorPropertyGridFieldLocation(sections, fieldId); - if (!location.IsValid() || - location.sectionIndex >= sections.size() || - location.fieldIndex >= sections[location.sectionIndex].fields.size()) { - return nullptr; - } - - return §ions[location.sectionIndex].fields[location.fieldIndex]; -} - -const UIEditorPropertyGridField* FindField( - const std::vector& sections, - std::string_view fieldId) { - const auto location = FindUIEditorPropertyGridFieldLocation(sections, fieldId); - if (!location.IsValid() || - location.sectionIndex >= sections.size() || - location.fieldIndex >= sections[location.sectionIndex].fields.size()) { - return nullptr; - } - - return §ions[location.sectionIndex].fields[location.fieldIndex]; -} - -void SetChangedValueResult( - const UIEditorPropertyGridField& field, - UIEditorPropertyGridInteractionResult& result) { - result.fieldValueChanged = true; - result.changedFieldId = field.fieldId; - result.changedValue = ResolveUIEditorPropertyGridFieldValueText(field); -} - -void MergeInteractionResult( - UIEditorPropertyGridInteractionResult& accumulated, - const UIEditorPropertyGridInteractionResult& current) { - accumulated.consumed = accumulated.consumed || current.consumed; - accumulated.sectionToggled = accumulated.sectionToggled || current.sectionToggled; - accumulated.selectionChanged = accumulated.selectionChanged || current.selectionChanged; - accumulated.keyboardNavigated = accumulated.keyboardNavigated || current.keyboardNavigated; - accumulated.editStarted = accumulated.editStarted || current.editStarted; - accumulated.editValueChanged = accumulated.editValueChanged || current.editValueChanged; - accumulated.editCommitted = accumulated.editCommitted || current.editCommitted; - accumulated.editCommitRejected = - accumulated.editCommitRejected || current.editCommitRejected; - accumulated.editCanceled = accumulated.editCanceled || current.editCanceled; - accumulated.popupOpened = accumulated.popupOpened || current.popupOpened; - accumulated.popupClosed = accumulated.popupClosed || current.popupClosed; - accumulated.fieldValueChanged = - accumulated.fieldValueChanged || current.fieldValueChanged; - accumulated.secondaryClicked = - accumulated.secondaryClicked || current.secondaryClicked; - - if (current.hitTarget.kind != UIEditorPropertyGridHitTargetKind::None) { - accumulated.hitTarget = current.hitTarget; - } - if (!current.toggledSectionId.empty()) { - accumulated.toggledSectionId = current.toggledSectionId; - } - if (!current.selectedFieldId.empty()) { - accumulated.selectedFieldId = current.selectedFieldId; - } - if (!current.activeFieldId.empty()) { - accumulated.activeFieldId = current.activeFieldId; - } - if (!current.committedFieldId.empty()) { - accumulated.committedFieldId = current.committedFieldId; - } - if (!current.committedValue.empty()) { - accumulated.committedValue = current.committedValue; - } - if (!current.changedFieldId.empty()) { - accumulated.changedFieldId = current.changedFieldId; - } - if (!current.changedValue.empty()) { - accumulated.changedValue = current.changedValue; - } -} - -UIEditorPropertyGridColorFieldVisualState* FindMutableColorFieldVisualState( - UIEditorPropertyGridInteractionState& state, - std::string_view fieldId) { - for (UIEditorPropertyGridColorFieldVisualState& entry : - state.propertyGridState.colorFieldStates) { - if (entry.fieldId == fieldId) { - return &entry; - } - } - - return nullptr; -} - -const UIEditorPropertyGridColorFieldVisualState* FindColorFieldVisualState( - const UIEditorPropertyGridInteractionState& state, - std::string_view fieldId) { - for (const UIEditorPropertyGridColorFieldVisualState& entry : - state.propertyGridState.colorFieldStates) { - if (entry.fieldId == fieldId) { - return &entry; - } - } - - return nullptr; -} - -UIEditorColorFieldInteractionState BuildColorFieldInteractionState( - const UIEditorPropertyGridInteractionState& state, - std::string_view fieldId) { - UIEditorColorFieldInteractionState interactionState = {}; - if (const UIEditorPropertyGridColorFieldVisualState* entry = - FindColorFieldVisualState(state, fieldId); - entry != nullptr) { - interactionState.colorFieldState = entry->state; - } - - interactionState.pointerPosition = state.pointerPosition; - interactionState.hasPointerPosition = state.hasPointerPosition; - return interactionState; -} - -void StoreColorFieldVisualState( - UIEditorPropertyGridInteractionState& state, - std::string_view fieldId, - const UIEditorColorFieldInteractionState& interactionState) { - if (UIEditorPropertyGridColorFieldVisualState* entry = - FindMutableColorFieldVisualState(state, fieldId); - entry != nullptr) { - entry->state = interactionState.colorFieldState; - return; - } - - UIEditorPropertyGridColorFieldVisualState entry = {}; - entry.fieldId = std::string(fieldId); - entry.state = interactionState.colorFieldState; - state.propertyGridState.colorFieldStates.push_back(std::move(entry)); -} - -void PruneColorFieldVisualStates( - UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections) { - const auto isVisibleColorFieldId = [&layout, §ions](std::string_view fieldId) { - const std::size_t visibleFieldIndex = - FindUIEditorPropertyGridVisibleFieldIndex(layout, fieldId, sections); - if (visibleFieldIndex == UIEditorPropertyGridInvalidIndex || - visibleFieldIndex >= layout.visibleFieldSectionIndices.size() || - visibleFieldIndex >= layout.visibleFieldIndices.size()) { - return false; - } - - const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; - const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; - return sectionIndex < sections.size() && - fieldIndex < sections[sectionIndex].fields.size() && - sections[sectionIndex].fields[fieldIndex].kind == - UIEditorPropertyGridFieldKind::Color; - }; - - state.propertyGridState.colorFieldStates.erase( - std::remove_if( - state.propertyGridState.colorFieldStates.begin(), - state.propertyGridState.colorFieldStates.end(), - [&isVisibleColorFieldId]( - const UIEditorPropertyGridColorFieldVisualState& entry) { - return !isVisibleColorFieldId(entry.fieldId); - }), - state.propertyGridState.colorFieldStates.end()); -} - -void CloseOtherColorFieldPopups( - UIEditorPropertyGridInteractionState& state, - std::string_view keepFieldId) { - for (UIEditorPropertyGridColorFieldVisualState& entry : - state.propertyGridState.colorFieldStates) { - if (entry.fieldId == keepFieldId) { - continue; - } - - entry.state.popupOpen = false; - entry.state.activeTarget = UIEditorColorFieldHitTargetKind::None; - } -} - -std::vector BuildPopupItems( - const UIEditorPropertyGridField& field) { - std::vector items = {}; - if (field.kind != UIEditorPropertyGridFieldKind::Enum) { - return items; - } - - items.reserve(field.enumValue.options.size()); - const std::size_t selectedIndex = - field.enumValue.options.empty() - ? 0u - : (std::min)( - field.enumValue.selectedIndex, - field.enumValue.options.size() - 1u); - for (std::size_t index = 0u; index < field.enumValue.options.size(); ++index) { - UIEditorMenuPopupItem item = {}; - item.itemId = field.fieldId + "." + std::to_string(index); - item.kind = UIEditorMenuItemKind::Command; - item.label = field.enumValue.options[index]; - item.enabled = !field.readOnly; - item.checked = index == selectedIndex; - items.push_back(std::move(item)); - } - - return items; -} - -::XCEngine::UI::UIRect ResolvePopupViewportRect( - const ::XCEngine::UI::UIRect& bounds) { - return ::XCEngine::UI::UIRect( - bounds.x - 4096.0f, - bounds.y - 4096.0f, - 8192.0f, - 8192.0f); -} - -bool BuildPopupLayout( - const UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections, - const Widgets::UIEditorMenuPopupMetrics& popupMetrics, - UIEditorMenuPopupLayout& popupLayout, - std::vector& popupItems) { - if (state.propertyGridState.popupFieldId.empty()) { - return false; - } - - const std::size_t visibleFieldIndex = - FindUIEditorPropertyGridVisibleFieldIndex( - layout, - state.propertyGridState.popupFieldId, - sections); - if (visibleFieldIndex == UIEditorPropertyGridInvalidIndex || - visibleFieldIndex >= layout.visibleFieldSectionIndices.size() || - visibleFieldIndex >= layout.visibleFieldIndices.size()) { - return false; - } - - const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; - const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; - if (sectionIndex >= sections.size() || - fieldIndex >= sections[sectionIndex].fields.size()) { - return false; - } - - const UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; - popupItems = BuildPopupItems(field); - if (popupItems.empty()) { - return false; - } - - const float popupWidth = (std::max)( - layout.fieldValueRects[visibleFieldIndex].width, - Widgets::ResolveUIEditorMenuPopupDesiredWidth(popupItems, popupMetrics)); - const float popupHeight = - Widgets::MeasureUIEditorMenuPopupHeight(popupItems, popupMetrics); - const auto placement = ResolvePopupPlacementRect( - layout.fieldValueRects[visibleFieldIndex], - UISize(popupWidth, popupHeight), - ResolvePopupViewportRect(layout.bounds), - UIPopupPlacement::BottomStart); - popupLayout = - Widgets::BuildUIEditorMenuPopupLayout(placement.rect, popupItems, popupMetrics); - return true; -} - -Widgets::UIEditorMenuPopupHitTarget ResolvePopupHit( - const UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections, - const Widgets::UIEditorMenuPopupMetrics& popupMetrics) { - if (!state.hasPointerPosition) { - return {}; - } - - UIEditorMenuPopupLayout popupLayout = {}; - std::vector popupItems = {}; - if (!BuildPopupLayout(state, layout, sections, popupMetrics, popupLayout, popupItems)) { - return {}; - } - - return Widgets::HitTestUIEditorMenuPopup( - popupLayout, - popupItems, - state.pointerPosition); -} - -void SyncHoverTarget( - UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections, - const Widgets::UIEditorMenuPopupMetrics& popupMetrics) { - ClearHoverState(state); - if (!state.hasPointerPosition) { - return; - } - - const UIEditorMenuPopupHitTarget popupHit = - ResolvePopupHit(state, layout, sections, popupMetrics); - if (popupHit.kind == UIEditorMenuPopupHitTargetKind::Item) { - state.propertyGridState.popupHighlightedIndex = popupHit.index; - return; - } - if (popupHit.kind != UIEditorMenuPopupHitTargetKind::None) { - return; - } - - const UIEditorPropertyGridHitTarget hitTarget = - HitTestUIEditorPropertyGrid(layout, state.pointerPosition); - state.propertyGridState.hoveredHitTarget = hitTarget.kind; - if (hitTarget.kind == UIEditorPropertyGridHitTargetKind::SectionHeader && - hitTarget.sectionIndex < sections.size()) { - state.propertyGridState.hoveredSectionId = - sections[hitTarget.sectionIndex].sectionId; - return; - } - - if ((hitTarget.kind == UIEditorPropertyGridHitTargetKind::FieldRow || - hitTarget.kind == UIEditorPropertyGridHitTargetKind::ValueBox) && - hitTarget.sectionIndex < sections.size() && - hitTarget.fieldIndex < sections[hitTarget.sectionIndex].fields.size()) { - state.propertyGridState.hoveredFieldId = - sections[hitTarget.sectionIndex].fields[hitTarget.fieldIndex].fieldId; - } -} - -void SyncKeyboardNavigation( - UIEditorPropertyGridInteractionState& state, - const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections) { - state.keyboardNavigation.SetItemCount(layout.visibleFieldIndices.size()); - state.keyboardNavigation.ClampToItemCount(); - - if (!selectionModel.HasSelection()) { - return; - } - - const std::size_t selectedVisibleIndex = - FindUIEditorPropertyGridVisibleFieldIndex( - layout, - selectionModel.GetSelectedId(), - sections); - if (selectedVisibleIndex == UIEditorPropertyGridInvalidIndex) { - return; - } - - if (!state.keyboardNavigation.HasCurrentIndex() || - state.keyboardNavigation.GetCurrentIndex() != selectedVisibleIndex) { - state.keyboardNavigation.SetCurrentIndex(selectedVisibleIndex); - } -} - -bool SelectVisibleField( - UIEditorPropertyGridInteractionState& state, - ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, - const UIEditorPropertyGridLayout& layout, - const std::vector& sections, - std::size_t visibleFieldIndex, - UIEditorPropertyGridInteractionResult& result) { - if (visibleFieldIndex >= layout.visibleFieldIndices.size() || - visibleFieldIndex >= layout.visibleFieldSectionIndices.size()) { - return false; - } - - const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; - const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; - if (sectionIndex >= sections.size() || - fieldIndex >= sections[sectionIndex].fields.size()) { - return false; - } - - const UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; - result.selectionChanged = selectionModel.SetSelection(field.fieldId); - result.selectedFieldId = field.fieldId; - result.consumed = true; - state.keyboardNavigation.SetCurrentIndex(visibleFieldIndex); - return true; -} - -bool BeginFieldEdit( - UIEditorPropertyGridInteractionState& state, - ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, - const UIEditorPropertyGridField& field, - UIEditorPropertyGridInteractionResult& result) { - if (field.readOnly || !IsInlineEditable(field)) { - return false; - } - - const std::string initialValue = - field.kind == UIEditorPropertyGridFieldKind::Text - ? field.valueText - : ResolveUIEditorPropertyGridFieldValueText(field); - const bool changed = propertyEditModel.BeginEdit(field.fieldId, initialValue); - if (!changed && - (!propertyEditModel.HasActiveEdit() || - propertyEditModel.GetActiveFieldId() != field.fieldId)) { - return false; - } - - state.textInputState.value = propertyEditModel.GetStagedValue(); - state.textInputState.caret = state.textInputState.value.size(); - result.editStarted = changed; - result.activeFieldId = field.fieldId; - result.consumed = true; - return true; -} - -bool CommitActiveEdit( - UIEditorPropertyGridInteractionState& state, - ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, - std::vector& sections, - UIEditorPropertyGridInteractionResult& result) { - if (!propertyEditModel.HasActiveEdit()) { - return false; - } - - UIEditorPropertyGridField* field = - FindMutableField(sections, propertyEditModel.GetActiveFieldId()); - if (field == nullptr) { - propertyEditModel.CancelEdit(); - state.textInputState = {}; - return false; - } - - result.activeFieldId = field->fieldId; - if (field->kind == UIEditorPropertyGridFieldKind::Number) { - Widgets::UIEditorNumberFieldSpec spec = {}; - spec.fieldId = field->fieldId; - spec.label = field->label; - spec.value = field->numberValue.value; - spec.step = field->numberValue.step; - spec.minValue = field->numberValue.minValue; - spec.maxValue = field->numberValue.maxValue; - spec.integerMode = field->numberValue.integerMode; - spec.readOnly = field->readOnly; - - double parsedValue = field->numberValue.value; - if (!Widgets::TryParseUIEditorNumberFieldValue( - spec, - propertyEditModel.GetStagedValue(), - parsedValue)) { - result.editCommitRejected = true; - result.consumed = true; - return false; - } - - field->numberValue.value = parsedValue; - result.committedFieldId = field->fieldId; - result.committedValue = ResolveUIEditorPropertyGridFieldValueText(*field); - SetChangedValueResult(*field, result); - } else { - field->valueText = propertyEditModel.GetStagedValue(); - result.committedFieldId = field->fieldId; - result.committedValue = field->valueText; - SetChangedValueResult(*field, result); - } - - propertyEditModel.CommitEdit(); - state.textInputState = {}; - result.editCommitted = true; - result.consumed = true; - return true; -} - -bool CancelActiveEdit( - UIEditorPropertyGridInteractionState& state, - ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, - UIEditorPropertyGridInteractionResult& result) { - if (!propertyEditModel.HasActiveEdit()) { - return false; - } - - result.activeFieldId = propertyEditModel.GetActiveFieldId(); - if (!propertyEditModel.CancelEdit()) { - return false; - } - - state.textInputState = {}; - result.editCanceled = true; - result.consumed = true; - return true; -} - -void ClosePopup( - UIEditorPropertyGridInteractionState& state, - UIEditorPropertyGridInteractionResult& result) { - if (state.propertyGridState.popupFieldId.empty()) { - return; - } - - ClearPopupState(state); - result.popupClosed = true; - result.consumed = true; -} - -void OpenPopup( - UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridField& field, - UIEditorPropertyGridInteractionResult& result) { - if (field.kind != UIEditorPropertyGridFieldKind::Enum || - field.readOnly || - field.enumValue.options.empty()) { - return; - } - - if (state.propertyGridState.popupFieldId == field.fieldId) { - return; - } - - state.propertyGridState.popupFieldId = field.fieldId; - state.propertyGridState.popupHighlightedIndex = - (std::min)(field.enumValue.selectedIndex, field.enumValue.options.size() - 1u); - result.popupOpened = true; - result.consumed = true; -} - -void MovePopupHighlight( - UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridField& field, - int delta) { - if (field.kind != UIEditorPropertyGridFieldKind::Enum || - field.enumValue.options.empty()) { - state.propertyGridState.popupHighlightedIndex = - UIEditorPropertyGridInvalidIndex; - return; - } - - if (state.propertyGridState.popupHighlightedIndex == UIEditorPropertyGridInvalidIndex || - state.propertyGridState.popupHighlightedIndex >= field.enumValue.options.size()) { - state.propertyGridState.popupHighlightedIndex = - (std::min)(field.enumValue.selectedIndex, field.enumValue.options.size() - 1u); - } - - const std::size_t currentIndex = state.propertyGridState.popupHighlightedIndex; - if (delta < 0) { - state.propertyGridState.popupHighlightedIndex = - currentIndex == 0u ? 0u : currentIndex - 1u; - } else { - state.propertyGridState.popupHighlightedIndex = - currentIndex + 1u >= field.enumValue.options.size() - ? field.enumValue.options.size() - 1u - : currentIndex + 1u; - } -} - -void JumpPopupHighlightToEdge( - UIEditorPropertyGridInteractionState& state, - const UIEditorPropertyGridField& field, - bool toEnd) { - if (field.kind != UIEditorPropertyGridFieldKind::Enum || - field.enumValue.options.empty()) { - state.propertyGridState.popupHighlightedIndex = - UIEditorPropertyGridInvalidIndex; - return; - } - - state.propertyGridState.popupHighlightedIndex = - toEnd ? field.enumValue.options.size() - 1u : 0u; -} - -bool CommitPopupSelection( - UIEditorPropertyGridInteractionState& state, - std::vector& sections, - UIEditorPropertyGridInteractionResult& result) { - UIEditorPropertyGridField* field = - FindMutableField(sections, state.propertyGridState.popupFieldId); - if (field == nullptr || - field->kind != UIEditorPropertyGridFieldKind::Enum || - field->readOnly || - field->enumValue.options.empty() || - state.propertyGridState.popupHighlightedIndex == - UIEditorPropertyGridInvalidIndex || - state.propertyGridState.popupHighlightedIndex >= - field->enumValue.options.size()) { - return false; - } - - field->enumValue.selectedIndex = state.propertyGridState.popupHighlightedIndex; - SetChangedValueResult(*field, result); - ClosePopup(state, result); - return true; -} - -bool ToggleBoolField( - UIEditorPropertyGridField& field, - UIEditorPropertyGridInteractionResult& result) { - if (field.kind != UIEditorPropertyGridFieldKind::Bool || field.readOnly) { - return false; - } - - field.boolValue = !field.boolValue; - SetChangedValueResult(field, result); - result.consumed = true; - return true; -} - -bool ProcessColorFieldEvent( - UIEditorPropertyGridInteractionState& state, - ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, - ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, - const UIEditorPropertyGridLayout& layout, - std::vector& sections, - const Widgets::UIEditorPropertyGridMetrics& metrics, - const UIInputEvent& event, - UIEditorPropertyGridInteractionResult& result) { - const Widgets::UIEditorColorFieldMetrics colorMetrics = - BuildUIEditorPropertyGridColorFieldMetrics(metrics); - const ::XCEngine::UI::UIRect popupViewportRect = - ResolvePopupViewportRect(layout.bounds); - bool handled = false; - - for (std::size_t visibleFieldIndex = 0u; - visibleFieldIndex < layout.visibleFieldIndices.size(); - ++visibleFieldIndex) { - const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; - const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; - if (sectionIndex >= sections.size() || - fieldIndex >= sections[sectionIndex].fields.size()) { - continue; - } - - UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; - if (field.kind != UIEditorPropertyGridFieldKind::Color) { - continue; - } - - UIEditorColorFieldInteractionState colorState = - BuildColorFieldInteractionState(state, field.fieldId); - const bool popupWasOpen = colorState.colorFieldState.popupOpen; - UIEditorColorFieldSpec spec = Widgets::Detail::BuildColorFieldSpec(field); - const UIEditorColorFieldInteractionFrame frame = - UpdateUIEditorColorFieldInteraction( - colorState, - spec, - layout.fieldRowRects[visibleFieldIndex], - { event }, - colorMetrics, - popupViewportRect); - - if (!frame.result.consumed && - frame.result.hitTarget.kind == UIEditorColorFieldHitTargetKind::None && - !popupWasOpen && - !colorState.colorFieldState.popupOpen) { - continue; - } - - handled = true; - field.colorValue.value = spec.value; - field.colorValue.showAlpha = spec.showAlpha; - StoreColorFieldVisualState(state, field.fieldId, colorState); - - if (frame.result.popupOpened) { - ClosePopup(state, result); - CloseOtherColorFieldPopups(state, field.fieldId); - } - - state.propertyGridState.focused = colorState.colorFieldState.focused; - state.propertyGridState.pressedFieldId.clear(); - - if (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None || - frame.result.popupOpened || - frame.result.colorChanged || - popupWasOpen || - colorState.colorFieldState.popupOpen) { - result.selectionChanged = - selectionModel.SetSelection(field.fieldId) || result.selectionChanged; - result.selectedFieldId = field.fieldId; - result.activeFieldId = field.fieldId; - state.keyboardNavigation.SetCurrentIndex(visibleFieldIndex); - } - - if (propertyEditModel.HasActiveEdit() && - propertyEditModel.GetActiveFieldId() != field.fieldId && - (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None || - frame.result.popupOpened)) { - CommitActiveEdit(state, propertyEditModel, sections, result); - } - - result.popupOpened = result.popupOpened || frame.result.popupOpened; - result.popupClosed = result.popupClosed || frame.result.popupClosed; - result.consumed = result.consumed || frame.result.consumed; - if (frame.result.colorChanged) { - SetChangedValueResult(field, result); - } - - if (frame.result.hitTarget.kind == UIEditorColorFieldHitTargetKind::Row) { - result.hitTarget.kind = UIEditorPropertyGridHitTargetKind::FieldRow; - result.hitTarget.sectionIndex = sectionIndex; - result.hitTarget.fieldIndex = fieldIndex; - result.hitTarget.visibleFieldIndex = visibleFieldIndex; - } else if (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None) { - result.hitTarget.kind = UIEditorPropertyGridHitTargetKind::ValueBox; - result.hitTarget.sectionIndex = sectionIndex; - result.hitTarget.fieldIndex = fieldIndex; - result.hitTarget.visibleFieldIndex = visibleFieldIndex; - } - } - - return handled; -} - -} // namespace XCEngine::UI::Editor::Detail diff --git a/new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp b/new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp index c63c4a2b..24973381 100644 --- a/new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp @@ -1,6 +1,6 @@ #include -#include +#include "Fields/VectorFieldInteractionInternal.h" #include @@ -8,12 +8,6 @@ namespace XCEngine::UI::Editor { namespace { -using ::XCEngine::Input::KeyCode; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPointerButton; -using ::XCEngine::UI::Text::HandleKeyDown; -using ::XCEngine::UI::Text::InsertCharacter; using ::XCEngine::UI::Editor::Widgets::BuildUIEditorVector2FieldLayout; using ::XCEngine::UI::Editor::Widgets::FormatUIEditorVector2FieldComponentValue; using ::XCEngine::UI::Editor::Widgets::HitTestUIEditorVector2Field; @@ -27,250 +21,49 @@ using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldLayout; using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldMetrics; using ::XCEngine::UI::Editor::Widgets::UIEditorVector2FieldSpec; -bool ShouldUsePointerPosition(const UIInputEvent& event) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - return true; - default: - return false; - } -} +struct Vector2FieldInteractionTraits { + using InteractionState = UIEditorVector2FieldInteractionState; + using InteractionResult = UIEditorVector2FieldInteractionResult; + using Spec = UIEditorVector2FieldSpec; + using Layout = UIEditorVector2FieldLayout; + using Metrics = UIEditorVector2FieldMetrics; + using HitTarget = UIEditorVector2FieldHitTarget; + using HitTargetKind = UIEditorVector2FieldHitTargetKind; -std::size_t ResolveFallbackSelectedComponentIndex( - const UIEditorVector2FieldInteractionState& state) { - return state.vector2FieldState.selectedComponentIndex == - UIEditorVector2FieldInvalidComponentIndex - ? 0u - : state.vector2FieldState.selectedComponentIndex; -} + static constexpr std::size_t kComponentCount = 2u; + static constexpr std::size_t kLastComponentIndex = 1u; + static constexpr std::size_t kInvalidComponentIndex = UIEditorVector2FieldInvalidComponentIndex; + static constexpr HitTargetKind kNoneHitTargetKind = UIEditorVector2FieldHitTargetKind::None; + static constexpr HitTargetKind kRowHitTargetKind = UIEditorVector2FieldHitTargetKind::Row; + static constexpr HitTargetKind kComponentHitTargetKind = UIEditorVector2FieldHitTargetKind::Component; -std::string BuildComponentEditFieldId( - const UIEditorVector2FieldSpec& spec, - std::size_t componentIndex) { - return spec.fieldId + "." + std::to_string(componentIndex); -} + static auto& FieldState(InteractionState& state) { return state.vector2FieldState; } + static const auto& FieldState(const InteractionState& state) { return state.vector2FieldState; } -bool IsPermittedCharacter( - const UIEditorVector2FieldSpec& spec, - std::uint32_t character) { - if (character >= static_cast('0') && - character <= static_cast('9')) { - return true; + static Layout BuildLayout(const ::XCEngine::UI::UIRect& bounds, const Spec& spec, const Metrics& metrics) { + return BuildUIEditorVector2FieldLayout(bounds, spec, metrics); } - if (character == static_cast('-') || - character == static_cast('+')) { - return true; + static HitTarget HitTest(const Layout& layout, const ::XCEngine::UI::UIPoint& point) { + return HitTestUIEditorVector2Field(layout, point); } - return !spec.integerMode && character == static_cast('.'); -} - -void SyncDisplayTexts( - UIEditorVector2FieldInteractionState& state, - const UIEditorVector2FieldSpec& spec) { - for (std::size_t componentIndex = 0u; - componentIndex < state.vector2FieldState.displayTexts.size(); - ++componentIndex) { - if (state.vector2FieldState.editing && - state.vector2FieldState.selectedComponentIndex == componentIndex) { - continue; - } - - state.vector2FieldState.displayTexts[componentIndex] = - FormatUIEditorVector2FieldComponentValue(spec, componentIndex); - } -} - -void SyncHoverTarget( - UIEditorVector2FieldInteractionState& state, - const UIEditorVector2FieldLayout& layout) { - if (!state.hasPointerPosition) { - state.vector2FieldState.hoveredTarget = UIEditorVector2FieldHitTargetKind::None; - state.vector2FieldState.hoveredComponentIndex = UIEditorVector2FieldInvalidComponentIndex; - return; + static bool IsPointInside(const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIPoint& point) { + return IsUIEditorVector2FieldPointInside(rect, point); } - const UIEditorVector2FieldHitTarget hitTarget = - HitTestUIEditorVector2Field(layout, state.pointerPosition); - state.vector2FieldState.hoveredTarget = hitTarget.kind; - state.vector2FieldState.hoveredComponentIndex = hitTarget.componentIndex; -} - -bool MoveSelection( - UIEditorVector2FieldInteractionState& state, - int direction, - UIEditorVector2FieldInteractionResult& result) { - const std::size_t before = ResolveFallbackSelectedComponentIndex(state); - const std::size_t after = - direction < 0 - ? (before == 0u ? 0u : before - 1u) - : (before >= 1u ? 1u : before + 1u); - state.vector2FieldState.selectedComponentIndex = after; - result.selectionChanged = before != after; - result.selectedComponentIndex = after; - result.consumed = true; - return true; -} - -bool SelectComponent( - UIEditorVector2FieldInteractionState& state, - std::size_t componentIndex, - UIEditorVector2FieldInteractionResult& result) { - if (componentIndex >= 2u) { - return false; + static std::string FormatComponentValue(const Spec& spec, std::size_t componentIndex) { + return FormatUIEditorVector2FieldComponentValue(spec, componentIndex); } - const std::size_t before = ResolveFallbackSelectedComponentIndex(state); - state.vector2FieldState.selectedComponentIndex = componentIndex; - result.selectionChanged = before != componentIndex; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool BeginEdit( - UIEditorVector2FieldInteractionState& state, - const UIEditorVector2FieldSpec& spec, - std::size_t componentIndex, - bool clearText) { - if (spec.readOnly || componentIndex >= spec.values.size()) { - return false; + static bool TryParseComponentValue(const Spec& spec, std::string_view text, double& outValue) { + return TryParseUIEditorVector2FieldComponentValue(spec, text, outValue); } - const std::string baseline = - FormatUIEditorVector2FieldComponentValue(spec, componentIndex); - const std::string editFieldId = - BuildComponentEditFieldId(spec, componentIndex); - const bool changed = state.editModel.BeginEdit(editFieldId, baseline); - if (!changed && - state.editModel.HasActiveEdit() && - state.editModel.GetActiveFieldId() != editFieldId) { - return false; + static double NormalizeComponentValue(const Spec& spec, double value) { + return NormalizeUIEditorVector2FieldComponentValue(spec, value); } - if (!changed && - state.vector2FieldState.editing && - state.vector2FieldState.selectedComponentIndex == componentIndex) { - return false; - } - - state.vector2FieldState.selectedComponentIndex = componentIndex; - state.vector2FieldState.editing = true; - state.textInputState.value = clearText ? std::string() : baseline; - state.textInputState.caret = state.textInputState.value.size(); - state.editModel.UpdateStagedValue(state.textInputState.value); - state.vector2FieldState.displayTexts[componentIndex] = state.textInputState.value; - return true; -} - -bool CommitEdit( - UIEditorVector2FieldInteractionState& state, - UIEditorVector2FieldSpec& spec, - UIEditorVector2FieldInteractionResult& result) { - if (!state.vector2FieldState.editing || - !state.editModel.HasActiveEdit() || - state.vector2FieldState.selectedComponentIndex >= spec.values.size()) { - return false; - } - - const std::size_t componentIndex = state.vector2FieldState.selectedComponentIndex; - double parsedValue = spec.values[componentIndex]; - if (!TryParseUIEditorVector2FieldComponentValue( - spec, - state.textInputState.value, - parsedValue)) { - result.consumed = true; - result.editCommitRejected = true; - return false; - } - - result.valuesBefore = spec.values; - spec.values[componentIndex] = NormalizeUIEditorVector2FieldComponentValue(spec, parsedValue); - result.valuesAfter = spec.values; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.editCommitted = true; - result.consumed = true; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.committedText = - FormatUIEditorVector2FieldComponentValue(spec, componentIndex); - - state.editModel.CommitEdit(); - state.textInputState = {}; - state.vector2FieldState.editing = false; - state.vector2FieldState.displayTexts[componentIndex] = result.committedText; - return true; -} - -bool CancelEdit( - UIEditorVector2FieldInteractionState& state, - const UIEditorVector2FieldSpec& spec, - UIEditorVector2FieldInteractionResult& result) { - if (!state.vector2FieldState.editing || - !state.editModel.HasActiveEdit() || - state.vector2FieldState.selectedComponentIndex >= spec.values.size()) { - return false; - } - - const std::size_t componentIndex = state.vector2FieldState.selectedComponentIndex; - state.editModel.CancelEdit(); - state.textInputState = {}; - state.vector2FieldState.editing = false; - state.vector2FieldState.displayTexts[componentIndex] = - FormatUIEditorVector2FieldComponentValue(spec, componentIndex); - result.consumed = true; - result.editCanceled = true; - result.valuesBefore = spec.values; - result.valuesAfter = spec.values; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool ApplyStep( - UIEditorVector2FieldInteractionState& state, - UIEditorVector2FieldSpec& spec, - double direction, - bool snapToEdge, - UIEditorVector2FieldInteractionResult& result) { - if (spec.readOnly) { - return false; - } - - const std::size_t componentIndex = ResolveFallbackSelectedComponentIndex(state); - state.vector2FieldState.selectedComponentIndex = componentIndex; - - if (state.vector2FieldState.editing && - !CommitEdit(state, spec, result)) { - return result.editCommitRejected; - } - - result.valuesBefore = spec.values; - if (snapToEdge) { - spec.values[componentIndex] = - direction < 0.0 - ? NormalizeUIEditorVector2FieldComponentValue(spec, spec.minValue) - : NormalizeUIEditorVector2FieldComponentValue(spec, spec.maxValue); - } else { - const double step = spec.step == 0.0 ? 1.0 : spec.step; - spec.values[componentIndex] = NormalizeUIEditorVector2FieldComponentValue( - spec, - spec.values[componentIndex] + step * direction); - result.stepDelta = step * direction; - } - - result.valuesAfter = spec.values; - result.stepApplied = true; - result.valueChanged = result.valuesBefore != result.valuesAfter || result.valueChanged; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.consumed = true; - state.vector2FieldState.displayTexts[componentIndex] = - FormatUIEditorVector2FieldComponentValue(spec, componentIndex); - return true; -} +}; } // namespace @@ -280,283 +73,18 @@ UIEditorVector2FieldInteractionFrame UpdateUIEditorVector2FieldInteraction( const ::XCEngine::UI::UIRect& bounds, const std::vector& inputEvents, const UIEditorVector2FieldMetrics& metrics) { - UIEditorVector2FieldLayout layout = BuildUIEditorVector2FieldLayout(bounds, spec, metrics); - SyncDisplayTexts(state, spec); - SyncHoverTarget(state, layout); - - UIEditorVector2FieldInteractionResult interactionResult = {}; - interactionResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex; - for (const UIInputEvent& event : inputEvents) { - if (ShouldUsePointerPosition(event)) { - state.pointerPosition = event.position; - state.hasPointerPosition = true; - } else if (event.type == UIInputEventType::PointerLeave) { - state.hasPointerPosition = false; - } - - UIEditorVector2FieldInteractionResult eventResult = {}; - eventResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex; - switch (event.type) { - case UIInputEventType::FocusGained: - eventResult.focusChanged = !state.vector2FieldState.focused; - state.vector2FieldState.focused = true; - if (state.vector2FieldState.selectedComponentIndex == - UIEditorVector2FieldInvalidComponentIndex) { - state.vector2FieldState.selectedComponentIndex = 0u; - } - eventResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex; - break; - - case UIInputEventType::FocusLost: - eventResult.focusChanged = state.vector2FieldState.focused; - state.vector2FieldState.focused = false; - state.vector2FieldState.activeTarget = UIEditorVector2FieldHitTargetKind::None; - state.vector2FieldState.activeComponentIndex = UIEditorVector2FieldInvalidComponentIndex; - state.hasPointerPosition = false; - if (state.vector2FieldState.editing) { - CommitEdit(state, spec, eventResult); - if (eventResult.editCommitRejected) { - CancelEdit(state, spec, eventResult); - } - } - break; - - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerLeave: - break; - - case UIInputEventType::PointerButtonDown: { - const UIEditorVector2FieldHitTarget hitTarget = - state.hasPointerPosition - ? HitTestUIEditorVector2Field(layout, state.pointerPosition) - : UIEditorVector2FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton != UIPointerButton::Left) { - break; - } - - const bool insideField = - state.hasPointerPosition && - IsUIEditorVector2FieldPointInside(layout.bounds, state.pointerPosition); - if (insideField) { - eventResult.focusChanged = !state.vector2FieldState.focused; - state.vector2FieldState.focused = true; - state.vector2FieldState.activeTarget = hitTarget.kind == UIEditorVector2FieldHitTargetKind::None - ? UIEditorVector2FieldHitTargetKind::Row - : hitTarget.kind; - state.vector2FieldState.activeComponentIndex = hitTarget.componentIndex; - if (hitTarget.kind == UIEditorVector2FieldHitTargetKind::Component) { - SelectComponent(state, hitTarget.componentIndex, eventResult); - } else if (state.vector2FieldState.selectedComponentIndex == - UIEditorVector2FieldInvalidComponentIndex) { - state.vector2FieldState.selectedComponentIndex = 0u; - eventResult.selectedComponentIndex = 0u; - } - eventResult.consumed = true; - } else { - if (state.vector2FieldState.editing) { - CommitEdit(state, spec, eventResult); - if (!eventResult.editCommitRejected) { - eventResult.focusChanged = state.vector2FieldState.focused; - state.vector2FieldState.focused = false; - } - } else if (state.vector2FieldState.focused) { - eventResult.focusChanged = true; - state.vector2FieldState.focused = false; - } - state.vector2FieldState.activeTarget = UIEditorVector2FieldHitTargetKind::None; - state.vector2FieldState.activeComponentIndex = UIEditorVector2FieldInvalidComponentIndex; - } - break; - } - - case UIInputEventType::PointerButtonUp: { - const UIEditorVector2FieldHitTarget hitTarget = - state.hasPointerPosition - ? HitTestUIEditorVector2Field(layout, state.pointerPosition) - : UIEditorVector2FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton == UIPointerButton::Left) { - const UIEditorVector2FieldHitTargetKind activeTarget = state.vector2FieldState.activeTarget; - const std::size_t activeComponentIndex = state.vector2FieldState.activeComponentIndex; - state.vector2FieldState.activeTarget = UIEditorVector2FieldHitTargetKind::None; - state.vector2FieldState.activeComponentIndex = UIEditorVector2FieldInvalidComponentIndex; - - if (activeTarget == UIEditorVector2FieldHitTargetKind::Component && - hitTarget.kind == UIEditorVector2FieldHitTargetKind::Component && - activeComponentIndex == hitTarget.componentIndex) { - SelectComponent(state, hitTarget.componentIndex, eventResult); - if (!state.vector2FieldState.editing) { - eventResult.editStarted = - BeginEdit(state, spec, hitTarget.componentIndex, false); - } - eventResult.consumed = true; - } else if (hitTarget.kind == UIEditorVector2FieldHitTargetKind::Row) { - eventResult.consumed = true; - } - } - break; - } - - case UIInputEventType::KeyDown: - if (!state.vector2FieldState.focused) { - break; - } - - if (state.vector2FieldState.editing) { - if (event.keyCode == static_cast(KeyCode::Escape)) { - CancelEdit(state, spec, eventResult); - break; - } - - if (event.keyCode == static_cast(KeyCode::Tab)) { - if (CommitEdit(state, spec, eventResult)) { - MoveSelection( - state, - event.modifiers.shift ? -1 : 1, - eventResult); - } - eventResult.consumed = true; - break; - } - - const auto textResult = - HandleKeyDown(state.textInputState, event.keyCode, event.modifiers); - if (textResult.handled) { - state.editModel.UpdateStagedValue(state.textInputState.value); - state.vector2FieldState.displayTexts[state.vector2FieldState.selectedComponentIndex] = - state.textInputState.value; - eventResult.consumed = true; - eventResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex; - if (textResult.submitRequested) { - CommitEdit(state, spec, eventResult); - } - break; - } - } else { - switch (static_cast(event.keyCode)) { - case KeyCode::Left: - MoveSelection(state, -1, eventResult); - break; - - case KeyCode::Right: - case KeyCode::Tab: - MoveSelection( - state, - static_cast(event.keyCode) == KeyCode::Tab && event.modifiers.shift ? -1 : 1, - eventResult); - break; - - case KeyCode::Up: - ApplyStep(state, spec, 1.0, false, eventResult); - break; - - case KeyCode::Down: - ApplyStep(state, spec, -1.0, false, eventResult); - break; - - case KeyCode::Home: - ApplyStep(state, spec, -1.0, true, eventResult); - break; - - case KeyCode::End: - ApplyStep(state, spec, 1.0, true, eventResult); - break; - - case KeyCode::Enter: - eventResult.selectedComponentIndex = ResolveFallbackSelectedComponentIndex(state); - eventResult.editStarted = BeginEdit( - state, - spec, - eventResult.selectedComponentIndex, - false); - eventResult.consumed = eventResult.editStarted; - break; - - default: - break; - } - } - break; - - case UIInputEventType::Character: - if (!state.vector2FieldState.focused || - spec.readOnly || - event.modifiers.control || - event.modifiers.alt || - event.modifiers.super || - !IsPermittedCharacter(spec, event.character)) { - break; - } - - if (state.vector2FieldState.selectedComponentIndex == - UIEditorVector2FieldInvalidComponentIndex) { - state.vector2FieldState.selectedComponentIndex = 0u; - } - eventResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex; - - if (!state.vector2FieldState.editing) { - eventResult.editStarted = BeginEdit( - state, - spec, - state.vector2FieldState.selectedComponentIndex, - true); - } - - if (InsertCharacter(state.textInputState, event.character)) { - state.editModel.UpdateStagedValue(state.textInputState.value); - state.vector2FieldState.displayTexts[state.vector2FieldState.selectedComponentIndex] = - state.textInputState.value; - eventResult.consumed = true; - } - break; - - default: - break; - } - - layout = BuildUIEditorVector2FieldLayout(bounds, spec, metrics); - SyncDisplayTexts(state, spec); - SyncHoverTarget(state, layout); - if (eventResult.hitTarget.kind == UIEditorVector2FieldHitTargetKind::None && - state.hasPointerPosition) { - eventResult.hitTarget = HitTestUIEditorVector2Field(layout, state.pointerPosition); - } - if (eventResult.selectedComponentIndex == UIEditorVector2FieldInvalidComponentIndex) { - eventResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex; - } - - if (eventResult.consumed || - eventResult.focusChanged || - eventResult.valueChanged || - eventResult.stepApplied || - eventResult.selectionChanged || - eventResult.editStarted || - eventResult.editCommitted || - eventResult.editCommitRejected || - eventResult.editCanceled || - eventResult.hitTarget.kind != UIEditorVector2FieldHitTargetKind::None) { - interactionResult = std::move(eventResult); - } - } - - layout = BuildUIEditorVector2FieldLayout(bounds, spec, metrics); - SyncDisplayTexts(state, spec); - SyncHoverTarget(state, layout); - if (interactionResult.hitTarget.kind == UIEditorVector2FieldHitTargetKind::None && - state.hasPointerPosition) { - interactionResult.hitTarget = HitTestUIEditorVector2Field(layout, state.pointerPosition); - } - if (interactionResult.selectedComponentIndex == UIEditorVector2FieldInvalidComponentIndex) { - interactionResult.selectedComponentIndex = state.vector2FieldState.selectedComponentIndex; - } - + UIEditorVector2FieldLayout layout = {}; + UIEditorVector2FieldInteractionResult result = + Internal::UpdateVectorFieldInteractionImpl( + state, + spec, + bounds, + inputEvents, + metrics, + layout); return { std::move(layout), - std::move(interactionResult) + std::move(result) }; } diff --git a/new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp b/new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp index 61c70d79..ab961f0a 100644 --- a/new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp @@ -1,6 +1,6 @@ #include -#include +#include "Fields/VectorFieldInteractionInternal.h" #include @@ -8,12 +8,6 @@ namespace XCEngine::UI::Editor { namespace { -using ::XCEngine::Input::KeyCode; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPointerButton; -using ::XCEngine::UI::Text::HandleKeyDown; -using ::XCEngine::UI::Text::InsertCharacter; using ::XCEngine::UI::Editor::Widgets::BuildUIEditorVector3FieldLayout; using ::XCEngine::UI::Editor::Widgets::FormatUIEditorVector3FieldComponentValue; using ::XCEngine::UI::Editor::Widgets::HitTestUIEditorVector3Field; @@ -27,250 +21,49 @@ using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldLayout; using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldMetrics; using ::XCEngine::UI::Editor::Widgets::UIEditorVector3FieldSpec; -bool ShouldUsePointerPosition(const UIInputEvent& event) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - return true; - default: - return false; - } -} +struct Vector3FieldInteractionTraits { + using InteractionState = UIEditorVector3FieldInteractionState; + using InteractionResult = UIEditorVector3FieldInteractionResult; + using Spec = UIEditorVector3FieldSpec; + using Layout = UIEditorVector3FieldLayout; + using Metrics = UIEditorVector3FieldMetrics; + using HitTarget = UIEditorVector3FieldHitTarget; + using HitTargetKind = UIEditorVector3FieldHitTargetKind; -std::size_t ResolveFallbackSelectedComponentIndex( - const UIEditorVector3FieldInteractionState& state) { - return state.vector3FieldState.selectedComponentIndex == - UIEditorVector3FieldInvalidComponentIndex - ? 0u - : state.vector3FieldState.selectedComponentIndex; -} + static constexpr std::size_t kComponentCount = 3u; + static constexpr std::size_t kLastComponentIndex = 2u; + static constexpr std::size_t kInvalidComponentIndex = UIEditorVector3FieldInvalidComponentIndex; + static constexpr HitTargetKind kNoneHitTargetKind = UIEditorVector3FieldHitTargetKind::None; + static constexpr HitTargetKind kRowHitTargetKind = UIEditorVector3FieldHitTargetKind::Row; + static constexpr HitTargetKind kComponentHitTargetKind = UIEditorVector3FieldHitTargetKind::Component; -std::string BuildComponentEditFieldId( - const UIEditorVector3FieldSpec& spec, - std::size_t componentIndex) { - return spec.fieldId + "." + std::to_string(componentIndex); -} + static auto& FieldState(InteractionState& state) { return state.vector3FieldState; } + static const auto& FieldState(const InteractionState& state) { return state.vector3FieldState; } -bool IsPermittedCharacter( - const UIEditorVector3FieldSpec& spec, - std::uint32_t character) { - if (character >= static_cast('0') && - character <= static_cast('9')) { - return true; + static Layout BuildLayout(const ::XCEngine::UI::UIRect& bounds, const Spec& spec, const Metrics& metrics) { + return BuildUIEditorVector3FieldLayout(bounds, spec, metrics); } - if (character == static_cast('-') || - character == static_cast('+')) { - return true; + static HitTarget HitTest(const Layout& layout, const ::XCEngine::UI::UIPoint& point) { + return HitTestUIEditorVector3Field(layout, point); } - return !spec.integerMode && character == static_cast('.'); -} - -void SyncDisplayTexts( - UIEditorVector3FieldInteractionState& state, - const UIEditorVector3FieldSpec& spec) { - for (std::size_t componentIndex = 0u; - componentIndex < state.vector3FieldState.displayTexts.size(); - ++componentIndex) { - if (state.vector3FieldState.editing && - state.vector3FieldState.selectedComponentIndex == componentIndex) { - continue; - } - - state.vector3FieldState.displayTexts[componentIndex] = - FormatUIEditorVector3FieldComponentValue(spec, componentIndex); - } -} - -void SyncHoverTarget( - UIEditorVector3FieldInteractionState& state, - const UIEditorVector3FieldLayout& layout) { - if (!state.hasPointerPosition) { - state.vector3FieldState.hoveredTarget = UIEditorVector3FieldHitTargetKind::None; - state.vector3FieldState.hoveredComponentIndex = UIEditorVector3FieldInvalidComponentIndex; - return; + static bool IsPointInside(const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIPoint& point) { + return IsUIEditorVector3FieldPointInside(rect, point); } - const UIEditorVector3FieldHitTarget hitTarget = - HitTestUIEditorVector3Field(layout, state.pointerPosition); - state.vector3FieldState.hoveredTarget = hitTarget.kind; - state.vector3FieldState.hoveredComponentIndex = hitTarget.componentIndex; -} - -bool MoveSelection( - UIEditorVector3FieldInteractionState& state, - int direction, - UIEditorVector3FieldInteractionResult& result) { - const std::size_t before = ResolveFallbackSelectedComponentIndex(state); - const std::size_t after = - direction < 0 - ? (before == 0u ? 0u : before - 1u) - : (before >= 2u ? 2u : before + 1u); - state.vector3FieldState.selectedComponentIndex = after; - result.selectionChanged = before != after; - result.selectedComponentIndex = after; - result.consumed = true; - return true; -} - -bool SelectComponent( - UIEditorVector3FieldInteractionState& state, - std::size_t componentIndex, - UIEditorVector3FieldInteractionResult& result) { - if (componentIndex >= 3u) { - return false; + static std::string FormatComponentValue(const Spec& spec, std::size_t componentIndex) { + return FormatUIEditorVector3FieldComponentValue(spec, componentIndex); } - const std::size_t before = ResolveFallbackSelectedComponentIndex(state); - state.vector3FieldState.selectedComponentIndex = componentIndex; - result.selectionChanged = before != componentIndex; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool BeginEdit( - UIEditorVector3FieldInteractionState& state, - const UIEditorVector3FieldSpec& spec, - std::size_t componentIndex, - bool clearText) { - if (spec.readOnly || componentIndex >= spec.values.size()) { - return false; + static bool TryParseComponentValue(const Spec& spec, std::string_view text, double& outValue) { + return TryParseUIEditorVector3FieldComponentValue(spec, text, outValue); } - const std::string baseline = - FormatUIEditorVector3FieldComponentValue(spec, componentIndex); - const std::string editFieldId = - BuildComponentEditFieldId(spec, componentIndex); - const bool changed = state.editModel.BeginEdit(editFieldId, baseline); - if (!changed && - state.editModel.HasActiveEdit() && - state.editModel.GetActiveFieldId() != editFieldId) { - return false; + static double NormalizeComponentValue(const Spec& spec, double value) { + return NormalizeUIEditorVector3FieldComponentValue(spec, value); } - if (!changed && - state.vector3FieldState.editing && - state.vector3FieldState.selectedComponentIndex == componentIndex) { - return false; - } - - state.vector3FieldState.selectedComponentIndex = componentIndex; - state.vector3FieldState.editing = true; - state.textInputState.value = clearText ? std::string() : baseline; - state.textInputState.caret = state.textInputState.value.size(); - state.editModel.UpdateStagedValue(state.textInputState.value); - state.vector3FieldState.displayTexts[componentIndex] = state.textInputState.value; - return true; -} - -bool CommitEdit( - UIEditorVector3FieldInteractionState& state, - UIEditorVector3FieldSpec& spec, - UIEditorVector3FieldInteractionResult& result) { - if (!state.vector3FieldState.editing || - !state.editModel.HasActiveEdit() || - state.vector3FieldState.selectedComponentIndex >= spec.values.size()) { - return false; - } - - const std::size_t componentIndex = state.vector3FieldState.selectedComponentIndex; - double parsedValue = spec.values[componentIndex]; - if (!TryParseUIEditorVector3FieldComponentValue( - spec, - state.textInputState.value, - parsedValue)) { - result.consumed = true; - result.editCommitRejected = true; - return false; - } - - result.valuesBefore = spec.values; - spec.values[componentIndex] = NormalizeUIEditorVector3FieldComponentValue(spec, parsedValue); - result.valuesAfter = spec.values; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.editCommitted = true; - result.consumed = true; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.committedText = - FormatUIEditorVector3FieldComponentValue(spec, componentIndex); - - state.editModel.CommitEdit(); - state.textInputState = {}; - state.vector3FieldState.editing = false; - state.vector3FieldState.displayTexts[componentIndex] = result.committedText; - return true; -} - -bool CancelEdit( - UIEditorVector3FieldInteractionState& state, - const UIEditorVector3FieldSpec& spec, - UIEditorVector3FieldInteractionResult& result) { - if (!state.vector3FieldState.editing || - !state.editModel.HasActiveEdit() || - state.vector3FieldState.selectedComponentIndex >= spec.values.size()) { - return false; - } - - const std::size_t componentIndex = state.vector3FieldState.selectedComponentIndex; - state.editModel.CancelEdit(); - state.textInputState = {}; - state.vector3FieldState.editing = false; - state.vector3FieldState.displayTexts[componentIndex] = - FormatUIEditorVector3FieldComponentValue(spec, componentIndex); - result.consumed = true; - result.editCanceled = true; - result.valuesBefore = spec.values; - result.valuesAfter = spec.values; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool ApplyStep( - UIEditorVector3FieldInteractionState& state, - UIEditorVector3FieldSpec& spec, - double direction, - bool snapToEdge, - UIEditorVector3FieldInteractionResult& result) { - if (spec.readOnly) { - return false; - } - - const std::size_t componentIndex = ResolveFallbackSelectedComponentIndex(state); - state.vector3FieldState.selectedComponentIndex = componentIndex; - - if (state.vector3FieldState.editing && - !CommitEdit(state, spec, result)) { - return result.editCommitRejected; - } - - result.valuesBefore = spec.values; - if (snapToEdge) { - spec.values[componentIndex] = - direction < 0.0 - ? NormalizeUIEditorVector3FieldComponentValue(spec, spec.minValue) - : NormalizeUIEditorVector3FieldComponentValue(spec, spec.maxValue); - } else { - const double step = spec.step == 0.0 ? 1.0 : spec.step; - spec.values[componentIndex] = NormalizeUIEditorVector3FieldComponentValue( - spec, - spec.values[componentIndex] + step * direction); - result.stepDelta = step * direction; - } - - result.valuesAfter = spec.values; - result.stepApplied = true; - result.valueChanged = result.valuesBefore != result.valuesAfter || result.valueChanged; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.consumed = true; - state.vector3FieldState.displayTexts[componentIndex] = - FormatUIEditorVector3FieldComponentValue(spec, componentIndex); - return true; -} +}; } // namespace @@ -280,283 +73,18 @@ UIEditorVector3FieldInteractionFrame UpdateUIEditorVector3FieldInteraction( const ::XCEngine::UI::UIRect& bounds, const std::vector& inputEvents, const UIEditorVector3FieldMetrics& metrics) { - UIEditorVector3FieldLayout layout = BuildUIEditorVector3FieldLayout(bounds, spec, metrics); - SyncDisplayTexts(state, spec); - SyncHoverTarget(state, layout); - - UIEditorVector3FieldInteractionResult interactionResult = {}; - interactionResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; - for (const UIInputEvent& event : inputEvents) { - if (ShouldUsePointerPosition(event)) { - state.pointerPosition = event.position; - state.hasPointerPosition = true; - } else if (event.type == UIInputEventType::PointerLeave) { - state.hasPointerPosition = false; - } - - UIEditorVector3FieldInteractionResult eventResult = {}; - eventResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; - switch (event.type) { - case UIInputEventType::FocusGained: - eventResult.focusChanged = !state.vector3FieldState.focused; - state.vector3FieldState.focused = true; - if (state.vector3FieldState.selectedComponentIndex == - UIEditorVector3FieldInvalidComponentIndex) { - state.vector3FieldState.selectedComponentIndex = 0u; - } - eventResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; - break; - - case UIInputEventType::FocusLost: - eventResult.focusChanged = state.vector3FieldState.focused; - state.vector3FieldState.focused = false; - state.vector3FieldState.activeTarget = UIEditorVector3FieldHitTargetKind::None; - state.vector3FieldState.activeComponentIndex = UIEditorVector3FieldInvalidComponentIndex; - state.hasPointerPosition = false; - if (state.vector3FieldState.editing) { - CommitEdit(state, spec, eventResult); - if (eventResult.editCommitRejected) { - CancelEdit(state, spec, eventResult); - } - } - break; - - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerLeave: - break; - - case UIInputEventType::PointerButtonDown: { - const UIEditorVector3FieldHitTarget hitTarget = - state.hasPointerPosition - ? HitTestUIEditorVector3Field(layout, state.pointerPosition) - : UIEditorVector3FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton != UIPointerButton::Left) { - break; - } - - const bool insideField = - state.hasPointerPosition && - IsUIEditorVector3FieldPointInside(layout.bounds, state.pointerPosition); - if (insideField) { - eventResult.focusChanged = !state.vector3FieldState.focused; - state.vector3FieldState.focused = true; - state.vector3FieldState.activeTarget = hitTarget.kind == UIEditorVector3FieldHitTargetKind::None - ? UIEditorVector3FieldHitTargetKind::Row - : hitTarget.kind; - state.vector3FieldState.activeComponentIndex = hitTarget.componentIndex; - if (hitTarget.kind == UIEditorVector3FieldHitTargetKind::Component) { - SelectComponent(state, hitTarget.componentIndex, eventResult); - } else if (state.vector3FieldState.selectedComponentIndex == - UIEditorVector3FieldInvalidComponentIndex) { - state.vector3FieldState.selectedComponentIndex = 0u; - eventResult.selectedComponentIndex = 0u; - } - eventResult.consumed = true; - } else { - if (state.vector3FieldState.editing) { - CommitEdit(state, spec, eventResult); - if (!eventResult.editCommitRejected) { - eventResult.focusChanged = state.vector3FieldState.focused; - state.vector3FieldState.focused = false; - } - } else if (state.vector3FieldState.focused) { - eventResult.focusChanged = true; - state.vector3FieldState.focused = false; - } - state.vector3FieldState.activeTarget = UIEditorVector3FieldHitTargetKind::None; - state.vector3FieldState.activeComponentIndex = UIEditorVector3FieldInvalidComponentIndex; - } - break; - } - - case UIInputEventType::PointerButtonUp: { - const UIEditorVector3FieldHitTarget hitTarget = - state.hasPointerPosition - ? HitTestUIEditorVector3Field(layout, state.pointerPosition) - : UIEditorVector3FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton == UIPointerButton::Left) { - const UIEditorVector3FieldHitTargetKind activeTarget = state.vector3FieldState.activeTarget; - const std::size_t activeComponentIndex = state.vector3FieldState.activeComponentIndex; - state.vector3FieldState.activeTarget = UIEditorVector3FieldHitTargetKind::None; - state.vector3FieldState.activeComponentIndex = UIEditorVector3FieldInvalidComponentIndex; - - if (activeTarget == UIEditorVector3FieldHitTargetKind::Component && - hitTarget.kind == UIEditorVector3FieldHitTargetKind::Component && - activeComponentIndex == hitTarget.componentIndex) { - SelectComponent(state, hitTarget.componentIndex, eventResult); - if (!state.vector3FieldState.editing) { - eventResult.editStarted = - BeginEdit(state, spec, hitTarget.componentIndex, false); - } - eventResult.consumed = true; - } else if (hitTarget.kind == UIEditorVector3FieldHitTargetKind::Row) { - eventResult.consumed = true; - } - } - break; - } - - case UIInputEventType::KeyDown: - if (!state.vector3FieldState.focused) { - break; - } - - if (state.vector3FieldState.editing) { - if (event.keyCode == static_cast(KeyCode::Escape)) { - CancelEdit(state, spec, eventResult); - break; - } - - if (event.keyCode == static_cast(KeyCode::Tab)) { - if (CommitEdit(state, spec, eventResult)) { - MoveSelection( - state, - event.modifiers.shift ? -1 : 1, - eventResult); - } - eventResult.consumed = true; - break; - } - - const auto textResult = - HandleKeyDown(state.textInputState, event.keyCode, event.modifiers); - if (textResult.handled) { - state.editModel.UpdateStagedValue(state.textInputState.value); - state.vector3FieldState.displayTexts[state.vector3FieldState.selectedComponentIndex] = - state.textInputState.value; - eventResult.consumed = true; - eventResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; - if (textResult.submitRequested) { - CommitEdit(state, spec, eventResult); - } - break; - } - } else { - switch (static_cast(event.keyCode)) { - case KeyCode::Left: - MoveSelection(state, -1, eventResult); - break; - - case KeyCode::Right: - case KeyCode::Tab: - MoveSelection( - state, - static_cast(event.keyCode) == KeyCode::Tab && event.modifiers.shift ? -1 : 1, - eventResult); - break; - - case KeyCode::Up: - ApplyStep(state, spec, 1.0, false, eventResult); - break; - - case KeyCode::Down: - ApplyStep(state, spec, -1.0, false, eventResult); - break; - - case KeyCode::Home: - ApplyStep(state, spec, -1.0, true, eventResult); - break; - - case KeyCode::End: - ApplyStep(state, spec, 1.0, true, eventResult); - break; - - case KeyCode::Enter: - eventResult.selectedComponentIndex = ResolveFallbackSelectedComponentIndex(state); - eventResult.editStarted = BeginEdit( - state, - spec, - eventResult.selectedComponentIndex, - false); - eventResult.consumed = eventResult.editStarted; - break; - - default: - break; - } - } - break; - - case UIInputEventType::Character: - if (!state.vector3FieldState.focused || - spec.readOnly || - event.modifiers.control || - event.modifiers.alt || - event.modifiers.super || - !IsPermittedCharacter(spec, event.character)) { - break; - } - - if (state.vector3FieldState.selectedComponentIndex == - UIEditorVector3FieldInvalidComponentIndex) { - state.vector3FieldState.selectedComponentIndex = 0u; - } - eventResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; - - if (!state.vector3FieldState.editing) { - eventResult.editStarted = BeginEdit( - state, - spec, - state.vector3FieldState.selectedComponentIndex, - true); - } - - if (InsertCharacter(state.textInputState, event.character)) { - state.editModel.UpdateStagedValue(state.textInputState.value); - state.vector3FieldState.displayTexts[state.vector3FieldState.selectedComponentIndex] = - state.textInputState.value; - eventResult.consumed = true; - } - break; - - default: - break; - } - - layout = BuildUIEditorVector3FieldLayout(bounds, spec, metrics); - SyncDisplayTexts(state, spec); - SyncHoverTarget(state, layout); - if (eventResult.hitTarget.kind == UIEditorVector3FieldHitTargetKind::None && - state.hasPointerPosition) { - eventResult.hitTarget = HitTestUIEditorVector3Field(layout, state.pointerPosition); - } - if (eventResult.selectedComponentIndex == UIEditorVector3FieldInvalidComponentIndex) { - eventResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; - } - - if (eventResult.consumed || - eventResult.focusChanged || - eventResult.valueChanged || - eventResult.stepApplied || - eventResult.selectionChanged || - eventResult.editStarted || - eventResult.editCommitted || - eventResult.editCommitRejected || - eventResult.editCanceled || - eventResult.hitTarget.kind != UIEditorVector3FieldHitTargetKind::None) { - interactionResult = std::move(eventResult); - } - } - - layout = BuildUIEditorVector3FieldLayout(bounds, spec, metrics); - SyncDisplayTexts(state, spec); - SyncHoverTarget(state, layout); - if (interactionResult.hitTarget.kind == UIEditorVector3FieldHitTargetKind::None && - state.hasPointerPosition) { - interactionResult.hitTarget = HitTestUIEditorVector3Field(layout, state.pointerPosition); - } - if (interactionResult.selectedComponentIndex == UIEditorVector3FieldInvalidComponentIndex) { - interactionResult.selectedComponentIndex = state.vector3FieldState.selectedComponentIndex; - } - + UIEditorVector3FieldLayout layout = {}; + UIEditorVector3FieldInteractionResult result = + Internal::UpdateVectorFieldInteractionImpl( + state, + spec, + bounds, + inputEvents, + metrics, + layout); return { std::move(layout), - std::move(interactionResult) + std::move(result) }; } diff --git a/new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp b/new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp index bee7fcaf..fc82db4b 100644 --- a/new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp @@ -1,6 +1,6 @@ #include -#include +#include "Fields/VectorFieldInteractionInternal.h" #include @@ -8,12 +8,6 @@ namespace XCEngine::UI::Editor { namespace { -using ::XCEngine::Input::KeyCode; -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPointerButton; -using ::XCEngine::UI::Text::HandleKeyDown; -using ::XCEngine::UI::Text::InsertCharacter; using ::XCEngine::UI::Editor::Widgets::BuildUIEditorVector4FieldLayout; using ::XCEngine::UI::Editor::Widgets::FormatUIEditorVector4FieldComponentValue; using ::XCEngine::UI::Editor::Widgets::HitTestUIEditorVector4Field; @@ -27,250 +21,49 @@ using ::XCEngine::UI::Editor::Widgets::UIEditorVector4FieldLayout; using ::XCEngine::UI::Editor::Widgets::UIEditorVector4FieldMetrics; using ::XCEngine::UI::Editor::Widgets::UIEditorVector4FieldSpec; -bool ShouldUsePointerPosition(const UIInputEvent& event) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - return true; - default: - return false; - } -} +struct Vector4FieldInteractionTraits { + using InteractionState = UIEditorVector4FieldInteractionState; + using InteractionResult = UIEditorVector4FieldInteractionResult; + using Spec = UIEditorVector4FieldSpec; + using Layout = UIEditorVector4FieldLayout; + using Metrics = UIEditorVector4FieldMetrics; + using HitTarget = UIEditorVector4FieldHitTarget; + using HitTargetKind = UIEditorVector4FieldHitTargetKind; -std::size_t ResolveFallbackSelectedComponentIndex( - const UIEditorVector4FieldInteractionState& state) { - return state.vector4FieldState.selectedComponentIndex == - UIEditorVector4FieldInvalidComponentIndex - ? 0u - : state.vector4FieldState.selectedComponentIndex; -} + static constexpr std::size_t kComponentCount = 4u; + static constexpr std::size_t kLastComponentIndex = 3u; + static constexpr std::size_t kInvalidComponentIndex = UIEditorVector4FieldInvalidComponentIndex; + static constexpr HitTargetKind kNoneHitTargetKind = UIEditorVector4FieldHitTargetKind::None; + static constexpr HitTargetKind kRowHitTargetKind = UIEditorVector4FieldHitTargetKind::Row; + static constexpr HitTargetKind kComponentHitTargetKind = UIEditorVector4FieldHitTargetKind::Component; -std::string BuildComponentEditFieldId( - const UIEditorVector4FieldSpec& spec, - std::size_t componentIndex) { - return spec.fieldId + "." + std::to_string(componentIndex); -} + static auto& FieldState(InteractionState& state) { return state.vector4FieldState; } + static const auto& FieldState(const InteractionState& state) { return state.vector4FieldState; } -bool IsPermittedCharacter( - const UIEditorVector4FieldSpec& spec, - std::uint32_t character) { - if (character >= static_cast('0') && - character <= static_cast('9')) { - return true; + static Layout BuildLayout(const ::XCEngine::UI::UIRect& bounds, const Spec& spec, const Metrics& metrics) { + return BuildUIEditorVector4FieldLayout(bounds, spec, metrics); } - if (character == static_cast('-') || - character == static_cast('+')) { - return true; + static HitTarget HitTest(const Layout& layout, const ::XCEngine::UI::UIPoint& point) { + return HitTestUIEditorVector4Field(layout, point); } - return !spec.integerMode && character == static_cast('.'); -} - -void SyncDisplayTexts( - UIEditorVector4FieldInteractionState& state, - const UIEditorVector4FieldSpec& spec) { - for (std::size_t componentIndex = 0u; - componentIndex < state.vector4FieldState.displayTexts.size(); - ++componentIndex) { - if (state.vector4FieldState.editing && - state.vector4FieldState.selectedComponentIndex == componentIndex) { - continue; - } - - state.vector4FieldState.displayTexts[componentIndex] = - FormatUIEditorVector4FieldComponentValue(spec, componentIndex); - } -} - -void SyncHoverTarget( - UIEditorVector4FieldInteractionState& state, - const UIEditorVector4FieldLayout& layout) { - if (!state.hasPointerPosition) { - state.vector4FieldState.hoveredTarget = UIEditorVector4FieldHitTargetKind::None; - state.vector4FieldState.hoveredComponentIndex = UIEditorVector4FieldInvalidComponentIndex; - return; + static bool IsPointInside(const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIPoint& point) { + return IsUIEditorVector4FieldPointInside(rect, point); } - const UIEditorVector4FieldHitTarget hitTarget = - HitTestUIEditorVector4Field(layout, state.pointerPosition); - state.vector4FieldState.hoveredTarget = hitTarget.kind; - state.vector4FieldState.hoveredComponentIndex = hitTarget.componentIndex; -} - -bool MoveSelection( - UIEditorVector4FieldInteractionState& state, - int direction, - UIEditorVector4FieldInteractionResult& result) { - const std::size_t before = ResolveFallbackSelectedComponentIndex(state); - const std::size_t after = - direction < 0 - ? (before == 0u ? 0u : before - 1u) - : (before >= 3u ? 3u : before + 1u); - state.vector4FieldState.selectedComponentIndex = after; - result.selectionChanged = before != after; - result.selectedComponentIndex = after; - result.consumed = true; - return true; -} - -bool SelectComponent( - UIEditorVector4FieldInteractionState& state, - std::size_t componentIndex, - UIEditorVector4FieldInteractionResult& result) { - if (componentIndex >= 4u) { - return false; + static std::string FormatComponentValue(const Spec& spec, std::size_t componentIndex) { + return FormatUIEditorVector4FieldComponentValue(spec, componentIndex); } - const std::size_t before = ResolveFallbackSelectedComponentIndex(state); - state.vector4FieldState.selectedComponentIndex = componentIndex; - result.selectionChanged = before != componentIndex; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool BeginEdit( - UIEditorVector4FieldInteractionState& state, - const UIEditorVector4FieldSpec& spec, - std::size_t componentIndex, - bool clearText) { - if (spec.readOnly || componentIndex >= spec.values.size()) { - return false; + static bool TryParseComponentValue(const Spec& spec, std::string_view text, double& outValue) { + return TryParseUIEditorVector4FieldComponentValue(spec, text, outValue); } - const std::string baseline = - FormatUIEditorVector4FieldComponentValue(spec, componentIndex); - const std::string editFieldId = - BuildComponentEditFieldId(spec, componentIndex); - const bool changed = state.editModel.BeginEdit(editFieldId, baseline); - if (!changed && - state.editModel.HasActiveEdit() && - state.editModel.GetActiveFieldId() != editFieldId) { - return false; + static double NormalizeComponentValue(const Spec& spec, double value) { + return NormalizeUIEditorVector4FieldComponentValue(spec, value); } - if (!changed && - state.vector4FieldState.editing && - state.vector4FieldState.selectedComponentIndex == componentIndex) { - return false; - } - - state.vector4FieldState.selectedComponentIndex = componentIndex; - state.vector4FieldState.editing = true; - state.textInputState.value = clearText ? std::string() : baseline; - state.textInputState.caret = state.textInputState.value.size(); - state.editModel.UpdateStagedValue(state.textInputState.value); - state.vector4FieldState.displayTexts[componentIndex] = state.textInputState.value; - return true; -} - -bool CommitEdit( - UIEditorVector4FieldInteractionState& state, - UIEditorVector4FieldSpec& spec, - UIEditorVector4FieldInteractionResult& result) { - if (!state.vector4FieldState.editing || - !state.editModel.HasActiveEdit() || - state.vector4FieldState.selectedComponentIndex >= spec.values.size()) { - return false; - } - - const std::size_t componentIndex = state.vector4FieldState.selectedComponentIndex; - double parsedValue = spec.values[componentIndex]; - if (!TryParseUIEditorVector4FieldComponentValue( - spec, - state.textInputState.value, - parsedValue)) { - result.consumed = true; - result.editCommitRejected = true; - return false; - } - - result.valuesBefore = spec.values; - spec.values[componentIndex] = NormalizeUIEditorVector4FieldComponentValue(spec, parsedValue); - result.valuesAfter = spec.values; - result.valueChanged = result.valuesBefore != result.valuesAfter; - result.editCommitted = true; - result.consumed = true; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.committedText = - FormatUIEditorVector4FieldComponentValue(spec, componentIndex); - - state.editModel.CommitEdit(); - state.textInputState = {}; - state.vector4FieldState.editing = false; - state.vector4FieldState.displayTexts[componentIndex] = result.committedText; - return true; -} - -bool CancelEdit( - UIEditorVector4FieldInteractionState& state, - const UIEditorVector4FieldSpec& spec, - UIEditorVector4FieldInteractionResult& result) { - if (!state.vector4FieldState.editing || - !state.editModel.HasActiveEdit() || - state.vector4FieldState.selectedComponentIndex >= spec.values.size()) { - return false; - } - - const std::size_t componentIndex = state.vector4FieldState.selectedComponentIndex; - state.editModel.CancelEdit(); - state.textInputState = {}; - state.vector4FieldState.editing = false; - state.vector4FieldState.displayTexts[componentIndex] = - FormatUIEditorVector4FieldComponentValue(spec, componentIndex); - result.consumed = true; - result.editCanceled = true; - result.valuesBefore = spec.values; - result.valuesAfter = spec.values; - result.selectedComponentIndex = componentIndex; - return true; -} - -bool ApplyStep( - UIEditorVector4FieldInteractionState& state, - UIEditorVector4FieldSpec& spec, - double direction, - bool snapToEdge, - UIEditorVector4FieldInteractionResult& result) { - if (spec.readOnly) { - return false; - } - - const std::size_t componentIndex = ResolveFallbackSelectedComponentIndex(state); - state.vector4FieldState.selectedComponentIndex = componentIndex; - - if (state.vector4FieldState.editing && - !CommitEdit(state, spec, result)) { - return result.editCommitRejected; - } - - result.valuesBefore = spec.values; - if (snapToEdge) { - spec.values[componentIndex] = - direction < 0.0 - ? NormalizeUIEditorVector4FieldComponentValue(spec, spec.minValue) - : NormalizeUIEditorVector4FieldComponentValue(spec, spec.maxValue); - } else { - const double step = spec.step == 0.0 ? 1.0 : spec.step; - spec.values[componentIndex] = NormalizeUIEditorVector4FieldComponentValue( - spec, - spec.values[componentIndex] + step * direction); - result.stepDelta = step * direction; - } - - result.valuesAfter = spec.values; - result.stepApplied = true; - result.valueChanged = result.valuesBefore != result.valuesAfter || result.valueChanged; - result.changedComponentIndex = componentIndex; - result.selectedComponentIndex = componentIndex; - result.consumed = true; - state.vector4FieldState.displayTexts[componentIndex] = - FormatUIEditorVector4FieldComponentValue(spec, componentIndex); - return true; -} +}; } // namespace @@ -280,283 +73,18 @@ UIEditorVector4FieldInteractionFrame UpdateUIEditorVector4FieldInteraction( const ::XCEngine::UI::UIRect& bounds, const std::vector& inputEvents, const UIEditorVector4FieldMetrics& metrics) { - UIEditorVector4FieldLayout layout = BuildUIEditorVector4FieldLayout(bounds, spec, metrics); - SyncDisplayTexts(state, spec); - SyncHoverTarget(state, layout); - - UIEditorVector4FieldInteractionResult interactionResult = {}; - interactionResult.selectedComponentIndex = state.vector4FieldState.selectedComponentIndex; - for (const UIInputEvent& event : inputEvents) { - if (ShouldUsePointerPosition(event)) { - state.pointerPosition = event.position; - state.hasPointerPosition = true; - } else if (event.type == UIInputEventType::PointerLeave) { - state.hasPointerPosition = false; - } - - UIEditorVector4FieldInteractionResult eventResult = {}; - eventResult.selectedComponentIndex = state.vector4FieldState.selectedComponentIndex; - switch (event.type) { - case UIInputEventType::FocusGained: - eventResult.focusChanged = !state.vector4FieldState.focused; - state.vector4FieldState.focused = true; - if (state.vector4FieldState.selectedComponentIndex == - UIEditorVector4FieldInvalidComponentIndex) { - state.vector4FieldState.selectedComponentIndex = 0u; - } - eventResult.selectedComponentIndex = state.vector4FieldState.selectedComponentIndex; - break; - - case UIInputEventType::FocusLost: - eventResult.focusChanged = state.vector4FieldState.focused; - state.vector4FieldState.focused = false; - state.vector4FieldState.activeTarget = UIEditorVector4FieldHitTargetKind::None; - state.vector4FieldState.activeComponentIndex = UIEditorVector4FieldInvalidComponentIndex; - state.hasPointerPosition = false; - if (state.vector4FieldState.editing) { - CommitEdit(state, spec, eventResult); - if (eventResult.editCommitRejected) { - CancelEdit(state, spec, eventResult); - } - } - break; - - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerLeave: - break; - - case UIInputEventType::PointerButtonDown: { - const UIEditorVector4FieldHitTarget hitTarget = - state.hasPointerPosition - ? HitTestUIEditorVector4Field(layout, state.pointerPosition) - : UIEditorVector4FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton != UIPointerButton::Left) { - break; - } - - const bool insideField = - state.hasPointerPosition && - IsUIEditorVector4FieldPointInside(layout.bounds, state.pointerPosition); - if (insideField) { - eventResult.focusChanged = !state.vector4FieldState.focused; - state.vector4FieldState.focused = true; - state.vector4FieldState.activeTarget = hitTarget.kind == UIEditorVector4FieldHitTargetKind::None - ? UIEditorVector4FieldHitTargetKind::Row - : hitTarget.kind; - state.vector4FieldState.activeComponentIndex = hitTarget.componentIndex; - if (hitTarget.kind == UIEditorVector4FieldHitTargetKind::Component) { - SelectComponent(state, hitTarget.componentIndex, eventResult); - } else if (state.vector4FieldState.selectedComponentIndex == - UIEditorVector4FieldInvalidComponentIndex) { - state.vector4FieldState.selectedComponentIndex = 0u; - eventResult.selectedComponentIndex = 0u; - } - eventResult.consumed = true; - } else { - if (state.vector4FieldState.editing) { - CommitEdit(state, spec, eventResult); - if (!eventResult.editCommitRejected) { - eventResult.focusChanged = state.vector4FieldState.focused; - state.vector4FieldState.focused = false; - } - } else if (state.vector4FieldState.focused) { - eventResult.focusChanged = true; - state.vector4FieldState.focused = false; - } - state.vector4FieldState.activeTarget = UIEditorVector4FieldHitTargetKind::None; - state.vector4FieldState.activeComponentIndex = UIEditorVector4FieldInvalidComponentIndex; - } - break; - } - - case UIInputEventType::PointerButtonUp: { - const UIEditorVector4FieldHitTarget hitTarget = - state.hasPointerPosition - ? HitTestUIEditorVector4Field(layout, state.pointerPosition) - : UIEditorVector4FieldHitTarget {}; - eventResult.hitTarget = hitTarget; - - if (event.pointerButton == UIPointerButton::Left) { - const UIEditorVector4FieldHitTargetKind activeTarget = state.vector4FieldState.activeTarget; - const std::size_t activeComponentIndex = state.vector4FieldState.activeComponentIndex; - state.vector4FieldState.activeTarget = UIEditorVector4FieldHitTargetKind::None; - state.vector4FieldState.activeComponentIndex = UIEditorVector4FieldInvalidComponentIndex; - - if (activeTarget == UIEditorVector4FieldHitTargetKind::Component && - hitTarget.kind == UIEditorVector4FieldHitTargetKind::Component && - activeComponentIndex == hitTarget.componentIndex) { - SelectComponent(state, hitTarget.componentIndex, eventResult); - if (!state.vector4FieldState.editing) { - eventResult.editStarted = - BeginEdit(state, spec, hitTarget.componentIndex, false); - } - eventResult.consumed = true; - } else if (hitTarget.kind == UIEditorVector4FieldHitTargetKind::Row) { - eventResult.consumed = true; - } - } - break; - } - - case UIInputEventType::KeyDown: - if (!state.vector4FieldState.focused) { - break; - } - - if (state.vector4FieldState.editing) { - if (event.keyCode == static_cast(KeyCode::Escape)) { - CancelEdit(state, spec, eventResult); - break; - } - - if (event.keyCode == static_cast(KeyCode::Tab)) { - if (CommitEdit(state, spec, eventResult)) { - MoveSelection( - state, - event.modifiers.shift ? -1 : 1, - eventResult); - } - eventResult.consumed = true; - break; - } - - const auto textResult = - HandleKeyDown(state.textInputState, event.keyCode, event.modifiers); - if (textResult.handled) { - state.editModel.UpdateStagedValue(state.textInputState.value); - state.vector4FieldState.displayTexts[state.vector4FieldState.selectedComponentIndex] = - state.textInputState.value; - eventResult.consumed = true; - eventResult.selectedComponentIndex = state.vector4FieldState.selectedComponentIndex; - if (textResult.submitRequested) { - CommitEdit(state, spec, eventResult); - } - break; - } - } else { - switch (static_cast(event.keyCode)) { - case KeyCode::Left: - MoveSelection(state, -1, eventResult); - break; - - case KeyCode::Right: - case KeyCode::Tab: - MoveSelection( - state, - static_cast(event.keyCode) == KeyCode::Tab && event.modifiers.shift ? -1 : 1, - eventResult); - break; - - case KeyCode::Up: - ApplyStep(state, spec, 1.0, false, eventResult); - break; - - case KeyCode::Down: - ApplyStep(state, spec, -1.0, false, eventResult); - break; - - case KeyCode::Home: - ApplyStep(state, spec, -1.0, true, eventResult); - break; - - case KeyCode::End: - ApplyStep(state, spec, 1.0, true, eventResult); - break; - - case KeyCode::Enter: - eventResult.selectedComponentIndex = ResolveFallbackSelectedComponentIndex(state); - eventResult.editStarted = BeginEdit( - state, - spec, - eventResult.selectedComponentIndex, - false); - eventResult.consumed = eventResult.editStarted; - break; - - default: - break; - } - } - break; - - case UIInputEventType::Character: - if (!state.vector4FieldState.focused || - spec.readOnly || - event.modifiers.control || - event.modifiers.alt || - event.modifiers.super || - !IsPermittedCharacter(spec, event.character)) { - break; - } - - if (state.vector4FieldState.selectedComponentIndex == - UIEditorVector4FieldInvalidComponentIndex) { - state.vector4FieldState.selectedComponentIndex = 0u; - } - eventResult.selectedComponentIndex = state.vector4FieldState.selectedComponentIndex; - - if (!state.vector4FieldState.editing) { - eventResult.editStarted = BeginEdit( - state, - spec, - state.vector4FieldState.selectedComponentIndex, - true); - } - - if (InsertCharacter(state.textInputState, event.character)) { - state.editModel.UpdateStagedValue(state.textInputState.value); - state.vector4FieldState.displayTexts[state.vector4FieldState.selectedComponentIndex] = - state.textInputState.value; - eventResult.consumed = true; - } - break; - - default: - break; - } - - layout = BuildUIEditorVector4FieldLayout(bounds, spec, metrics); - SyncDisplayTexts(state, spec); - SyncHoverTarget(state, layout); - if (eventResult.hitTarget.kind == UIEditorVector4FieldHitTargetKind::None && - state.hasPointerPosition) { - eventResult.hitTarget = HitTestUIEditorVector4Field(layout, state.pointerPosition); - } - if (eventResult.selectedComponentIndex == UIEditorVector4FieldInvalidComponentIndex) { - eventResult.selectedComponentIndex = state.vector4FieldState.selectedComponentIndex; - } - - if (eventResult.consumed || - eventResult.focusChanged || - eventResult.valueChanged || - eventResult.stepApplied || - eventResult.selectionChanged || - eventResult.editStarted || - eventResult.editCommitted || - eventResult.editCommitRejected || - eventResult.editCanceled || - eventResult.hitTarget.kind != UIEditorVector4FieldHitTargetKind::None) { - interactionResult = std::move(eventResult); - } - } - - layout = BuildUIEditorVector4FieldLayout(bounds, spec, metrics); - SyncDisplayTexts(state, spec); - SyncHoverTarget(state, layout); - if (interactionResult.hitTarget.kind == UIEditorVector4FieldHitTargetKind::None && - state.hasPointerPosition) { - interactionResult.hitTarget = HitTestUIEditorVector4Field(layout, state.pointerPosition); - } - if (interactionResult.selectedComponentIndex == UIEditorVector4FieldInvalidComponentIndex) { - interactionResult.selectedComponentIndex = state.vector4FieldState.selectedComponentIndex; - } - + UIEditorVector4FieldLayout layout = {}; + UIEditorVector4FieldInteractionResult result = + Internal::UpdateVectorFieldInteractionImpl( + state, + spec, + bounds, + inputEvents, + metrics, + layout); return { std::move(layout), - std::move(interactionResult) + std::move(result) }; } diff --git a/new_editor/src/Fields/VectorFieldInteractionInternal.h b/new_editor/src/Fields/VectorFieldInteractionInternal.h new file mode 100644 index 00000000..4f747f6e --- /dev/null +++ b/new_editor/src/Fields/VectorFieldInteractionInternal.h @@ -0,0 +1,463 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::Internal { + +using ::XCEngine::Input::KeyCode; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPointerButton; +using ::XCEngine::UI::Text::HandleKeyDown; +using ::XCEngine::UI::Text::InsertCharacter; + +inline bool ShouldUseVectorFieldPointerPosition(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + return true; + default: + return false; + } +} + +template +std::size_t ResolveFallbackSelectedComponentIndex(const typename Traits::InteractionState& state) { + const auto& fieldState = Traits::FieldState(state); + return fieldState.selectedComponentIndex == Traits::kInvalidComponentIndex + ? 0u + : fieldState.selectedComponentIndex; +} + +template +std::string BuildVectorFieldComponentEditFieldId( + const typename Traits::Spec& spec, + std::size_t componentIndex) { + return spec.fieldId + "." + std::to_string(componentIndex); +} + +template +bool IsPermittedVectorFieldCharacter(const typename Traits::Spec& spec, std::uint32_t character) { + if (character >= static_cast('0') && + character <= static_cast('9')) { + return true; + } + if (character == static_cast('-') || + character == static_cast('+')) { + return true; + } + return !spec.integerMode && character == static_cast('.'); +} + +template +void SyncVectorFieldDisplayTexts(typename Traits::InteractionState& state, const typename Traits::Spec& spec) { + auto& fieldState = Traits::FieldState(state); + for (std::size_t componentIndex = 0u; componentIndex < fieldState.displayTexts.size(); ++componentIndex) { + if (fieldState.editing && fieldState.selectedComponentIndex == componentIndex) { + continue; + } + fieldState.displayTexts[componentIndex] = Traits::FormatComponentValue(spec, componentIndex); + } +} + +template +void SyncVectorFieldHoverTarget(typename Traits::InteractionState& state, const typename Traits::Layout& layout) { + auto& fieldState = Traits::FieldState(state); + if (!state.hasPointerPosition) { + fieldState.hoveredTarget = Traits::kNoneHitTargetKind; + fieldState.hoveredComponentIndex = Traits::kInvalidComponentIndex; + return; + } + const typename Traits::HitTarget hitTarget = Traits::HitTest(layout, state.pointerPosition); + fieldState.hoveredTarget = hitTarget.kind; + fieldState.hoveredComponentIndex = hitTarget.componentIndex; +} + +template +bool MoveVectorFieldSelection( + typename Traits::InteractionState& state, + int direction, + typename Traits::InteractionResult& result) { + auto& fieldState = Traits::FieldState(state); + const std::size_t before = ResolveFallbackSelectedComponentIndex(state); + const std::size_t after = direction < 0 + ? (before == 0u ? 0u : before - 1u) + : (before >= Traits::kLastComponentIndex ? Traits::kLastComponentIndex : before + 1u); + fieldState.selectedComponentIndex = after; + result.selectionChanged = before != after; + result.selectedComponentIndex = after; + result.consumed = true; + return true; +} + +template +bool SelectVectorFieldComponent( + typename Traits::InteractionState& state, + std::size_t componentIndex, + typename Traits::InteractionResult& result) { + if (componentIndex >= Traits::kComponentCount) { + return false; + } + auto& fieldState = Traits::FieldState(state); + const std::size_t before = ResolveFallbackSelectedComponentIndex(state); + fieldState.selectedComponentIndex = componentIndex; + result.selectionChanged = before != componentIndex; + result.selectedComponentIndex = componentIndex; + return true; +} + +template +bool BeginVectorFieldEdit( + typename Traits::InteractionState& state, + const typename Traits::Spec& spec, + std::size_t componentIndex, + bool clearText) { + auto& fieldState = Traits::FieldState(state); + if (spec.readOnly || componentIndex >= spec.values.size()) { + return false; + } + const std::string baseline = Traits::FormatComponentValue(spec, componentIndex); + const std::string editFieldId = BuildVectorFieldComponentEditFieldId(spec, componentIndex); + const bool changed = state.editModel.BeginEdit(editFieldId, baseline); + if (!changed && state.editModel.HasActiveEdit() && state.editModel.GetActiveFieldId() != editFieldId) { + return false; + } + if (!changed && fieldState.editing && fieldState.selectedComponentIndex == componentIndex) { + return false; + } + fieldState.selectedComponentIndex = componentIndex; + fieldState.editing = true; + state.textInputState.value = clearText ? std::string() : baseline; + state.textInputState.caret = state.textInputState.value.size(); + state.editModel.UpdateStagedValue(state.textInputState.value); + fieldState.displayTexts[componentIndex] = state.textInputState.value; + return true; +} + +template +bool CommitVectorFieldEdit( + typename Traits::InteractionState& state, + typename Traits::Spec& spec, + typename Traits::InteractionResult& result) { + auto& fieldState = Traits::FieldState(state); + if (!fieldState.editing || !state.editModel.HasActiveEdit() || fieldState.selectedComponentIndex >= spec.values.size()) { + return false; + } + const std::size_t componentIndex = fieldState.selectedComponentIndex; + double parsedValue = spec.values[componentIndex]; + if (!Traits::TryParseComponentValue(spec, state.textInputState.value, parsedValue)) { + result.consumed = true; + result.editCommitRejected = true; + return false; + } + result.valuesBefore = spec.values; + spec.values[componentIndex] = Traits::NormalizeComponentValue(spec, parsedValue); + result.valuesAfter = spec.values; + result.valueChanged = result.valuesBefore != result.valuesAfter; + result.editCommitted = true; + result.consumed = true; + result.changedComponentIndex = componentIndex; + result.selectedComponentIndex = componentIndex; + result.committedText = Traits::FormatComponentValue(spec, componentIndex); + state.editModel.CommitEdit(); + state.textInputState = {}; + fieldState.editing = false; + fieldState.displayTexts[componentIndex] = result.committedText; + return true; +} + +template +bool CancelVectorFieldEdit( + typename Traits::InteractionState& state, + const typename Traits::Spec& spec, + typename Traits::InteractionResult& result) { + auto& fieldState = Traits::FieldState(state); + if (!fieldState.editing || !state.editModel.HasActiveEdit() || fieldState.selectedComponentIndex >= spec.values.size()) { + return false; + } + const std::size_t componentIndex = fieldState.selectedComponentIndex; + state.editModel.CancelEdit(); + state.textInputState = {}; + fieldState.editing = false; + fieldState.displayTexts[componentIndex] = Traits::FormatComponentValue(spec, componentIndex); + result.consumed = true; + result.editCanceled = true; + result.valuesBefore = spec.values; + result.valuesAfter = spec.values; + result.selectedComponentIndex = componentIndex; + return true; +} + +template +bool ApplyVectorFieldStep( + typename Traits::InteractionState& state, + typename Traits::Spec& spec, + double direction, + bool snapToEdge, + typename Traits::InteractionResult& result) { + auto& fieldState = Traits::FieldState(state); + if (spec.readOnly) { + return false; + } + const std::size_t componentIndex = ResolveFallbackSelectedComponentIndex(state); + fieldState.selectedComponentIndex = componentIndex; + if (fieldState.editing && !CommitVectorFieldEdit(state, spec, result)) { + return result.editCommitRejected; + } + result.valuesBefore = spec.values; + if (snapToEdge) { + spec.values[componentIndex] = direction < 0.0 + ? Traits::NormalizeComponentValue(spec, spec.minValue) + : Traits::NormalizeComponentValue(spec, spec.maxValue); + } else { + const double step = spec.step == 0.0 ? 1.0 : spec.step; + spec.values[componentIndex] = Traits::NormalizeComponentValue(spec, spec.values[componentIndex] + step * direction); + result.stepDelta = step * direction; + } + result.valuesAfter = spec.values; + result.stepApplied = true; + result.valueChanged = result.valuesBefore != result.valuesAfter || result.valueChanged; + result.changedComponentIndex = componentIndex; + result.selectedComponentIndex = componentIndex; + result.consumed = true; + fieldState.displayTexts[componentIndex] = Traits::FormatComponentValue(spec, componentIndex); + return true; +} + +template +typename Traits::InteractionResult UpdateVectorFieldInteractionImpl( + typename Traits::InteractionState& state, + typename Traits::Spec& spec, + const ::XCEngine::UI::UIRect& bounds, + const std::vector& inputEvents, + const typename Traits::Metrics& metrics, + typename Traits::Layout& layout) { + auto refresh = [&]() { + layout = Traits::BuildLayout(bounds, spec, metrics); + SyncVectorFieldDisplayTexts(state, spec); + SyncVectorFieldHoverTarget(state, layout); + }; + auto makeResult = [&]() { + typename Traits::InteractionResult result = {}; + result.selectedComponentIndex = Traits::FieldState(state).selectedComponentIndex; + return result; + }; + auto shouldPublish = [&](const typename Traits::InteractionResult& result) { + return result.consumed || result.focusChanged || result.valueChanged || result.stepApplied || + result.selectionChanged || result.editStarted || result.editCommitted || + result.editCommitRejected || result.editCanceled || + result.hitTarget.kind != Traits::kNoneHitTargetKind; + }; + + refresh(); + typename Traits::InteractionResult interactionResult = makeResult(); + for (const UIInputEvent& event : inputEvents) { + if (ShouldUseVectorFieldPointerPosition(event)) { + state.pointerPosition = event.position; + state.hasPointerPosition = true; + } else if (event.type == UIInputEventType::PointerLeave) { + state.hasPointerPosition = false; + } + + auto& fieldState = Traits::FieldState(state); + typename Traits::InteractionResult eventResult = makeResult(); + switch (event.type) { + case UIInputEventType::FocusGained: + eventResult.focusChanged = !fieldState.focused; + fieldState.focused = true; + if (fieldState.selectedComponentIndex == Traits::kInvalidComponentIndex) { + fieldState.selectedComponentIndex = 0u; + } + eventResult.selectedComponentIndex = fieldState.selectedComponentIndex; + break; + case UIInputEventType::FocusLost: + eventResult.focusChanged = fieldState.focused; + fieldState.focused = false; + fieldState.activeTarget = Traits::kNoneHitTargetKind; + fieldState.activeComponentIndex = Traits::kInvalidComponentIndex; + state.hasPointerPosition = false; + if (fieldState.editing) { + CommitVectorFieldEdit(state, spec, eventResult); + if (eventResult.editCommitRejected) { + CancelVectorFieldEdit(state, spec, eventResult); + } + } + break; + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + break; + case UIInputEventType::PointerButtonDown: { + const typename Traits::HitTarget hitTarget = state.hasPointerPosition + ? Traits::HitTest(layout, state.pointerPosition) + : typename Traits::HitTarget {}; + eventResult.hitTarget = hitTarget; + if (event.pointerButton != UIPointerButton::Left) { + break; + } + const bool insideField = state.hasPointerPosition && Traits::IsPointInside(layout.bounds, state.pointerPosition); + if (insideField) { + eventResult.focusChanged = !fieldState.focused; + fieldState.focused = true; + fieldState.activeTarget = hitTarget.kind == Traits::kNoneHitTargetKind ? Traits::kRowHitTargetKind : hitTarget.kind; + fieldState.activeComponentIndex = hitTarget.componentIndex; + if (hitTarget.kind == Traits::kComponentHitTargetKind) { + SelectVectorFieldComponent(state, hitTarget.componentIndex, eventResult); + } else if (fieldState.selectedComponentIndex == Traits::kInvalidComponentIndex) { + fieldState.selectedComponentIndex = 0u; + eventResult.selectedComponentIndex = 0u; + } + eventResult.consumed = true; + } else { + if (fieldState.editing) { + CommitVectorFieldEdit(state, spec, eventResult); + if (!eventResult.editCommitRejected) { + eventResult.focusChanged = fieldState.focused; + fieldState.focused = false; + } + } else if (fieldState.focused) { + eventResult.focusChanged = true; + fieldState.focused = false; + } + fieldState.activeTarget = Traits::kNoneHitTargetKind; + fieldState.activeComponentIndex = Traits::kInvalidComponentIndex; + } + break; + } + case UIInputEventType::PointerButtonUp: { + const typename Traits::HitTarget hitTarget = state.hasPointerPosition + ? Traits::HitTest(layout, state.pointerPosition) + : typename Traits::HitTarget {}; + eventResult.hitTarget = hitTarget; + if (event.pointerButton == UIPointerButton::Left) { + const typename Traits::HitTargetKind activeTarget = fieldState.activeTarget; + const std::size_t activeComponentIndex = fieldState.activeComponentIndex; + fieldState.activeTarget = Traits::kNoneHitTargetKind; + fieldState.activeComponentIndex = Traits::kInvalidComponentIndex; + if (activeTarget == Traits::kComponentHitTargetKind && + hitTarget.kind == Traits::kComponentHitTargetKind && + activeComponentIndex == hitTarget.componentIndex) { + SelectVectorFieldComponent(state, hitTarget.componentIndex, eventResult); + if (!fieldState.editing) { + eventResult.editStarted = BeginVectorFieldEdit(state, spec, hitTarget.componentIndex, false); + } + eventResult.consumed = true; + } else if (hitTarget.kind == Traits::kRowHitTargetKind) { + eventResult.consumed = true; + } + } + break; + } + case UIInputEventType::KeyDown: + if (!fieldState.focused) { + break; + } + if (fieldState.editing) { + if (event.keyCode == static_cast(KeyCode::Escape)) { + CancelVectorFieldEdit(state, spec, eventResult); + break; + } + if (event.keyCode == static_cast(KeyCode::Tab)) { + if (CommitVectorFieldEdit(state, spec, eventResult)) { + MoveVectorFieldSelection(state, event.modifiers.shift ? -1 : 1, eventResult); + } + eventResult.consumed = true; + break; + } + const auto textResult = HandleKeyDown(state.textInputState, event.keyCode, event.modifiers); + if (textResult.handled) { + state.editModel.UpdateStagedValue(state.textInputState.value); + fieldState.displayTexts[fieldState.selectedComponentIndex] = state.textInputState.value; + eventResult.consumed = true; + eventResult.selectedComponentIndex = fieldState.selectedComponentIndex; + if (textResult.submitRequested) { + CommitVectorFieldEdit(state, spec, eventResult); + } + break; + } + } else { + switch (static_cast(event.keyCode)) { + case KeyCode::Left: + MoveVectorFieldSelection(state, -1, eventResult); + break; + case KeyCode::Right: + case KeyCode::Tab: + MoveVectorFieldSelection( + state, + static_cast(event.keyCode) == KeyCode::Tab && event.modifiers.shift ? -1 : 1, + eventResult); + break; + case KeyCode::Up: + ApplyVectorFieldStep(state, spec, 1.0, false, eventResult); + break; + case KeyCode::Down: + ApplyVectorFieldStep(state, spec, -1.0, false, eventResult); + break; + case KeyCode::Home: + ApplyVectorFieldStep(state, spec, -1.0, true, eventResult); + break; + case KeyCode::End: + ApplyVectorFieldStep(state, spec, 1.0, true, eventResult); + break; + case KeyCode::Enter: + eventResult.selectedComponentIndex = ResolveFallbackSelectedComponentIndex(state); + eventResult.editStarted = BeginVectorFieldEdit(state, spec, eventResult.selectedComponentIndex, false); + eventResult.consumed = eventResult.editStarted; + break; + default: + break; + } + } + break; + case UIInputEventType::Character: + if (!fieldState.focused || spec.readOnly || event.modifiers.control || event.modifiers.alt || + event.modifiers.super || !IsPermittedVectorFieldCharacter(spec, event.character)) { + break; + } + if (fieldState.selectedComponentIndex == Traits::kInvalidComponentIndex) { + fieldState.selectedComponentIndex = 0u; + } + eventResult.selectedComponentIndex = fieldState.selectedComponentIndex; + if (!fieldState.editing) { + eventResult.editStarted = BeginVectorFieldEdit(state, spec, fieldState.selectedComponentIndex, true); + } + if (InsertCharacter(state.textInputState, event.character)) { + state.editModel.UpdateStagedValue(state.textInputState.value); + fieldState.displayTexts[fieldState.selectedComponentIndex] = state.textInputState.value; + eventResult.consumed = true; + } + break; + default: + break; + } + + refresh(); + if (eventResult.hitTarget.kind == Traits::kNoneHitTargetKind && state.hasPointerPosition) { + eventResult.hitTarget = Traits::HitTest(layout, state.pointerPosition); + } + if (eventResult.selectedComponentIndex == Traits::kInvalidComponentIndex) { + eventResult.selectedComponentIndex = fieldState.selectedComponentIndex; + } + if (shouldPublish(eventResult)) { + interactionResult = std::move(eventResult); + } + } + + refresh(); + if (interactionResult.hitTarget.kind == Traits::kNoneHitTargetKind && state.hasPointerPosition) { + interactionResult.hitTarget = Traits::HitTest(layout, state.pointerPosition); + } + if (interactionResult.selectedComponentIndex == Traits::kInvalidComponentIndex) { + interactionResult.selectedComponentIndex = Traits::FieldState(state).selectedComponentIndex; + } + return interactionResult; +} + +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Shell/UIEditorShellInteractionInternal.h b/new_editor/src/Shell/ShellInteractionInternal.h similarity index 95% rename from new_editor/src/Shell/UIEditorShellInteractionInternal.h rename to new_editor/src/Shell/ShellInteractionInternal.h index 68be1308..15e2f630 100644 --- a/new_editor/src/Shell/UIEditorShellInteractionInternal.h +++ b/new_editor/src/Shell/ShellInteractionInternal.h @@ -1,12 +1,10 @@ #pragma once #include - #include - #include -namespace XCEngine::UI::Editor::Detail { +namespace XCEngine::UI::Editor::Internal { inline constexpr ::XCEngine::UI::UIElementId kShellPathRoot = 0x1E1001ull; inline constexpr ::XCEngine::UI::UIElementId kMenuBarPathRoot = 0x1E1002ull; @@ -72,9 +70,9 @@ std::vector BuildPopupFrames( const UIEditorShellInteractionState& state, std::string_view hoveredPopupId, std::string_view hoveredItemId); -bool ShouldUsePointerPosition(const ::XCEngine::UI::UIInputEvent& event); +bool ShouldUseShellPointerPosition(const ::XCEngine::UI::UIInputEvent& event); std::vector<::XCEngine::UI::UIInputEvent> FilterWorkspaceInputEvents( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, bool menuModalDuringFrame); -} // namespace XCEngine::UI::Editor::Detail +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Shell/UIEditorShellInteractionRendering.cpp b/new_editor/src/Shell/ShellInteractionRendering.cpp similarity index 93% rename from new_editor/src/Shell/UIEditorShellInteractionRendering.cpp rename to new_editor/src/Shell/ShellInteractionRendering.cpp index 3366570b..da4de909 100644 --- a/new_editor/src/Shell/UIEditorShellInteractionRendering.cpp +++ b/new_editor/src/Shell/ShellInteractionRendering.cpp @@ -1,5 +1,4 @@ -#include "Shell/UIEditorShellInteractionInternal.h" - +#include "Shell/ShellInteractionInternal.h" #include namespace XCEngine::UI::Editor { @@ -12,7 +11,7 @@ void AppendUIEditorShellInteraction( const UIEditorShellInteractionPalette& palette, const UIEditorShellInteractionMetrics& metrics) { const UIEditorShellComposeModel shellModel = - Detail::BuildShellComposeModel(model, frame.request.menuBarItems); + Internal::BuildShellComposeModel(model, frame.request.menuBarItems); AppendUIEditorShellCompose( drawList, frame.shellFrame, diff --git a/new_editor/src/Shell/UIEditorShellInteractionRequest.cpp b/new_editor/src/Shell/ShellInteractionRequest.cpp similarity index 99% rename from new_editor/src/Shell/UIEditorShellInteractionRequest.cpp rename to new_editor/src/Shell/ShellInteractionRequest.cpp index b9a252ea..629298af 100644 --- a/new_editor/src/Shell/UIEditorShellInteractionRequest.cpp +++ b/new_editor/src/Shell/ShellInteractionRequest.cpp @@ -1,5 +1,4 @@ -#include "Shell/UIEditorShellInteractionInternal.h" - +#include "Shell/ShellInteractionInternal.h" #include #include #include @@ -110,7 +109,7 @@ std::size_t FindPopupItemIndex( } // namespace -namespace Detail { +namespace Internal { UIElementId HashText(std::string_view text) { std::uint64_t hash = 1469598103934665603ull; @@ -459,7 +458,7 @@ std::vector BuildPopupFrames( return popupFrames; } -bool ShouldUsePointerPosition(const UIInputEvent& event) { +bool ShouldUseShellPointerPosition(const UIInputEvent& event) { switch (event.type) { case UIInputEventType::PointerMove: case UIInputEventType::PointerEnter: @@ -490,7 +489,7 @@ std::vector FilterWorkspaceInputEvents( return filtered; } -} // namespace Detail +} // namespace Internal UIEditorShellInteractionModel ResolveUIEditorShellInteractionModel( const UIEditorWorkspaceController& controller, @@ -517,7 +516,7 @@ UIEditorShellInteractionRequest ResolveUIEditorShellInteractionRequest( const UIEditorShellInteractionState& state, const UIEditorShellInteractionMetrics& metrics, const UIEditorShellInteractionServices& services) { - return Detail::BuildRequest( + return Internal::BuildRequest( bounds, controller, model, diff --git a/new_editor/src/Shell/UIEditorShellCompose.cpp b/new_editor/src/Shell/UIEditorShellCompose.cpp index e91d8280..955d5cb5 100644 --- a/new_editor/src/Shell/UIEditorShellCompose.cpp +++ b/new_editor/src/Shell/UIEditorShellCompose.cpp @@ -417,7 +417,8 @@ void AppendUIEditorShellCompose( palette.dockHostPalette, metrics.dockHostMetrics, palette.viewportPalette, - metrics.viewportMetrics); + metrics.viewportMetrics, + UIEditorWorkspaceComposeAppendOptions{ true }); AppendUIEditorStatusBarBackground( drawList, @@ -435,4 +436,16 @@ void AppendUIEditorShellCompose( metrics.statusBarMetrics); } +void AppendUIEditorShellComposeOverlay( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorShellComposeFrame& frame, + const UIEditorShellComposePalette& palette, + const UIEditorShellComposeMetrics& metrics) { + AppendUIEditorWorkspaceComposeOverlay( + drawList, + frame.workspaceFrame, + palette.dockHostPalette, + metrics.dockHostMetrics); +} + } // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Shell/UIEditorShellInteraction.cpp b/new_editor/src/Shell/UIEditorShellInteraction.cpp index 29350bd1..46495794 100644 --- a/new_editor/src/Shell/UIEditorShellInteraction.cpp +++ b/new_editor/src/Shell/UIEditorShellInteraction.cpp @@ -1,4 +1,4 @@ -#include "Shell/UIEditorShellInteractionInternal.h" +#include "Shell/ShellInteractionInternal.h" #include @@ -27,7 +27,7 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( UIEditorShellInteractionResult interactionResult = {}; bool menuModalDuringFrame = state.menuSession.HasOpenMenu(); - Detail::BuildRequestOutput requestBuild = Detail::BuildRequest( + Internal::BuildRequestOutput requestBuild = Internal::BuildRequest( bounds, controller, model, @@ -42,7 +42,7 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( menuModalDuringFrame = interactionResult.menuMutation.changed || state.menuSession.HasOpenMenu(); - requestBuild = Detail::BuildRequest( + requestBuild = Internal::BuildRequest( bounds, controller, model, @@ -55,14 +55,14 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( for (const UIInputEvent& event : inputEvents) { UIEditorShellInteractionResult eventResult = {}; - if (Detail::ShouldUsePointerPosition(event)) { + if (Internal::ShouldUseShellPointerPosition(event)) { state.pointerPosition = event.position; state.hasPointerPosition = true; } else if (event.type == UIInputEventType::PointerLeave) { state.hasPointerPosition = false; } - const Detail::RequestHit hit = Detail::HitTestRequest( + const Internal::RequestHit hit = Internal::HitTestRequest( request, state.pointerPosition, state.hasPointerPosition); @@ -90,7 +90,7 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( eventResult.menuMutation = state.menuSession.HoverMenuBarRoot( hit.menuButton->menuId, - Detail::BuildRootPopupEntry(*hit.menuButton)); + Internal::BuildRootPopupEntry(*hit.menuButton)); } else { eventResult.menuMutation = state.menuSession.DismissFromFocusLoss(hit.menuButton->path); @@ -103,7 +103,7 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( eventResult.menuMutation = state.menuSession.HoverSubmenu( hit.popupItem->itemId, - Detail::BuildSubmenuPopupEntry(*hit.popupItem)); + Internal::BuildSubmenuPopupEntry(*hit.popupItem)); } else { eventResult.menuMutation = state.menuSession.DismissFromFocusLoss(hit.popupItem->path); @@ -137,7 +137,7 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( eventResult.menuMutation = state.menuSession.OpenMenuBarRoot( hit.menuButton->menuId, - Detail::BuildRootPopupEntry(*hit.menuButton)); + Internal::BuildRootPopupEntry(*hit.menuButton)); } } else if (hit.popupItem != nullptr) { state.focused = true; @@ -149,7 +149,7 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( eventResult.menuMutation = state.menuSession.HoverSubmenu( hit.popupItem->itemId, - Detail::BuildSubmenuPopupEntry(*hit.popupItem)); + Internal::BuildSubmenuPopupEntry(*hit.popupItem)); } else if (hit.popupItem->enabled) { eventResult.commandTriggered = true; eventResult.commandId = hit.popupItem->commandId; @@ -176,7 +176,7 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( eventResult.consumed = true; eventResult.menuMutation = state.menuSession.DismissFromPointerDown( - UIInputPath { Detail::kOutsidePointerPath }); + UIInputPath { Internal::kOutsidePointerPath }); } break; @@ -192,13 +192,13 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( break; } - if (Detail::HasMeaningfulInteractionResult(eventResult)) { + if (Internal::HasMeaningfulInteractionResult(eventResult)) { interactionResult = std::move(eventResult); } if (interactionResult.menuMutation.changed || state.menuSession.HasOpenMenu()) { menuModalDuringFrame = true; - request = Detail::BuildRequest( + request = Internal::BuildRequest( bounds, controller, model, @@ -209,7 +209,7 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( } const std::vector workspaceInputEvents = - Detail::FilterWorkspaceInputEvents(inputEvents, menuModalDuringFrame); + Internal::FilterWorkspaceInputEvents(inputEvents, menuModalDuringFrame); UIEditorWorkspaceInteractionModel workspaceModel = {}; workspaceModel.workspacePresentations = model.workspacePresentations; UIEditorWorkspaceInteractionFrame workspaceInteractionFrame = @@ -222,7 +222,7 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( metrics.shellMetrics.dockHostMetrics); state.composeState.workspaceState = state.workspaceInteractionState.composeState; - request = Detail::BuildRequest( + request = Internal::BuildRequest( bounds, controller, model, @@ -230,18 +230,18 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( metrics, services).request; - const Detail::RequestHit finalHit = Detail::HitTestRequest( + const Internal::RequestHit finalHit = Internal::HitTestRequest( request, state.pointerPosition, state.hasPointerPosition); - Detail::UpdateMenuBarVisualState(state, request, finalHit); + Internal::UpdateMenuBarVisualState(state, request, finalHit); UIEditorShellInteractionFrame frame = {}; frame.request = request; frame.shellFrame.layout = request.shellRequest.layout; frame.shellFrame.workspaceFrame = workspaceInteractionFrame.composeFrame; frame.workspaceInteractionFrame = std::move(workspaceInteractionFrame); - frame.popupFrames = Detail::BuildPopupFrames( + frame.popupFrames = Internal::BuildPopupFrames( frame.request, state, finalHit.popupRequest != nullptr ? finalHit.popupRequest->popupId @@ -294,7 +294,7 @@ UIEditorShellInteractionFrame UpdateUIEditorShellInteraction( inputEvents, services, metrics); - if (!Detail::ShouldRefreshResolvedShellModel(frame.result)) { + if (!Internal::ShouldRefreshResolvedShellModel(frame.result)) { return frame; } diff --git a/new_editor/src/Workspace/SplitterDragCorrection/Chain.cpp b/new_editor/src/Workspace/SplitterDragCorrection/Chain.cpp new file mode 100644 index 00000000..ec5848ec --- /dev/null +++ b/new_editor/src/Workspace/SplitterDragCorrection/Chain.cpp @@ -0,0 +1,308 @@ +#include "Workspace/SplitterDragCorrection/Internal.h" + +#include + +namespace XCEngine::UI::Editor::Internal { + +namespace { + +bool FindWorkspaceNodePathRecursive( + const UIEditorWorkspaceNode& node, + std::string_view targetNodeId, + std::vector& path) { + path.push_back(&node); + if (node.nodeId == targetNodeId) { + return true; + } + + for (const UIEditorWorkspaceNode& child : node.children) { + if (FindWorkspaceNodePathRecursive(child, targetNodeId, path)) { + return true; + } + } + + path.pop_back(); + return false; +} + +std::size_t CollectSplitChainLayoutRecursive( + const UIEditorWorkspaceNode& node, + const ::XCEngine::UI::UIRect& bounds, + const UIEditorPanelRegistry& panelRegistry, + const UIEditorWorkspaceSession& session, + const Widgets::UIEditorDockHostMetrics& metrics, + UIEditorWorkspaceSplitAxis chainAxis, + SplitChainLayout& outChain) { + const DockHostNodeMeasureResult nodeMeasure = + MeasureDockHostNode(node, panelRegistry, session, metrics); + if (!nodeMeasure.visible) { + return 0u; + } + + if (node.kind != UIEditorWorkspaceNodeKind::Split || + node.splitAxis != chainAxis) { + outChain.leaves.push_back( + { GetMainExtent(bounds, chainAxis), GetMainExtent(nodeMeasure.minimumSize, chainAxis) }); + return 1u; + } + + const DockHostNodeMeasureResult primaryMeasure = + MeasureDockHostNode(node.children[0], panelRegistry, session, metrics); + const DockHostNodeMeasureResult secondaryMeasure = + MeasureDockHostNode(node.children[1], panelRegistry, session, metrics); + if (!primaryMeasure.visible) { + return CollectSplitChainLayoutRecursive( + node.children[1], + bounds, + panelRegistry, + session, + metrics, + chainAxis, + outChain); + } + if (!secondaryMeasure.visible) { + return CollectSplitChainLayoutRecursive( + node.children[0], + bounds, + panelRegistry, + session, + metrics, + chainAxis, + outChain); + } + + ::XCEngine::UI::Layout::UISplitterConstraints constraints = {}; + constraints.primaryMin = GetMainExtent(primaryMeasure.minimumSize, node.splitAxis); + constraints.secondaryMin = GetMainExtent(secondaryMeasure.minimumSize, node.splitAxis); + const auto arranged = ::XCEngine::UI::Layout::ArrangeUISplitter( + bounds, + ToUILayoutAxis(node.splitAxis), + node.splitRatio, + constraints, + metrics.splitterMetrics); + + const std::size_t leafStart = outChain.leaves.size(); + const std::size_t primaryLeafCount = CollectSplitChainLayoutRecursive( + node.children[0], + arranged.primaryRect, + panelRegistry, + session, + metrics, + chainAxis, + outChain); + const std::size_t secondaryLeafCount = CollectSplitChainLayoutRecursive( + node.children[1], + arranged.secondaryRect, + panelRegistry, + session, + metrics, + chainAxis, + outChain); + + if (primaryLeafCount > 0u && secondaryLeafCount > 0u) { + outChain.splits.push_back({ node.nodeId, leafStart, primaryLeafCount }); + } + return primaryLeafCount + secondaryLeafCount; +} + +} // namespace + +const UIEditorWorkspaceNode* FindSameAxisChainRoot( + const UIEditorWorkspaceModel& workspace, + std::string_view splitNodeId) { + std::vector path = {}; + if (!FindWorkspaceNodePathRecursive(workspace.root, splitNodeId, path) || + path.empty()) { + return nullptr; + } + + const UIEditorWorkspaceNode* root = path.back(); + if (root->kind != UIEditorWorkspaceNodeKind::Split) { + return nullptr; + } + + const UIEditorWorkspaceSplitAxis axis = root->splitAxis; + for (std::size_t index = path.size(); index > 1u; --index) { + const UIEditorWorkspaceNode* parent = path[index - 2u]; + if (parent->kind != UIEditorWorkspaceNodeKind::Split || + parent->splitAxis != axis) { + break; + } + root = parent; + } + + return root; +} + +bool BuildSplitChainLayout( + const UIEditorWorkspaceLayoutSnapshot& snapshot, + const Widgets::UIEditorDockHostLayout& layout, + std::string_view splitNodeId, + const UIEditorPanelRegistry& panelRegistry, + const Widgets::UIEditorDockHostMetrics& metrics, + SplitChainLayout& outChain) { + const UIEditorWorkspaceNode* chainRoot = + FindSameAxisChainRoot(snapshot.workspace, splitNodeId); + if (chainRoot == nullptr || chainRoot->kind != UIEditorWorkspaceNodeKind::Split) { + return false; + } + + const Widgets::UIEditorDockHostSplitterLayout* rootSplitter = + Widgets::FindUIEditorDockHostSplitterLayout(layout, chainRoot->nodeId); + if (rootSplitter == nullptr) { + return false; + } + + outChain = {}; + outChain.root = chainRoot; + outChain.axis = chainRoot->splitAxis; + CollectSplitChainLayoutRecursive( + *chainRoot, + rootSplitter->bounds, + panelRegistry, + snapshot.session, + metrics, + outChain.axis, + outChain); + return outChain.leaves.size() >= 2u; +} + +const SplitChainSplitInfo* FindSplitChainSplitInfo( + const SplitChainLayout& chain, + std::string_view nodeId) { + const auto it = std::find_if( + chain.splits.begin(), + chain.splits.end(), + [nodeId](const SplitChainSplitInfo& split) { + return split.nodeId == nodeId; + }); + return it == chain.splits.end() ? nullptr : &(*it); +} + +float ClampLocalBoundaryDelta( + const SplitChainLeaf& leading, + const SplitChainLeaf& trailing, + float delta) { + const float minimumDelta = leading.minimumExtent - leading.extent; + const float maximumDelta = trailing.extent - trailing.minimumExtent; + return (std::clamp)(delta, minimumDelta, maximumDelta); +} + +SplitRatioResolveResult ResolveSplitChainRatiosRecursive( + const UIEditorWorkspaceNode& node, + UIEditorWorkspaceSplitAxis chainAxis, + const std::vector& leafExtents, + float handleThickness, + const UIEditorPanelRegistry& panelRegistry, + const UIEditorWorkspaceSession& session, + const Widgets::UIEditorDockHostMetrics& metrics, + std::size_t& leafCursor, + std::vector& outAssignments) { + const DockHostNodeMeasureResult nodeMeasure = + MeasureDockHostNode(node, panelRegistry, session, metrics); + if (!nodeMeasure.visible) { + return {}; + } + + if (node.kind != UIEditorWorkspaceNodeKind::Split || + node.splitAxis != chainAxis) { + if (leafCursor >= leafExtents.size()) { + return {}; + } + const float extent = leafExtents[leafCursor]; + ++leafCursor; + return { 1u, extent }; + } + + const DockHostNodeMeasureResult primaryMeasure = + MeasureDockHostNode(node.children[0], panelRegistry, session, metrics); + const DockHostNodeMeasureResult secondaryMeasure = + MeasureDockHostNode(node.children[1], panelRegistry, session, metrics); + if (!primaryMeasure.visible) { + return ResolveSplitChainRatiosRecursive( + node.children[1], + chainAxis, + leafExtents, + handleThickness, + panelRegistry, + session, + metrics, + leafCursor, + outAssignments); + } + if (!secondaryMeasure.visible) { + return ResolveSplitChainRatiosRecursive( + node.children[0], + chainAxis, + leafExtents, + handleThickness, + panelRegistry, + session, + metrics, + leafCursor, + outAssignments); + } + + const SplitRatioResolveResult primary = ResolveSplitChainRatiosRecursive( + node.children[0], + chainAxis, + leafExtents, + handleThickness, + panelRegistry, + session, + metrics, + leafCursor, + outAssignments); + const SplitRatioResolveResult secondary = ResolveSplitChainRatiosRecursive( + node.children[1], + chainAxis, + leafExtents, + handleThickness, + panelRegistry, + session, + metrics, + leafCursor, + outAssignments); + + if (primary.leafCount == 0u || secondary.leafCount == 0u) { + return primary.leafCount > 0u ? primary : secondary; + } + + const float usableExtent = primary.totalExtent + secondary.totalExtent; + const float splitRatio = + usableExtent <= 0.0f ? node.splitRatio : primary.totalExtent / usableExtent; + outAssignments.push_back({ node.nodeId, splitRatio }); + return { + primary.leafCount + secondary.leafCount, + primary.totalExtent + secondary.totalExtent + handleThickness + }; +} + +::XCEngine::UI::Layout::UISplitterLayoutResult BuildRequestedSplitterLayoutFromPointer( + const Widgets::UIEditorDockHostSplitterLayout& splitter, + const ::XCEngine::UI::UIPoint& pointerPosition) { + ::XCEngine::UI::Layout::UISplitterLayoutOptions options = {}; + options.axis = ToUILayoutAxis(splitter.axis); + options.ratio = splitter.splitterLayout.splitRatio; + options.handleThickness = splitter.metrics.thickness; + options.minPrimaryExtent = splitter.constraints.primaryMin; + options.minSecondaryExtent = splitter.constraints.secondaryMin; + + const float pointerMainPosition = + splitter.axis == UIEditorWorkspaceSplitAxis::Horizontal + ? pointerPosition.x + : pointerPosition.y; + const float requestedRatio = + ::XCEngine::UI::Layout::ResolveSplitterRatioFromPointerPosition( + options, + splitter.bounds, + pointerMainPosition); + return ::XCEngine::UI::Layout::ArrangeUISplitter( + splitter.bounds, + options.axis, + requestedRatio, + splitter.constraints, + splitter.metrics); +} + +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Workspace/SplitterDragCorrection/Correction.cpp b/new_editor/src/Workspace/SplitterDragCorrection/Correction.cpp new file mode 100644 index 00000000..c235b2be --- /dev/null +++ b/new_editor/src/Workspace/SplitterDragCorrection/Correction.cpp @@ -0,0 +1,179 @@ +#include "Workspace/SplitterDragCorrection/Internal.h" + +#include +#include + +namespace XCEngine::UI::Editor { + +bool IsUIEditorWorkspaceSplitterDragCorrectionActive( + const UIEditorWorkspaceSplitterDragCorrectionState& state) { + return !state.splitNodeId.empty(); +} + +void ResetUIEditorWorkspaceSplitterDragCorrectionState( + UIEditorWorkspaceSplitterDragCorrectionState& state) { + state = {}; +} + +void BeginUIEditorWorkspaceSplitterDragCorrection( + UIEditorWorkspaceSplitterDragCorrectionState& state, + std::string_view splitNodeId, + const UIEditorWorkspaceLayoutSnapshot& baselineSnapshot, + const Widgets::UIEditorDockHostLayout& baselineDockLayout) { + state.splitNodeId = std::string(splitNodeId); + state.baselineSnapshot = baselineSnapshot; + state.baselineDockLayout = baselineDockLayout; +} + +bool TryBuildUIEditorWorkspaceSplitterDragCorrectedSnapshot( + const UIEditorWorkspaceSplitterDragCorrectionState& state, + const UIPoint& pointerPosition, + bool hasPointerPosition, + const UIEditorPanelRegistry& panelRegistry, + const Widgets::UIEditorDockHostMetrics& metrics, + UIEditorWorkspaceLayoutSnapshot& outSnapshot) { + if (!IsUIEditorWorkspaceSplitterDragCorrectionActive(state) || + !hasPointerPosition) { + return false; + } + + const Widgets::UIEditorDockHostSplitterLayout* baselineSplitter = + Widgets::FindUIEditorDockHostSplitterLayout( + state.baselineDockLayout, + state.splitNodeId); + if (baselineSplitter == nullptr) { + return false; + } + + Internal::SplitChainLayout chain = {}; + if (!Internal::BuildSplitChainLayout( + state.baselineSnapshot, + state.baselineDockLayout, + state.splitNodeId, + panelRegistry, + metrics, + chain)) { + return false; + } + + const Internal::SplitChainSplitInfo* activeSplit = + Internal::FindSplitChainSplitInfo(chain, state.splitNodeId); + if (activeSplit == nullptr || activeSplit->primaryLeafCount == 0u) { + return false; + } + + const std::size_t boundaryLeafIndex = + activeSplit->leafStart + activeSplit->primaryLeafCount - 1u; + if (boundaryLeafIndex + 1u >= chain.leaves.size()) { + return false; + } + + const auto requestedLayout = Internal::BuildRequestedSplitterLayoutFromPointer( + *baselineSplitter, + pointerPosition); + const float delta = Internal::ClampLocalBoundaryDelta( + chain.leaves[boundaryLeafIndex], + chain.leaves[boundaryLeafIndex + 1u], + requestedLayout.primaryExtent - baselineSplitter->splitterLayout.primaryExtent); + if (std::fabs(delta) <= 0.0001f) { + return false; + } + + std::vector leafExtents = {}; + leafExtents.reserve(chain.leaves.size()); + for (const Internal::SplitChainLeaf& leaf : chain.leaves) { + leafExtents.push_back(leaf.extent); + } + leafExtents[boundaryLeafIndex] += delta; + leafExtents[boundaryLeafIndex + 1u] -= delta; + + outSnapshot = state.baselineSnapshot; + std::vector ratioAssignments = {}; + std::size_t leafCursor = 0u; + Internal::ResolveSplitChainRatiosRecursive( + *chain.root, + chain.axis, + leafExtents, + metrics.splitterMetrics.thickness, + panelRegistry, + outSnapshot.session, + metrics, + leafCursor, + ratioAssignments); + if (leafCursor != leafExtents.size()) { + return false; + } + + bool changed = false; + for (const Internal::SplitRatioAssignment& assignment : ratioAssignments) { + const UIEditorWorkspaceNode* currentNode = + FindUIEditorWorkspaceNode(outSnapshot.workspace, assignment.nodeId); + if (currentNode == nullptr || currentNode->kind != UIEditorWorkspaceNodeKind::Split) { + return false; + } + + if (std::fabs(currentNode->splitRatio - assignment.splitRatio) <= 0.0001f) { + continue; + } + + if (!TrySetUIEditorWorkspaceSplitRatio( + outSnapshot.workspace, + assignment.nodeId, + assignment.splitRatio)) { + return false; + } + changed = true; + } + + return changed; +} + +bool TryApplyUIEditorWorkspaceSplitterDragCorrection( + UIEditorWorkspaceSplitterDragCorrectionState& state, + const UIEditorDockHostInteractionState& dockHostInteractionState, + const UIEditorWorkspaceLayoutSnapshot& preUpdateSnapshot, + const Widgets::UIEditorDockHostLayout& preUpdateDockLayout, + UIEditorWorkspaceController& workspaceController, + const Widgets::UIEditorDockHostMetrics& metrics) { + const std::string& activeSplitterNodeId = + dockHostInteractionState.dockHostState.activeSplitterNodeId; + if (!dockHostInteractionState.splitterDragState.active || + activeSplitterNodeId.empty()) { + ResetUIEditorWorkspaceSplitterDragCorrectionState(state); + return false; + } + + if (!IsUIEditorWorkspaceSplitterDragCorrectionActive(state) || + state.splitNodeId != activeSplitterNodeId) { + BeginUIEditorWorkspaceSplitterDragCorrection( + state, + activeSplitterNodeId, + preUpdateSnapshot, + preUpdateDockLayout); + } + + UIEditorWorkspaceLayoutSnapshot correctedSnapshot = {}; + if (!TryBuildUIEditorWorkspaceSplitterDragCorrectedSnapshot( + state, + dockHostInteractionState.pointerPosition, + dockHostInteractionState.hasPointerPosition, + workspaceController.GetPanelRegistry(), + metrics, + correctedSnapshot)) { + return false; + } + + const UIEditorWorkspaceLayoutSnapshot currentSnapshot = + workspaceController.CaptureLayoutSnapshot(); + if (AreUIEditorWorkspaceLayoutSnapshotsEquivalent( + currentSnapshot, + correctedSnapshot)) { + return false; + } + + const UIEditorWorkspaceLayoutOperationResult correctionResult = + workspaceController.RestoreLayoutSnapshot(correctedSnapshot); + return correctionResult.status == UIEditorWorkspaceLayoutOperationStatus::Changed; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Workspace/SplitterDragCorrection/Internal.h b/new_editor/src/Workspace/SplitterDragCorrection/Internal.h new file mode 100644 index 00000000..6a064644 --- /dev/null +++ b/new_editor/src/Workspace/SplitterDragCorrection/Internal.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include "Docking/DockHostMeasureInternal.h" + +#include +#include +#include + +namespace XCEngine::UI::Editor::Internal { + +struct SplitChainLeaf { + float extent = 0.0f; + float minimumExtent = 0.0f; +}; + +struct SplitChainSplitInfo { + std::string nodeId = {}; + std::size_t leafStart = 0u; + std::size_t primaryLeafCount = 0u; +}; + +struct SplitChainLayout { + const UIEditorWorkspaceNode* root = nullptr; + UIEditorWorkspaceSplitAxis axis = UIEditorWorkspaceSplitAxis::Horizontal; + std::vector leaves = {}; + std::vector splits = {}; +}; + +struct SplitRatioAssignment { + std::string nodeId = {}; + float splitRatio = 0.5f; +}; + +struct SplitRatioResolveResult { + std::size_t leafCount = 0u; + float totalExtent = 0.0f; +}; + +const UIEditorWorkspaceNode* FindSameAxisChainRoot( + const UIEditorWorkspaceModel& workspace, + std::string_view splitNodeId); +bool BuildSplitChainLayout( + const UIEditorWorkspaceLayoutSnapshot& snapshot, + const Widgets::UIEditorDockHostLayout& layout, + std::string_view splitNodeId, + const UIEditorPanelRegistry& panelRegistry, + const Widgets::UIEditorDockHostMetrics& metrics, + SplitChainLayout& outChain); +const SplitChainSplitInfo* FindSplitChainSplitInfo( + const SplitChainLayout& chain, + std::string_view nodeId); +float ClampLocalBoundaryDelta( + const SplitChainLeaf& leading, + const SplitChainLeaf& trailing, + float delta); +SplitRatioResolveResult ResolveSplitChainRatiosRecursive( + const UIEditorWorkspaceNode& node, + UIEditorWorkspaceSplitAxis chainAxis, + const std::vector& leafExtents, + float handleThickness, + const UIEditorPanelRegistry& panelRegistry, + const UIEditorWorkspaceSession& session, + const Widgets::UIEditorDockHostMetrics& metrics, + std::size_t& leafCursor, + std::vector& outAssignments); +::XCEngine::UI::Layout::UISplitterLayoutResult BuildRequestedSplitterLayoutFromPointer( + const Widgets::UIEditorDockHostSplitterLayout& splitter, + const ::XCEngine::UI::UIPoint& pointerPosition); + +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp b/new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp index ddb61e4f..43568ea1 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp @@ -290,7 +290,8 @@ void AppendUIEditorWorkspaceCompose( const Widgets::UIEditorDockHostPalette& dockHostPalette, const Widgets::UIEditorDockHostMetrics& dockHostMetrics, const Widgets::UIEditorViewportSlotPalette& viewportPalette, - const Widgets::UIEditorViewportSlotMetrics& viewportMetrics) { + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics, + const UIEditorWorkspaceComposeAppendOptions& options) { AppendUIEditorDockHostBackground( drawList, frame.dockHostLayout, @@ -321,6 +322,7 @@ void AppendUIEditorWorkspaceCompose( UIEditorDockHostForegroundOptions foregroundOptions = {}; foregroundOptions.externalBodyPanelIds = CollectUIEditorWorkspaceComposeExternalBodyPanelIds(frame); + foregroundOptions.deferDropPreviewOverlay = options.deferDockPreviewOverlay; AppendUIEditorDockHostForeground( drawList, frame.dockHostLayout, @@ -329,4 +331,16 @@ void AppendUIEditorWorkspaceCompose( dockHostMetrics); } +void AppendUIEditorWorkspaceComposeOverlay( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorWorkspaceComposeFrame& frame, + const Widgets::UIEditorDockHostPalette& dockHostPalette, + const Widgets::UIEditorDockHostMetrics& dockHostMetrics) { + AppendUIEditorDockHostOverlay( + drawList, + frame.dockHostLayout, + dockHostPalette, + dockHostMetrics); +} + } // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Workspace/UIEditorWorkspaceController.cpp b/new_editor/src/Workspace/UIEditorWorkspaceController.cpp index 3742ec50..f8334bf6 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceController.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceController.cpp @@ -1,8 +1,8 @@ -#include "Workspace/UIEditorWorkspaceControllerInternal.h" +#include "Workspace/WorkspaceControllerInternal.h" #include -namespace XCEngine::UI::Editor::Detail { +namespace XCEngine::UI::Editor::Internal { bool IsPanelOpenAndVisible( const UIEditorWorkspaceSession& session, @@ -72,7 +72,7 @@ std::size_t CountVisibleTabs( return visibleCount; } -} // namespace XCEngine::UI::Editor::Detail +} // namespace XCEngine::UI::Editor::Internal namespace XCEngine::UI::Editor { @@ -179,7 +179,7 @@ UIEditorWorkspaceCommandResult UIEditorWorkspaceController::BuildResult( result.panelId = command.panelId; result.message = std::move(message); result.activePanelId = m_workspace.activePanelId; - result.visiblePanelIds = Detail::CollectVisiblePanelIds(m_workspace, m_session); + result.visiblePanelIds = Internal::CollectVisiblePanelIds(m_workspace, m_session); return result; } @@ -190,7 +190,7 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::BuildLayoutO result.status = status; result.message = std::move(message); result.activePanelId = m_workspace.activePanelId; - result.visiblePanelIds = Detail::CollectVisiblePanelIds(m_workspace, m_session); + result.visiblePanelIds = Internal::CollectVisiblePanelIds(m_workspace, m_session); return result; } diff --git a/new_editor/src/Workspace/UIEditorWorkspaceModel.cpp b/new_editor/src/Workspace/UIEditorWorkspaceModel.cpp index 76a07539..70be3607 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceModel.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceModel.cpp @@ -1,10 +1,10 @@ -#include "Workspace/UIEditorWorkspaceModelInternal.h" +#include "Workspace/WorkspaceModelInternal.h" #include #include #include -namespace XCEngine::UI::Editor::Detail { +namespace XCEngine::UI::Editor::Internal { UIEditorWorkspaceValidationResult MakeValidationError( UIEditorWorkspaceValidationCode code, @@ -554,7 +554,7 @@ UIEditorWorkspaceValidationResult ValidateNodeRecursive( return {}; } -} // namespace XCEngine::UI::Editor::Detail +} // namespace XCEngine::UI::Editor::Internal namespace XCEngine::UI::Editor { @@ -594,7 +594,7 @@ bool AreUIEditorWorkspaceModelsEquivalent( UIEditorWorkspaceModel BuildDefaultEditorShellWorkspaceModel() { const UIEditorPanelRegistry registry = BuildDefaultEditorShellPanelRegistry(); const UIEditorPanelDescriptor& rootPanel = - Detail::RequirePanelDescriptor(registry, "editor-foundation-root"); + Internal::RequirePanelDescriptor(registry, "editor-foundation-root"); UIEditorWorkspaceModel workspace = {}; workspace.root = BuildUIEditorWorkspaceSingleTabStack( @@ -626,7 +626,7 @@ UIEditorWorkspaceNode BuildUIEditorWorkspaceSingleTabStack( std::string title, bool placeholder) { UIEditorWorkspaceNode panel = BuildUIEditorWorkspacePanel( - Detail::BuildSingleTabPanelNodeId(nodeId), + Internal::BuildSingleTabPanelNodeId(nodeId), std::move(panelId), std::move(title), placeholder); @@ -666,7 +666,7 @@ UIEditorWorkspaceNode BuildUIEditorWorkspaceSplit( UIEditorWorkspaceModel CanonicalizeUIEditorWorkspaceModel( UIEditorWorkspaceModel workspace) { - Detail::CanonicalizeNodeRecursive(workspace.root, false); + Internal::CanonicalizeNodeRecursive(workspace.root, false); return workspace; } diff --git a/new_editor/src/Workspace/UIEditorWorkspaceControllerDispatch.cpp b/new_editor/src/Workspace/WorkspaceControllerDispatch.cpp similarity index 100% rename from new_editor/src/Workspace/UIEditorWorkspaceControllerDispatch.cpp rename to new_editor/src/Workspace/WorkspaceControllerDispatch.cpp diff --git a/new_editor/src/Workspace/UIEditorWorkspaceControllerInternal.h b/new_editor/src/Workspace/WorkspaceControllerInternal.h similarity index 90% rename from new_editor/src/Workspace/UIEditorWorkspaceControllerInternal.h rename to new_editor/src/Workspace/WorkspaceControllerInternal.h index 37e3d87b..a65170e5 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceControllerInternal.h +++ b/new_editor/src/Workspace/WorkspaceControllerInternal.h @@ -1,12 +1,11 @@ #pragma once #include - #include #include #include -namespace XCEngine::UI::Editor::Detail { +namespace XCEngine::UI::Editor::Internal { bool IsPanelOpenAndVisible( const UIEditorWorkspaceSession& session, @@ -32,4 +31,4 @@ std::size_t CountVisibleTabs( const UIEditorWorkspaceNode& node, const UIEditorWorkspaceSession& session); -} // namespace XCEngine::UI::Editor::Detail +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Workspace/UIEditorWorkspaceControllerLayoutOps.cpp b/new_editor/src/Workspace/WorkspaceControllerLayoutOps.cpp similarity index 96% rename from new_editor/src/Workspace/UIEditorWorkspaceControllerLayoutOps.cpp rename to new_editor/src/Workspace/WorkspaceControllerLayoutOps.cpp index 3ee8ad6d..08fc0e3c 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceControllerLayoutOps.cpp +++ b/new_editor/src/Workspace/WorkspaceControllerLayoutOps.cpp @@ -1,4 +1,4 @@ -#include "Workspace/UIEditorWorkspaceControllerInternal.h" +#include "Workspace/WorkspaceControllerInternal.h" #include @@ -159,8 +159,8 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::ReorderTab( "ReorderTab target tab stack is missing."); } - const Detail::VisibleTabStackInfo tabInfo = - Detail::ResolveVisibleTabStackInfo(*tabStack, m_session, panelId); + const Internal::VisibleTabStackInfo tabInfo = + Internal::ResolveVisibleTabStackInfo(*tabStack, m_session, panelId); if (!tabInfo.panelExists) { return BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus::Rejected, @@ -267,8 +267,8 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::MoveTabToSta "MoveTabToStack source or target tab stack is missing."); } - const Detail::VisibleTabStackInfo sourceInfo = - Detail::ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId); + const Internal::VisibleTabStackInfo sourceInfo = + Internal::ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId); if (!sourceInfo.panelExists) { return BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus::Rejected, @@ -282,7 +282,7 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::MoveTabToSta } const std::size_t visibleTargetCount = - Detail::CountVisibleTabs(*targetTabStack, m_session); + Internal::CountVisibleTabs(*targetTabStack, m_session); if (targetVisibleInsertionIndex > visibleTargetCount) { return BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus::Rejected, @@ -369,8 +369,8 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::DockTabRelat "DockTabRelative source or target tab stack is missing."); } - const Detail::VisibleTabStackInfo sourceInfo = - Detail::ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId); + const Internal::VisibleTabStackInfo sourceInfo = + Internal::ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId); if (!sourceInfo.panelExists) { return BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus::Rejected, diff --git a/new_editor/src/Workspace/UIEditorWorkspaceModelInternal.h b/new_editor/src/Workspace/WorkspaceModelInternal.h similarity index 97% rename from new_editor/src/Workspace/UIEditorWorkspaceModelInternal.h rename to new_editor/src/Workspace/WorkspaceModelInternal.h index 8f8a3b4d..4740d189 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceModelInternal.h +++ b/new_editor/src/Workspace/WorkspaceModelInternal.h @@ -10,7 +10,7 @@ #include #include -namespace XCEngine::UI::Editor::Detail { +namespace XCEngine::UI::Editor::Internal { UIEditorWorkspaceValidationResult MakeValidationError( UIEditorWorkspaceValidationCode code, @@ -91,4 +91,4 @@ UIEditorWorkspaceValidationResult ValidateNodeRecursive( const UIEditorWorkspaceNode& node, std::unordered_set& panelIds); -} // namespace XCEngine::UI::Editor::Detail +} // namespace XCEngine::UI::Editor::Internal diff --git a/new_editor/src/Workspace/UIEditorWorkspaceModelMutation.cpp b/new_editor/src/Workspace/WorkspaceModelMutation.cpp similarity index 88% rename from new_editor/src/Workspace/UIEditorWorkspaceModelMutation.cpp rename to new_editor/src/Workspace/WorkspaceModelMutation.cpp index 0e0e8f4e..cb587201 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceModelMutation.cpp +++ b/new_editor/src/Workspace/WorkspaceModelMutation.cpp @@ -1,5 +1,4 @@ -#include "Workspace/UIEditorWorkspaceModelInternal.h" - +#include "Workspace/WorkspaceModelInternal.h" #include #include #include @@ -9,7 +8,7 @@ namespace XCEngine::UI::Editor { bool TryActivateUIEditorWorkspacePanel( UIEditorWorkspaceModel& workspace, std::string_view panelId) { - if (!Detail::TryActivateRecursive(workspace.root, panelId)) { + if (!Internal::TryActivateRecursive(workspace.root, panelId)) { return false; } @@ -21,12 +20,12 @@ bool TrySetUIEditorWorkspaceSplitRatio( UIEditorWorkspaceModel& workspace, std::string_view nodeId, float splitRatio) { - if (!Detail::IsValidSplitRatio(splitRatio)) { + if (!Internal::IsValidSplitRatio(splitRatio)) { return false; } UIEditorWorkspaceNode* node = - Detail::FindMutableNodeRecursive(workspace.root, nodeId); + Internal::FindMutableNodeRecursive(workspace.root, nodeId); if (node == nullptr || node->kind != UIEditorWorkspaceNodeKind::Split) { return false; } @@ -46,7 +45,7 @@ bool TryReorderUIEditorWorkspaceTab( std::string_view panelId, std::size_t targetVisibleInsertionIndex) { UIEditorWorkspaceNode* node = - Detail::FindMutableNodeRecursive(workspace.root, nodeId); + Internal::FindMutableNodeRecursive(workspace.root, nodeId); if (node == nullptr || node->kind != UIEditorWorkspaceNodeKind::TabStack) { return false; } @@ -63,7 +62,7 @@ bool TryReorderUIEditorWorkspaceTab( return false; } - if (!Detail::IsPanelOpenAndVisibleInSession(session, child.panel.panelId)) { + if (!Internal::IsPanelOpenAndVisibleInSession(session, child.panel.panelId)) { continue; } @@ -113,7 +112,7 @@ bool TryReorderUIEditorWorkspaceTab( std::size_t nextVisibleIndex = 0u; for (std::size_t index = 0; index < originalChildren.size(); ++index) { const UIEditorWorkspaceNode& originalChild = originalChildren[index]; - if (!Detail::IsPanelOpenAndVisibleInSession( + if (!Internal::IsPanelOpenAndVisibleInSession( session, originalChild.panel.panelId)) { node->children[index] = originalChild; @@ -140,7 +139,7 @@ bool TryExtractUIEditorWorkspaceVisiblePanelNode( std::string_view sourceNodeId, std::string_view panelId, UIEditorWorkspaceNode& extractedPanel) { - return Detail::TryExtractVisiblePanelFromTabStack( + return Internal::TryExtractVisiblePanelFromTabStack( workspace, session, sourceNodeId, @@ -168,12 +167,12 @@ bool TryInsertUIEditorWorkspacePanelNodeToStack( } if (targetVisibleInsertionIndex > - Detail::CountVisibleChildren(*targetNode, session)) { + Internal::CountVisibleChildren(*targetNode, session)) { return false; } UIEditorWorkspaceNode* targetStack = - Detail::FindMutableNodeRecursive(workspace.root, targetNodeId); + Internal::FindMutableNodeRecursive(workspace.root, targetNodeId); if (targetStack == nullptr || targetStack->kind != UIEditorWorkspaceNodeKind::TabStack) { return false; @@ -181,7 +180,7 @@ bool TryInsertUIEditorWorkspacePanelNodeToStack( const std::string movedPanelId = panelNode.panel.panelId; const std::size_t actualInsertionIndex = - Detail::ResolveActualInsertionIndexForVisibleInsertion( + Internal::ResolveActualInsertionIndexForVisibleInsertion( *targetStack, session, targetVisibleInsertionIndex); @@ -225,18 +224,18 @@ bool TryDockUIEditorWorkspacePanelNodeRelative( session, std::move(panelNode), targetNodeId, - Detail::CountVisibleChildren(*targetNode, session)); + Internal::CountVisibleChildren(*targetNode, session)); } UIEditorWorkspaceNode* targetStack = - Detail::FindMutableNodeRecursive(workspace.root, targetNodeId); + Internal::FindMutableNodeRecursive(workspace.root, targetNodeId); if (targetStack == nullptr || targetStack->kind != UIEditorWorkspaceNodeKind::TabStack) { return false; } const std::string movedPanelId = panelNode.panel.panelId; - const std::string movedStackNodeId = Detail::MakeUniqueNodeId( + const std::string movedStackNodeId = Internal::MakeUniqueNodeId( workspace, std::string(targetNodeId) + "__dock_" + movedPanelId + "_stack"); UIEditorWorkspaceNode movedStack = {}; @@ -248,7 +247,7 @@ bool TryDockUIEditorWorkspacePanelNodeRelative( UIEditorWorkspaceNode existingTarget = std::move(*targetStack); UIEditorWorkspaceNode primary = {}; UIEditorWorkspaceNode secondary = {}; - if (Detail::IsLeadingDockPlacement(placement)) { + if (Internal::IsLeadingDockPlacement(placement)) { primary = std::move(movedStack); secondary = std::move(existingTarget); } else { @@ -256,16 +255,16 @@ bool TryDockUIEditorWorkspacePanelNodeRelative( secondary = std::move(movedStack); } - const float requestedRatio = Detail::ClampDockSplitRatio(splitRatio); + const float requestedRatio = Internal::ClampDockSplitRatio(splitRatio); const float resolvedSplitRatio = - Detail::IsLeadingDockPlacement(placement) + Internal::IsLeadingDockPlacement(placement) ? requestedRatio : (1.0f - requestedRatio); *targetStack = BuildUIEditorWorkspaceSplit( - Detail::MakeUniqueNodeId( + Internal::MakeUniqueNodeId( workspace, std::string(targetNodeId) + "__dock_split"), - Detail::ResolveDockSplitAxis(placement), + Internal::ResolveDockSplitAxis(placement), resolvedSplitRatio, std::move(primary), std::move(secondary)); @@ -304,12 +303,12 @@ bool TryMoveUIEditorWorkspaceTabToStack( } if (targetVisibleInsertionIndex > - Detail::CountVisibleChildren(*targetNode, session)) { + Internal::CountVisibleChildren(*targetNode, session)) { return false; } UIEditorWorkspaceNode extractedPanel = {}; - if (!Detail::TryExtractVisiblePanelFromTabStack( + if (!Internal::TryExtractVisiblePanelFromTabStack( workspace, session, sourceNodeId, @@ -348,7 +347,7 @@ bool TryDockUIEditorWorkspaceTabRelative( sourceNodeId, panelId, targetNodeId, - Detail::CountVisibleChildren(*targetNode, session)); + Internal::CountVisibleChildren(*targetNode, session)); } if (sourceNodeId.empty() || @@ -374,7 +373,7 @@ bool TryDockUIEditorWorkspaceTabRelative( } UIEditorWorkspaceNode extractedPanel = {}; - if (!Detail::TryExtractVisiblePanelFromTabStack( + if (!Internal::TryExtractVisiblePanelFromTabStack( workspace, session, sourceNodeId, diff --git a/new_editor/src/Workspace/UIEditorWorkspaceModelQueries.cpp b/new_editor/src/Workspace/WorkspaceModelQueries.cpp similarity index 77% rename from new_editor/src/Workspace/UIEditorWorkspaceModelQueries.cpp rename to new_editor/src/Workspace/WorkspaceModelQueries.cpp index 6435f03a..dae0c00c 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceModelQueries.cpp +++ b/new_editor/src/Workspace/WorkspaceModelQueries.cpp @@ -1,11 +1,11 @@ -#include "Workspace/UIEditorWorkspaceModelInternal.h" +#include "Workspace/WorkspaceModelInternal.h" namespace XCEngine::UI::Editor { std::vector CollectUIEditorWorkspaceVisiblePanels( const UIEditorWorkspaceModel& workspace) { std::vector visiblePanels = {}; - Detail::CollectVisiblePanelsRecursive( + Internal::CollectVisiblePanelsRecursive( workspace.root, workspace.activePanelId, visiblePanels); @@ -15,13 +15,13 @@ std::vector CollectUIEditorWorkspaceVisiblePanels bool ContainsUIEditorWorkspacePanel( const UIEditorWorkspaceModel& workspace, std::string_view panelId) { - return Detail::FindPanelRecursive(workspace.root, panelId) != nullptr; + return Internal::FindPanelRecursive(workspace.root, panelId) != nullptr; } const UIEditorWorkspaceNode* FindUIEditorWorkspaceNode( const UIEditorWorkspaceModel& workspace, std::string_view nodeId) { - return Detail::FindNodeRecursive(workspace.root, nodeId); + return Internal::FindNodeRecursive(workspace.root, nodeId); } const UIEditorWorkspacePanelState* FindUIEditorWorkspaceActivePanel( @@ -34,7 +34,7 @@ const UIEditorWorkspacePanelState* FindUIEditorWorkspaceActivePanel( CollectUIEditorWorkspaceVisiblePanels(workspace); for (const UIEditorWorkspaceVisiblePanel& panel : visiblePanels) { if (panel.panelId == workspace.activePanelId) { - return Detail::FindPanelRecursive(workspace.root, workspace.activePanelId); + return Internal::FindPanelRecursive(workspace.root, workspace.activePanelId); } } diff --git a/new_editor/src/Workspace/UIEditorWorkspaceModelValidation.cpp b/new_editor/src/Workspace/WorkspaceModelValidation.cpp similarity index 82% rename from new_editor/src/Workspace/UIEditorWorkspaceModelValidation.cpp rename to new_editor/src/Workspace/WorkspaceModelValidation.cpp index 6547ed72..2d5ddb4d 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceModelValidation.cpp +++ b/new_editor/src/Workspace/WorkspaceModelValidation.cpp @@ -1,4 +1,4 @@ -#include "Workspace/UIEditorWorkspaceModelInternal.h" +#include "Workspace/WorkspaceModelInternal.h" namespace XCEngine::UI::Editor { @@ -6,7 +6,7 @@ UIEditorWorkspaceValidationResult ValidateUIEditorWorkspace( const UIEditorWorkspaceModel& workspace) { std::unordered_set panelIds = {}; UIEditorWorkspaceValidationResult result = - Detail::ValidateNodeRecursive(workspace.root, panelIds); + Internal::ValidateNodeRecursive(workspace.root, panelIds); if (!result.IsValid()) { return result; } @@ -15,7 +15,7 @@ UIEditorWorkspaceValidationResult ValidateUIEditorWorkspace( const UIEditorWorkspacePanelState* activePanel = FindUIEditorWorkspaceActivePanel(workspace); if (activePanel == nullptr) { - return Detail::MakeValidationError( + return Internal::MakeValidationError( UIEditorWorkspaceValidationCode::InvalidActivePanelId, "Active panel id '" + workspace.activePanelId + "' is missing or hidden by the current tab selection.");