Formalize scene viewport chrome and presentation helpers
This commit is contained in:
@@ -1,5 +1,27 @@
|
|||||||
# SceneViewport Overlay/Gizmo Rework Checkpoint
|
# SceneViewport Overlay/Gizmo Rework Checkpoint
|
||||||
|
|
||||||
|
## Update 2026-04-04 Phase 5G
|
||||||
|
|
||||||
|
### Chrome/Focus/Presentation Shell Formalization Completed
|
||||||
|
|
||||||
|
- Added `SceneViewportChrome.{h,cpp}` to take over the remaining scene viewport chrome responsibilities:
|
||||||
|
- top toolbar rendering
|
||||||
|
- in-viewport tool overlay rendering
|
||||||
|
- unified tool-change command building and execution
|
||||||
|
- Extended `SceneViewportInteractionFrame.h` with explicit helpers for:
|
||||||
|
- post-interaction viewport focus decisions
|
||||||
|
- final scene viewport presentation refresh and HUD draw
|
||||||
|
- `SceneViewPanel` no longer owns inline scene toolbar/tool overlay rendering, ad-hoc tool switching glue, focus heuristics, or the final gizmo refresh/HUD presentation tail path.
|
||||||
|
- Added focused editor tests covering tool-command composition/execution and the new focus/presentation helpers.
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
|
||||||
|
- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false`
|
||||||
|
- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportChromeTest.*:SceneViewportNavigationTest.*:SceneViewportInteractionFrameTest.*:SceneViewportInteractionActionsTest.*:SceneViewportInteractionResolverTest.*:SceneViewportTransformGizmoCoordinatorTest.*:SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*`
|
||||||
|
- `cmake --build build --config Debug --target XCEditor`
|
||||||
|
|
||||||
|
All commands completed successfully in `Debug`.
|
||||||
|
|
||||||
## Update 2026-04-04 Phase 5F
|
## Update 2026-04-04 Phase 5F
|
||||||
|
|
||||||
### Interaction Frame/Request Glue Formalization Completed
|
### Interaction Frame/Request Glue Formalization Completed
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ add_executable(${PROJECT_NAME} WIN32
|
|||||||
src/Viewport/SceneViewportRotateGizmo.cpp
|
src/Viewport/SceneViewportRotateGizmo.cpp
|
||||||
src/Viewport/SceneViewportScaleGizmo.cpp
|
src/Viewport/SceneViewportScaleGizmo.cpp
|
||||||
src/Viewport/SceneViewportHudOverlay.cpp
|
src/Viewport/SceneViewportHudOverlay.cpp
|
||||||
|
src/Viewport/SceneViewportChrome.cpp
|
||||||
src/Viewport/SceneViewportInteractionActions.cpp
|
src/Viewport/SceneViewportInteractionActions.cpp
|
||||||
src/Viewport/SceneViewportInteractionResolver.cpp
|
src/Viewport/SceneViewportInteractionResolver.cpp
|
||||||
src/Viewport/SceneViewportTransformGizmoCoordinator.cpp
|
src/Viewport/SceneViewportTransformGizmoCoordinator.cpp
|
||||||
|
|||||||
231
editor/src/Viewport/SceneViewportChrome.cpp
Normal file
231
editor/src/Viewport/SceneViewportChrome.cpp
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
#include "SceneViewportChrome.h"
|
||||||
|
|
||||||
|
#include "Platform/Win32Utf8.h"
|
||||||
|
#include "UI/UI.h"
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const char* GetSceneViewportPivotModeLabel(SceneViewportPivotMode mode) {
|
||||||
|
return mode == SceneViewportPivotMode::Pivot ? "Pivot" : "Center";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* GetSceneViewportTransformSpaceModeLabel(SceneViewportTransformSpaceMode mode) {
|
||||||
|
return mode == SceneViewportTransformSpaceMode::Global ? "Global" : "Local";
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetSceneToolbarToggleWidth(const char* firstLabel, const char* secondLabel) {
|
||||||
|
constexpr float kHorizontalPadding = 10.0f;
|
||||||
|
constexpr float kMinWidth = 68.0f;
|
||||||
|
const float maxLabelWidth = (std::max)(
|
||||||
|
ImGui::CalcTextSize(firstLabel).x,
|
||||||
|
ImGui::CalcTextSize(secondLabel).x);
|
||||||
|
return (std::max)(kMinWidth, maxLabelWidth + kHorizontalPadding * 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* GetSceneViewportToolTooltip(SceneViewportToolMode toolMode) {
|
||||||
|
switch (toolMode) {
|
||||||
|
case SceneViewportToolMode::ViewMove:
|
||||||
|
return "View Move";
|
||||||
|
case SceneViewportToolMode::Move:
|
||||||
|
return "Move";
|
||||||
|
case SceneViewportToolMode::Rotate:
|
||||||
|
return "Rotate";
|
||||||
|
case SceneViewportToolMode::Scale:
|
||||||
|
return "Scale";
|
||||||
|
case SceneViewportToolMode::Transform:
|
||||||
|
return "Transform";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* GetSceneViewportToolIconBaseName(SceneViewportToolMode toolMode) {
|
||||||
|
switch (toolMode) {
|
||||||
|
case SceneViewportToolMode::ViewMove:
|
||||||
|
return "view_move_tool";
|
||||||
|
case SceneViewportToolMode::Move:
|
||||||
|
return "move_tool";
|
||||||
|
case SceneViewportToolMode::Rotate:
|
||||||
|
return "rotate_tool";
|
||||||
|
case SceneViewportToolMode::Scale:
|
||||||
|
return "scale_tool";
|
||||||
|
case SceneViewportToolMode::Transform:
|
||||||
|
return "transform_tool";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BuildSceneViewportIconPath(const char* iconBaseName, bool active) {
|
||||||
|
const std::filesystem::path exeDir(
|
||||||
|
Platform::Utf8ToWide(Platform::GetExecutableDirectoryUtf8()));
|
||||||
|
std::filesystem::path iconPath =
|
||||||
|
exeDir / L".." / L".." / L"resources" / L"Icons" /
|
||||||
|
std::filesystem::path(Platform::Utf8ToWide(iconBaseName));
|
||||||
|
if (active) {
|
||||||
|
iconPath += L"_on";
|
||||||
|
}
|
||||||
|
iconPath += L".png";
|
||||||
|
return Platform::WideToUtf8(iconPath.lexically_normal().wstring());
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& GetSceneViewportToolIconPath(SceneViewportToolMode toolMode, bool active) {
|
||||||
|
static std::string cachedPaths[5][2] = {};
|
||||||
|
const size_t toolIndex = static_cast<size_t>(toolMode);
|
||||||
|
const size_t stateIndex = active ? 1u : 0u;
|
||||||
|
std::string& cachedPath = cachedPaths[toolIndex][stateIndex];
|
||||||
|
if (!cachedPath.empty()) {
|
||||||
|
return cachedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedPath = BuildSceneViewportIconPath(GetSceneViewportToolIconBaseName(toolMode), active);
|
||||||
|
return cachedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void RenderSceneViewportTopBar(
|
||||||
|
SceneViewportPivotMode& pivotMode,
|
||||||
|
SceneViewportTransformSpaceMode& transformSpaceMode) {
|
||||||
|
constexpr float kSceneToolbarHeight = 24.0f;
|
||||||
|
constexpr float kSceneToolbarPaddingY = 0.0f;
|
||||||
|
constexpr float kSceneToolbarButtonHeight = kSceneToolbarHeight - kSceneToolbarPaddingY * 2.0f;
|
||||||
|
constexpr ImVec2 kSceneToolbarButtonFramePadding(8.0f, 1.0f);
|
||||||
|
|
||||||
|
UI::PanelToolbarScope toolbar(
|
||||||
|
"SceneToolbar",
|
||||||
|
kSceneToolbarHeight,
|
||||||
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse,
|
||||||
|
true,
|
||||||
|
ImVec2(UI::ToolbarPadding().x, kSceneToolbarPaddingY),
|
||||||
|
ImVec2(0.0f, UI::ToolbarItemSpacing().y),
|
||||||
|
ImVec4(0.23f, 0.23f, 0.23f, 1.0f));
|
||||||
|
if (!toolbar.IsOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, kSceneToolbarButtonFramePadding);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
|
||||||
|
|
||||||
|
if (UI::ToolbarButton(
|
||||||
|
GetSceneViewportPivotModeLabel(pivotMode),
|
||||||
|
true,
|
||||||
|
ImVec2(GetSceneToolbarToggleWidth("Pivot", "Center"), kSceneToolbarButtonHeight))) {
|
||||||
|
pivotMode = pivotMode == SceneViewportPivotMode::Pivot
|
||||||
|
? SceneViewportPivotMode::Center
|
||||||
|
: SceneViewportPivotMode::Pivot;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine(0.0f, 0.0f);
|
||||||
|
|
||||||
|
if (UI::ToolbarButton(
|
||||||
|
GetSceneViewportTransformSpaceModeLabel(transformSpaceMode),
|
||||||
|
true,
|
||||||
|
ImVec2(GetSceneToolbarToggleWidth("Global", "Local"), kSceneToolbarButtonHeight))) {
|
||||||
|
transformSpaceMode = transformSpaceMode == SceneViewportTransformSpaceMode::Global
|
||||||
|
? SceneViewportTransformSpaceMode::Local
|
||||||
|
: SceneViewportTransformSpaceMode::Global;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneViewportToolOverlayResult RenderSceneViewportToolOverlay(
|
||||||
|
const ViewportPanelContentResult& content,
|
||||||
|
SceneViewportToolMode activeTool) {
|
||||||
|
SceneViewportToolOverlayResult result = {};
|
||||||
|
if (!content.hasViewportArea) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr float kButtonExtent = 30.0f;
|
||||||
|
constexpr float kButtonSpacing = 6.0f;
|
||||||
|
constexpr float kPanelPadding = 6.0f;
|
||||||
|
constexpr float kViewportInset = 10.0f;
|
||||||
|
constexpr float kIconInset = 4.0f;
|
||||||
|
constexpr size_t kToolCount = 5;
|
||||||
|
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
if (drawList == nullptr) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImVec2 panelMin(
|
||||||
|
content.itemMin.x + kViewportInset,
|
||||||
|
content.itemMin.y + kViewportInset);
|
||||||
|
const ImVec2 panelMax(
|
||||||
|
panelMin.x + kPanelPadding * 2.0f + kButtonExtent,
|
||||||
|
panelMin.y + kPanelPadding * 2.0f + kToolCount * kButtonExtent + (kToolCount - 1) * kButtonSpacing);
|
||||||
|
drawList->AddRectFilled(panelMin, panelMax, IM_COL32(24, 26, 29, 220), 7.0f);
|
||||||
|
drawList->AddRect(panelMin, panelMax, IM_COL32(255, 255, 255, 28), 7.0f, 0, 1.0f);
|
||||||
|
|
||||||
|
const SceneViewportToolMode toolModes[kToolCount] = {
|
||||||
|
SceneViewportToolMode::ViewMove,
|
||||||
|
SceneViewportToolMode::Move,
|
||||||
|
SceneViewportToolMode::Rotate,
|
||||||
|
SceneViewportToolMode::Scale,
|
||||||
|
SceneViewportToolMode::Transform
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t index = 0; index < kToolCount; ++index) {
|
||||||
|
const SceneViewportToolMode toolMode = toolModes[index];
|
||||||
|
const bool active = toolMode == activeTool;
|
||||||
|
const ImVec2 buttonMin(
|
||||||
|
panelMin.x + kPanelPadding,
|
||||||
|
panelMin.y + kPanelPadding + index * (kButtonExtent + kButtonSpacing));
|
||||||
|
const ImVec2 buttonMax(buttonMin.x + kButtonExtent, buttonMin.y + kButtonExtent);
|
||||||
|
|
||||||
|
ImGui::SetCursorScreenPos(buttonMin);
|
||||||
|
const std::string buttonId = std::string("##SceneToolButton") + std::to_string(static_cast<int>(toolMode));
|
||||||
|
const bool clicked = ImGui::InvisibleButton(
|
||||||
|
buttonId.c_str(),
|
||||||
|
ImVec2(kButtonExtent, kButtonExtent),
|
||||||
|
ImGuiButtonFlags_MouseButtonLeft |
|
||||||
|
ImGuiButtonFlags_PressedOnClick |
|
||||||
|
ImGuiButtonFlags_AllowOverlap);
|
||||||
|
|
||||||
|
const bool hovered = ImGui::IsItemHovered();
|
||||||
|
const bool held = ImGui::IsItemActive();
|
||||||
|
result.hovered = result.hovered || hovered;
|
||||||
|
result.clicked = result.clicked || clicked;
|
||||||
|
if (clicked) {
|
||||||
|
result.clickedTool = toolMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImU32 backgroundColor = ImGui::GetColorU32(
|
||||||
|
held ? UI::ToolbarButtonActiveColor()
|
||||||
|
: hovered ? UI::ToolbarButtonHoveredColor(active)
|
||||||
|
: UI::ToolbarButtonColor(active));
|
||||||
|
drawList->AddRectFilled(buttonMin, buttonMax, backgroundColor, 5.0f);
|
||||||
|
drawList->AddRect(buttonMin, buttonMax, IM_COL32(255, 255, 255, active ? 48 : 24), 5.0f, 0, 1.0f);
|
||||||
|
|
||||||
|
const ImVec2 iconMin(buttonMin.x + kIconInset, buttonMin.y + kIconInset);
|
||||||
|
const ImVec2 iconMax(buttonMax.x - kIconInset, buttonMax.y - kIconInset);
|
||||||
|
if (!UI::DrawTextureAssetPreview(
|
||||||
|
drawList,
|
||||||
|
iconMin,
|
||||||
|
iconMax,
|
||||||
|
GetSceneViewportToolIconPath(toolMode, active))) {
|
||||||
|
drawList->AddText(
|
||||||
|
ImVec2(buttonMin.x + 8.0f, buttonMin.y + 7.0f),
|
||||||
|
IM_COL32(230, 230, 230, 255),
|
||||||
|
GetSceneViewportToolTooltip(toolMode));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hovered) {
|
||||||
|
ImGui::SetTooltip("%s", GetSceneViewportToolTooltip(toolMode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
88
editor/src/Viewport/SceneViewportChrome.h
Normal file
88
editor/src/Viewport/SceneViewportChrome.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Core/IUndoManager.h"
|
||||||
|
#include "SceneViewportMoveGizmo.h"
|
||||||
|
#include "SceneViewportNavigation.h"
|
||||||
|
#include "SceneViewportRotateGizmo.h"
|
||||||
|
#include "SceneViewportScaleGizmo.h"
|
||||||
|
#include "panels/ViewportPanelContent.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
struct SceneViewportToolOverlayResult {
|
||||||
|
bool hovered = false;
|
||||||
|
bool clicked = false;
|
||||||
|
SceneViewportToolMode clickedTool = SceneViewportToolMode::Move;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SceneViewportToolCommand {
|
||||||
|
SceneViewportToolMode targetTool = SceneViewportToolMode::Move;
|
||||||
|
bool triggered = false;
|
||||||
|
bool cancelMoveGizmo = false;
|
||||||
|
bool cancelRotateGizmo = false;
|
||||||
|
bool cancelScaleGizmo = false;
|
||||||
|
|
||||||
|
bool HasAction() const {
|
||||||
|
return triggered;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void RenderSceneViewportTopBar(
|
||||||
|
SceneViewportPivotMode& pivotMode,
|
||||||
|
SceneViewportTransformSpaceMode& transformSpaceMode);
|
||||||
|
|
||||||
|
SceneViewportToolOverlayResult RenderSceneViewportToolOverlay(
|
||||||
|
const ViewportPanelContentResult& content,
|
||||||
|
SceneViewportToolMode activeTool);
|
||||||
|
|
||||||
|
inline SceneViewportToolCommand BuildSceneViewportToolCommand(
|
||||||
|
const SceneViewportToolOverlayResult& overlayResult,
|
||||||
|
const SceneViewportToolShortcutAction& shortcutAction) {
|
||||||
|
SceneViewportToolCommand command = {};
|
||||||
|
|
||||||
|
if (overlayResult.clicked) {
|
||||||
|
command.targetTool = overlayResult.clickedTool;
|
||||||
|
command.triggered = true;
|
||||||
|
command.cancelMoveGizmo = true;
|
||||||
|
command.cancelRotateGizmo = true;
|
||||||
|
command.cancelScaleGizmo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shortcutAction.HasAction()) {
|
||||||
|
command.targetTool = shortcutAction.targetTool;
|
||||||
|
command.triggered = true;
|
||||||
|
command.cancelMoveGizmo = command.cancelMoveGizmo || shortcutAction.cancelMoveGizmo;
|
||||||
|
command.cancelRotateGizmo = command.cancelRotateGizmo || shortcutAction.cancelRotateGizmo;
|
||||||
|
command.cancelScaleGizmo = command.cancelScaleGizmo || shortcutAction.cancelScaleGizmo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ExecuteSceneViewportToolCommand(
|
||||||
|
const SceneViewportToolCommand& command,
|
||||||
|
IUndoManager& undoManager,
|
||||||
|
SceneViewportMoveGizmo& moveGizmo,
|
||||||
|
SceneViewportRotateGizmo& rotateGizmo,
|
||||||
|
SceneViewportScaleGizmo& scaleGizmo,
|
||||||
|
SceneViewportToolMode& toolMode) {
|
||||||
|
if (!command.HasAction()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.cancelMoveGizmo && moveGizmo.IsActive()) {
|
||||||
|
moveGizmo.CancelDrag(&undoManager);
|
||||||
|
}
|
||||||
|
if (command.cancelRotateGizmo && rotateGizmo.IsActive()) {
|
||||||
|
rotateGizmo.CancelDrag(&undoManager);
|
||||||
|
}
|
||||||
|
if (command.cancelScaleGizmo && scaleGizmo.IsActive()) {
|
||||||
|
scaleGizmo.CancelDrag(&undoManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
toolMode = command.targetTool;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
|
#include "SceneViewportNavigation.h"
|
||||||
#include "IViewportHostService.h"
|
#include "IViewportHostService.h"
|
||||||
#include "SceneViewportEditorModes.h"
|
#include "SceneViewportEditorModes.h"
|
||||||
#include "SceneViewportHudOverlay.h"
|
#include "SceneViewportHudOverlay.h"
|
||||||
|
#include "SceneViewportInteractionActions.h"
|
||||||
#include "SceneViewportInteractionResolver.h"
|
#include "SceneViewportInteractionResolver.h"
|
||||||
#include "SceneViewportTransformGizmoCoordinator.h"
|
#include "SceneViewportTransformGizmoCoordinator.h"
|
||||||
|
|
||||||
@@ -128,5 +130,62 @@ inline SceneViewportInteractionResolveRequest BuildSceneViewportInteractionResol
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool ShouldFocusSceneViewportAfterInteraction(
|
||||||
|
bool toolCommandTriggered,
|
||||||
|
const SceneViewportInteractionActions& interactionActions,
|
||||||
|
const SceneViewportNavigationUpdate& navigationUpdate) {
|
||||||
|
return toolCommandTriggered ||
|
||||||
|
interactionActions.HasClickAction() ||
|
||||||
|
navigationUpdate.beginLookDrag ||
|
||||||
|
navigationUpdate.beginPanDrag;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SceneViewportPresentationRequest {
|
||||||
|
IEditorContext* context = nullptr;
|
||||||
|
IViewportHostService* viewportHostService = nullptr;
|
||||||
|
bool hasInteractiveViewport = false;
|
||||||
|
SceneViewportFrameGeometry geometry = {};
|
||||||
|
SceneViewportTransformGizmoFrameOptions gizmoFrameOptions = {};
|
||||||
|
SceneViewportMoveGizmo* moveGizmo = nullptr;
|
||||||
|
SceneViewportRotateGizmo* rotateGizmo = nullptr;
|
||||||
|
SceneViewportScaleGizmo* scaleGizmo = nullptr;
|
||||||
|
ImDrawList* drawList = nullptr;
|
||||||
|
ImVec2 viewportMin = ImVec2(0.0f, 0.0f);
|
||||||
|
ImVec2 viewportMax = ImVec2(0.0f, 0.0f);
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return context != nullptr &&
|
||||||
|
viewportHostService != nullptr &&
|
||||||
|
moveGizmo != nullptr &&
|
||||||
|
rotateGizmo != nullptr &&
|
||||||
|
scaleGizmo != nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void RefreshAndDrawSceneViewportPresentation(const SceneViewportPresentationRequest& request) {
|
||||||
|
if (!request.IsValid() || !request.hasInteractiveViewport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SceneViewportOverlayData overlay = request.viewportHostService->GetSceneViewOverlayData();
|
||||||
|
RefreshAndSubmitSceneViewportTransformGizmoFrame(
|
||||||
|
*request.viewportHostService,
|
||||||
|
BuildSceneViewportTransformGizmoRefreshRequest(
|
||||||
|
*request.context,
|
||||||
|
overlay,
|
||||||
|
request.geometry.viewportSize,
|
||||||
|
request.geometry.localMousePosition,
|
||||||
|
request.gizmoFrameOptions),
|
||||||
|
*request.moveGizmo,
|
||||||
|
*request.rotateGizmo,
|
||||||
|
*request.scaleGizmo);
|
||||||
|
|
||||||
|
DrawSceneViewportHudOverlay(
|
||||||
|
request.drawList,
|
||||||
|
BuildSceneViewportHudOverlayData(overlay),
|
||||||
|
request.viewportMin,
|
||||||
|
request.viewportMax);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Editor
|
} // namespace Editor
|
||||||
} // namespace XCEngine
|
} // namespace XCEngine
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
#include "Core/ISceneManager.h"
|
#include "Core/ISceneManager.h"
|
||||||
#include "Core/ISelectionManager.h"
|
#include "Core/ISelectionManager.h"
|
||||||
#include "SceneViewPanel.h"
|
#include "SceneViewPanel.h"
|
||||||
|
#include "Viewport/SceneViewportChrome.h"
|
||||||
#include "Viewport/SceneViewportEditorOverlayData.h"
|
#include "Viewport/SceneViewportEditorOverlayData.h"
|
||||||
#include "Viewport/SceneViewportHudOverlay.h"
|
|
||||||
#include "Viewport/SceneViewportInteractionFrame.h"
|
#include "Viewport/SceneViewportInteractionFrame.h"
|
||||||
#include "Viewport/SceneViewportInteractionActions.h"
|
#include "Viewport/SceneViewportInteractionActions.h"
|
||||||
#include "Viewport/SceneViewportInteractionResolver.h"
|
#include "Viewport/SceneViewportInteractionResolver.h"
|
||||||
@@ -12,241 +12,12 @@
|
|||||||
#include "Viewport/SceneViewportNavigation.h"
|
#include "Viewport/SceneViewportNavigation.h"
|
||||||
#include "Viewport/SceneViewportTransformGizmoCoordinator.h"
|
#include "Viewport/SceneViewportTransformGizmoCoordinator.h"
|
||||||
#include "ViewportPanelContent.h"
|
#include "ViewportPanelContent.h"
|
||||||
#include "Platform/Win32Utf8.h"
|
|
||||||
#include "UI/UI.h"
|
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
struct SceneViewportToolOverlayResult {
|
|
||||||
bool hovered = false;
|
|
||||||
bool clicked = false;
|
|
||||||
SceneViewportToolMode clickedTool = SceneViewportToolMode::Move;
|
|
||||||
};
|
|
||||||
|
|
||||||
const char* GetSceneViewportPivotModeLabel(SceneViewportPivotMode mode) {
|
|
||||||
return mode == SceneViewportPivotMode::Pivot ? "Pivot" : "Center";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetSceneViewportTransformSpaceModeLabel(SceneViewportTransformSpaceMode mode) {
|
|
||||||
return mode == SceneViewportTransformSpaceMode::Global ? "Global" : "Local";
|
|
||||||
}
|
|
||||||
|
|
||||||
float GetSceneToolbarToggleWidth(const char* firstLabel, const char* secondLabel) {
|
|
||||||
constexpr float kHorizontalPadding = 10.0f;
|
|
||||||
constexpr float kMinWidth = 68.0f;
|
|
||||||
const float maxLabelWidth = (std::max)(
|
|
||||||
ImGui::CalcTextSize(firstLabel).x,
|
|
||||||
ImGui::CalcTextSize(secondLabel).x);
|
|
||||||
return (std::max)(kMinWidth, maxLabelWidth + kHorizontalPadding * 2.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderSceneViewportTopBar(
|
|
||||||
SceneViewportPivotMode& pivotMode,
|
|
||||||
SceneViewportTransformSpaceMode& transformSpaceMode) {
|
|
||||||
constexpr float kSceneToolbarHeight = 24.0f;
|
|
||||||
constexpr float kSceneToolbarPaddingY = 0.0f;
|
|
||||||
constexpr float kSceneToolbarButtonHeight = kSceneToolbarHeight - kSceneToolbarPaddingY * 2.0f;
|
|
||||||
constexpr ImVec2 kSceneToolbarButtonFramePadding(8.0f, 1.0f);
|
|
||||||
|
|
||||||
UI::PanelToolbarScope toolbar(
|
|
||||||
"SceneToolbar",
|
|
||||||
kSceneToolbarHeight,
|
|
||||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse,
|
|
||||||
true,
|
|
||||||
ImVec2(UI::ToolbarPadding().x, kSceneToolbarPaddingY),
|
|
||||||
ImVec2(0.0f, UI::ToolbarItemSpacing().y),
|
|
||||||
ImVec4(0.23f, 0.23f, 0.23f, 1.0f));
|
|
||||||
if (!toolbar.IsOpen()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, kSceneToolbarButtonFramePadding);
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f);
|
|
||||||
|
|
||||||
if (UI::ToolbarButton(
|
|
||||||
GetSceneViewportPivotModeLabel(pivotMode),
|
|
||||||
true,
|
|
||||||
ImVec2(GetSceneToolbarToggleWidth("Pivot", "Center"), kSceneToolbarButtonHeight))) {
|
|
||||||
pivotMode = pivotMode == SceneViewportPivotMode::Pivot
|
|
||||||
? SceneViewportPivotMode::Center
|
|
||||||
: SceneViewportPivotMode::Pivot;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SameLine(0.0f, 0.0f);
|
|
||||||
|
|
||||||
if (UI::ToolbarButton(
|
|
||||||
GetSceneViewportTransformSpaceModeLabel(transformSpaceMode),
|
|
||||||
true,
|
|
||||||
ImVec2(GetSceneToolbarToggleWidth("Global", "Local"), kSceneToolbarButtonHeight))) {
|
|
||||||
transformSpaceMode = transformSpaceMode == SceneViewportTransformSpaceMode::Global
|
|
||||||
? SceneViewportTransformSpaceMode::Local
|
|
||||||
: SceneViewportTransformSpaceMode::Global;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PopStyleVar(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetSceneViewportToolTooltip(SceneViewportToolMode toolMode) {
|
|
||||||
switch (toolMode) {
|
|
||||||
case SceneViewportToolMode::ViewMove:
|
|
||||||
return "View Move";
|
|
||||||
case SceneViewportToolMode::Move:
|
|
||||||
return "Move";
|
|
||||||
case SceneViewportToolMode::Rotate:
|
|
||||||
return "Rotate";
|
|
||||||
case SceneViewportToolMode::Scale:
|
|
||||||
return "Scale";
|
|
||||||
case SceneViewportToolMode::Transform:
|
|
||||||
return "Transform";
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetSceneViewportToolIconBaseName(SceneViewportToolMode toolMode) {
|
|
||||||
switch (toolMode) {
|
|
||||||
case SceneViewportToolMode::ViewMove:
|
|
||||||
return "view_move_tool";
|
|
||||||
case SceneViewportToolMode::Move:
|
|
||||||
return "move_tool";
|
|
||||||
case SceneViewportToolMode::Rotate:
|
|
||||||
return "rotate_tool";
|
|
||||||
case SceneViewportToolMode::Scale:
|
|
||||||
return "scale_tool";
|
|
||||||
case SceneViewportToolMode::Transform:
|
|
||||||
return "transform_tool";
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string BuildSceneViewportIconPath(const char* iconBaseName, bool active = false) {
|
|
||||||
const std::filesystem::path exeDir(
|
|
||||||
XCEngine::Editor::Platform::Utf8ToWide(XCEngine::Editor::Platform::GetExecutableDirectoryUtf8()));
|
|
||||||
std::filesystem::path iconPath =
|
|
||||||
exeDir / L".." / L".." / L"resources" / L"Icons" /
|
|
||||||
std::filesystem::path(XCEngine::Editor::Platform::Utf8ToWide(iconBaseName));
|
|
||||||
if (active) {
|
|
||||||
iconPath += L"_on";
|
|
||||||
}
|
|
||||||
iconPath += L".png";
|
|
||||||
return XCEngine::Editor::Platform::WideToUtf8(iconPath.lexically_normal().wstring());
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& GetSceneViewportToolIconPath(SceneViewportToolMode toolMode, bool active) {
|
|
||||||
static std::string cachedPaths[5][2] = {};
|
|
||||||
const size_t toolIndex = static_cast<size_t>(toolMode);
|
|
||||||
const size_t stateIndex = active ? 1u : 0u;
|
|
||||||
std::string& cachedPath = cachedPaths[toolIndex][stateIndex];
|
|
||||||
if (!cachedPath.empty()) {
|
|
||||||
return cachedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedPath = BuildSceneViewportIconPath(GetSceneViewportToolIconBaseName(toolMode), active);
|
|
||||||
return cachedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
SceneViewportToolOverlayResult RenderSceneViewportToolOverlay(
|
|
||||||
const ViewportPanelContentResult& content,
|
|
||||||
SceneViewportToolMode activeTool) {
|
|
||||||
SceneViewportToolOverlayResult result = {};
|
|
||||||
if (!content.hasViewportArea) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr float kButtonExtent = 30.0f;
|
|
||||||
constexpr float kButtonSpacing = 6.0f;
|
|
||||||
constexpr float kPanelPadding = 6.0f;
|
|
||||||
constexpr float kViewportInset = 10.0f;
|
|
||||||
constexpr float kIconInset = 4.0f;
|
|
||||||
constexpr size_t kToolCount = 5;
|
|
||||||
|
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
|
||||||
if (drawList == nullptr) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ImVec2 panelMin(
|
|
||||||
content.itemMin.x + kViewportInset,
|
|
||||||
content.itemMin.y + kViewportInset);
|
|
||||||
const ImVec2 panelMax(
|
|
||||||
panelMin.x + kPanelPadding * 2.0f + kButtonExtent,
|
|
||||||
panelMin.y + kPanelPadding * 2.0f + kToolCount * kButtonExtent + (kToolCount - 1) * kButtonSpacing);
|
|
||||||
drawList->AddRectFilled(panelMin, panelMax, IM_COL32(24, 26, 29, 220), 7.0f);
|
|
||||||
drawList->AddRect(panelMin, panelMax, IM_COL32(255, 255, 255, 28), 7.0f, 0, 1.0f);
|
|
||||||
|
|
||||||
const SceneViewportToolMode toolModes[kToolCount] = {
|
|
||||||
SceneViewportToolMode::ViewMove,
|
|
||||||
SceneViewportToolMode::Move,
|
|
||||||
SceneViewportToolMode::Rotate,
|
|
||||||
SceneViewportToolMode::Scale,
|
|
||||||
SceneViewportToolMode::Transform
|
|
||||||
};
|
|
||||||
|
|
||||||
for (size_t index = 0; index < kToolCount; ++index) {
|
|
||||||
const SceneViewportToolMode toolMode = toolModes[index];
|
|
||||||
const bool active = toolMode == activeTool;
|
|
||||||
const ImVec2 buttonMin(
|
|
||||||
panelMin.x + kPanelPadding,
|
|
||||||
panelMin.y + kPanelPadding + index * (kButtonExtent + kButtonSpacing));
|
|
||||||
const ImVec2 buttonMax(buttonMin.x + kButtonExtent, buttonMin.y + kButtonExtent);
|
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(buttonMin);
|
|
||||||
const std::string buttonId = std::string("##SceneToolButton") + std::to_string(static_cast<int>(toolMode));
|
|
||||||
const bool clicked = ImGui::InvisibleButton(
|
|
||||||
buttonId.c_str(),
|
|
||||||
ImVec2(kButtonExtent, kButtonExtent),
|
|
||||||
ImGuiButtonFlags_MouseButtonLeft |
|
|
||||||
ImGuiButtonFlags_PressedOnClick |
|
|
||||||
ImGuiButtonFlags_AllowOverlap);
|
|
||||||
|
|
||||||
const bool hovered = ImGui::IsItemHovered();
|
|
||||||
const bool held = ImGui::IsItemActive();
|
|
||||||
result.hovered = result.hovered || hovered;
|
|
||||||
result.clicked = result.clicked || clicked;
|
|
||||||
if (clicked) {
|
|
||||||
result.clickedTool = toolMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ImU32 backgroundColor = ImGui::GetColorU32(
|
|
||||||
held ? UI::ToolbarButtonActiveColor()
|
|
||||||
: hovered ? UI::ToolbarButtonHoveredColor(active)
|
|
||||||
: UI::ToolbarButtonColor(active));
|
|
||||||
drawList->AddRectFilled(buttonMin, buttonMax, backgroundColor, 5.0f);
|
|
||||||
drawList->AddRect(buttonMin, buttonMax, IM_COL32(255, 255, 255, active ? 48 : 24), 5.0f, 0, 1.0f);
|
|
||||||
|
|
||||||
const ImVec2 iconMin(buttonMin.x + kIconInset, buttonMin.y + kIconInset);
|
|
||||||
const ImVec2 iconMax(buttonMax.x - kIconInset, buttonMax.y - kIconInset);
|
|
||||||
if (!UI::DrawTextureAssetPreview(
|
|
||||||
drawList,
|
|
||||||
iconMin,
|
|
||||||
iconMax,
|
|
||||||
GetSceneViewportToolIconPath(toolMode, active))) {
|
|
||||||
drawList->AddText(
|
|
||||||
ImVec2(buttonMin.x + 8.0f, buttonMin.y + 7.0f),
|
|
||||||
IM_COL32(230, 230, 230, 255),
|
|
||||||
GetSceneViewportToolTooltip(toolMode));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hovered) {
|
|
||||||
ImGui::SetTooltip("%s", GetSceneViewportToolTooltip(toolMode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
SceneViewPanel::SceneViewPanel() : Panel("Scene") {}
|
SceneViewPanel::SceneViewPanel() : Panel("Scene") {}
|
||||||
|
|
||||||
void SceneViewPanel::Render() {
|
void SceneViewPanel::Render() {
|
||||||
@@ -265,11 +36,6 @@ void SceneViewPanel::Render() {
|
|||||||
const SceneViewportToolOverlayResult toolOverlay = RenderSceneViewportToolOverlay(content, m_toolMode);
|
const SceneViewportToolOverlayResult toolOverlay = RenderSceneViewportToolOverlay(content, m_toolMode);
|
||||||
const bool viewportContentHovered = content.hovered && !toolOverlay.hovered;
|
const bool viewportContentHovered = content.hovered && !toolOverlay.hovered;
|
||||||
|
|
||||||
if (toolOverlay.clicked) {
|
|
||||||
CancelSceneViewportTransformGizmoFrame(*m_context, m_moveGizmo, m_rotateGizmo, m_scaleGizmo);
|
|
||||||
m_toolMode = toolOverlay.clickedTool;
|
|
||||||
}
|
|
||||||
|
|
||||||
SceneViewportToolShortcutRequest toolShortcutRequest = {};
|
SceneViewportToolShortcutRequest toolShortcutRequest = {};
|
||||||
toolShortcutRequest.wantTextInput = io.WantTextInput;
|
toolShortcutRequest.wantTextInput = io.WantTextInput;
|
||||||
toolShortcutRequest.lookDragging = m_navigationState.lookDragging;
|
toolShortcutRequest.lookDragging = m_navigationState.lookDragging;
|
||||||
@@ -280,18 +46,15 @@ void SceneViewPanel::Render() {
|
|||||||
toolShortcutRequest.pressedScale = ImGui::IsKeyPressed(ImGuiKey_R, false);
|
toolShortcutRequest.pressedScale = ImGui::IsKeyPressed(ImGuiKey_R, false);
|
||||||
const SceneViewportToolShortcutAction toolShortcutAction =
|
const SceneViewportToolShortcutAction toolShortcutAction =
|
||||||
BuildSceneViewportToolShortcutAction(toolShortcutRequest);
|
BuildSceneViewportToolShortcutAction(toolShortcutRequest);
|
||||||
if (toolShortcutAction.HasAction()) {
|
const SceneViewportToolCommand toolCommand =
|
||||||
if (toolShortcutAction.cancelMoveGizmo && m_moveGizmo.IsActive()) {
|
BuildSceneViewportToolCommand(toolOverlay, toolShortcutAction);
|
||||||
m_moveGizmo.CancelDrag(&m_context->GetUndoManager());
|
ExecuteSceneViewportToolCommand(
|
||||||
}
|
toolCommand,
|
||||||
if (toolShortcutAction.cancelRotateGizmo && m_rotateGizmo.IsActive()) {
|
m_context->GetUndoManager(),
|
||||||
m_rotateGizmo.CancelDrag(&m_context->GetUndoManager());
|
m_moveGizmo,
|
||||||
}
|
m_rotateGizmo,
|
||||||
if (toolShortcutAction.cancelScaleGizmo && m_scaleGizmo.IsActive()) {
|
m_scaleGizmo,
|
||||||
m_scaleGizmo.CancelDrag(&m_context->GetUndoManager());
|
m_toolMode);
|
||||||
}
|
|
||||||
m_toolMode = toolShortcutAction.targetTool;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SceneViewportToolState toolState = BuildSceneViewportToolState(
|
const SceneViewportToolState toolState = BuildSceneViewportToolState(
|
||||||
m_toolMode,
|
m_toolMode,
|
||||||
@@ -362,8 +125,10 @@ void SceneViewPanel::Render() {
|
|||||||
UpdateSceneViewportNavigationState(navigationRequest);
|
UpdateSceneViewportNavigationState(navigationRequest);
|
||||||
m_navigationState = navigationUpdate.state;
|
m_navigationState = navigationUpdate.state;
|
||||||
|
|
||||||
if (toolOverlay.clicked || interactionActions.HasClickAction() || navigationUpdate.beginLookDrag ||
|
if (ShouldFocusSceneViewportAfterInteraction(
|
||||||
navigationUpdate.beginPanDrag) {
|
toolCommand.HasAction(),
|
||||||
|
interactionActions,
|
||||||
|
navigationUpdate)) {
|
||||||
ImGui::SetWindowFocus();
|
ImGui::SetWindowFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,26 +191,19 @@ void SceneViewPanel::Render() {
|
|||||||
|
|
||||||
viewportHostService->UpdateSceneViewInput(*m_context, input);
|
viewportHostService->UpdateSceneViewInput(*m_context, input);
|
||||||
|
|
||||||
if (content.hasViewportArea && content.frame.hasTexture) {
|
SceneViewportPresentationRequest presentationRequest = {};
|
||||||
const SceneViewportOverlayData overlay = viewportHostService->GetSceneViewOverlayData();
|
presentationRequest.context = m_context;
|
||||||
RefreshAndSubmitSceneViewportTransformGizmoFrame(
|
presentationRequest.viewportHostService = viewportHostService;
|
||||||
*viewportHostService,
|
presentationRequest.hasInteractiveViewport = hasInteractiveViewport;
|
||||||
BuildSceneViewportTransformGizmoRefreshRequest(
|
presentationRequest.geometry = frameGeometry;
|
||||||
*m_context,
|
presentationRequest.gizmoFrameOptions = toolState.gizmoFrameOptions;
|
||||||
overlay,
|
presentationRequest.moveGizmo = &m_moveGizmo;
|
||||||
frameGeometry.viewportSize,
|
presentationRequest.rotateGizmo = &m_rotateGizmo;
|
||||||
frameGeometry.localMousePosition,
|
presentationRequest.scaleGizmo = &m_scaleGizmo;
|
||||||
toolState.gizmoFrameOptions),
|
presentationRequest.drawList = ImGui::GetWindowDrawList();
|
||||||
m_moveGizmo,
|
presentationRequest.viewportMin = content.itemMin;
|
||||||
m_rotateGizmo,
|
presentationRequest.viewportMax = content.itemMax;
|
||||||
m_scaleGizmo);
|
RefreshAndDrawSceneViewportPresentation(presentationRequest);
|
||||||
|
|
||||||
DrawSceneViewportHudOverlay(
|
|
||||||
ImGui::GetWindowDrawList(),
|
|
||||||
BuildSceneViewportHudOverlayData(overlay),
|
|
||||||
content.itemMin,
|
|
||||||
content.itemMax);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Actions::ObserveInactiveActionRoute(*m_context);
|
Actions::ObserveInactiveActionRoute(*m_context);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ set(EDITOR_TEST_SOURCES
|
|||||||
test_scene_viewport_interaction_actions.cpp
|
test_scene_viewport_interaction_actions.cpp
|
||||||
test_scene_viewport_interaction_resolver.cpp
|
test_scene_viewport_interaction_resolver.cpp
|
||||||
test_scene_viewport_interaction_frame.cpp
|
test_scene_viewport_interaction_frame.cpp
|
||||||
|
test_scene_viewport_chrome.cpp
|
||||||
test_scene_viewport_transform_gizmo_coordinator.cpp
|
test_scene_viewport_transform_gizmo_coordinator.cpp
|
||||||
test_scene_viewport_shader_paths.cpp
|
test_scene_viewport_shader_paths.cpp
|
||||||
test_scene_viewport_overlay_renderer.cpp
|
test_scene_viewport_overlay_renderer.cpp
|
||||||
|
|||||||
113
tests/editor/test_scene_viewport_chrome.cpp
Normal file
113
tests/editor/test_scene_viewport_chrome.cpp
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "Core/IUndoManager.h"
|
||||||
|
#include "Viewport/SceneViewportChrome.h"
|
||||||
|
#include "Viewport/SceneViewportMoveGizmo.h"
|
||||||
|
#include "Viewport/SceneViewportRotateGizmo.h"
|
||||||
|
#include "Viewport/SceneViewportScaleGizmo.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using XCEngine::Editor::BuildSceneViewportToolCommand;
|
||||||
|
using XCEngine::Editor::ExecuteSceneViewportToolCommand;
|
||||||
|
using XCEngine::Editor::SceneViewportToolCommand;
|
||||||
|
using XCEngine::Editor::SceneViewportToolMode;
|
||||||
|
using XCEngine::Editor::SceneViewportToolOverlayResult;
|
||||||
|
using XCEngine::Editor::SceneViewportToolShortcutAction;
|
||||||
|
|
||||||
|
class StubUndoManager : public XCEngine::Editor::IUndoManager {
|
||||||
|
public:
|
||||||
|
void ClearHistory() override {}
|
||||||
|
bool CanUndo() const override { return false; }
|
||||||
|
bool CanRedo() const override { return false; }
|
||||||
|
const std::string& GetUndoLabel() const override { return empty; }
|
||||||
|
const std::string& GetRedoLabel() const override { return empty; }
|
||||||
|
void Undo() override {}
|
||||||
|
void Redo() override {}
|
||||||
|
XCEngine::Editor::UndoStateSnapshot CaptureCurrentState() const override { return {}; }
|
||||||
|
void PushCommand(const std::string&, XCEngine::Editor::UndoStateSnapshot, XCEngine::Editor::UndoStateSnapshot) override {}
|
||||||
|
void BeginInteractiveChange(const std::string&) override {}
|
||||||
|
bool HasPendingInteractiveChange() const override { return false; }
|
||||||
|
void FinalizeInteractiveChange() override {}
|
||||||
|
void CancelInteractiveChange() override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string empty;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(SceneViewportChromeTest, ToolCommandUsesOverlayClickAsFullToolSwitch) {
|
||||||
|
SceneViewportToolOverlayResult overlay = {};
|
||||||
|
overlay.clicked = true;
|
||||||
|
overlay.clickedTool = SceneViewportToolMode::Transform;
|
||||||
|
|
||||||
|
const SceneViewportToolShortcutAction shortcut = {};
|
||||||
|
const SceneViewportToolCommand command = BuildSceneViewportToolCommand(overlay, shortcut);
|
||||||
|
|
||||||
|
EXPECT_TRUE(command.HasAction());
|
||||||
|
EXPECT_EQ(command.targetTool, SceneViewportToolMode::Transform);
|
||||||
|
EXPECT_TRUE(command.cancelMoveGizmo);
|
||||||
|
EXPECT_TRUE(command.cancelRotateGizmo);
|
||||||
|
EXPECT_TRUE(command.cancelScaleGizmo);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SceneViewportChromeTest, ToolCommandUsesShortcutWhenOverlayDidNotTrigger) {
|
||||||
|
const SceneViewportToolOverlayResult overlay = {};
|
||||||
|
|
||||||
|
SceneViewportToolShortcutAction shortcut = {};
|
||||||
|
shortcut.triggered = true;
|
||||||
|
shortcut.targetTool = SceneViewportToolMode::Rotate;
|
||||||
|
shortcut.cancelMoveGizmo = true;
|
||||||
|
shortcut.cancelScaleGizmo = true;
|
||||||
|
|
||||||
|
const SceneViewportToolCommand command = BuildSceneViewportToolCommand(overlay, shortcut);
|
||||||
|
|
||||||
|
EXPECT_TRUE(command.HasAction());
|
||||||
|
EXPECT_EQ(command.targetTool, SceneViewportToolMode::Rotate);
|
||||||
|
EXPECT_TRUE(command.cancelMoveGizmo);
|
||||||
|
EXPECT_FALSE(command.cancelRotateGizmo);
|
||||||
|
EXPECT_TRUE(command.cancelScaleGizmo);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SceneViewportChromeTest, ShortcutOverridesOverlayTargetWhilePreservingCancelUnion) {
|
||||||
|
SceneViewportToolOverlayResult overlay = {};
|
||||||
|
overlay.clicked = true;
|
||||||
|
overlay.clickedTool = SceneViewportToolMode::Move;
|
||||||
|
|
||||||
|
SceneViewportToolShortcutAction shortcut = {};
|
||||||
|
shortcut.triggered = true;
|
||||||
|
shortcut.targetTool = SceneViewportToolMode::Scale;
|
||||||
|
shortcut.cancelMoveGizmo = true;
|
||||||
|
shortcut.cancelRotateGizmo = true;
|
||||||
|
|
||||||
|
const SceneViewportToolCommand command = BuildSceneViewportToolCommand(overlay, shortcut);
|
||||||
|
|
||||||
|
EXPECT_TRUE(command.HasAction());
|
||||||
|
EXPECT_EQ(command.targetTool, SceneViewportToolMode::Scale);
|
||||||
|
EXPECT_TRUE(command.cancelMoveGizmo);
|
||||||
|
EXPECT_TRUE(command.cancelRotateGizmo);
|
||||||
|
EXPECT_TRUE(command.cancelScaleGizmo);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SceneViewportChromeTest, ExecuteToolCommandUpdatesToolModeWhenTriggered) {
|
||||||
|
StubUndoManager undoManager = {};
|
||||||
|
XCEngine::Editor::SceneViewportMoveGizmo moveGizmo = {};
|
||||||
|
XCEngine::Editor::SceneViewportRotateGizmo rotateGizmo = {};
|
||||||
|
XCEngine::Editor::SceneViewportScaleGizmo scaleGizmo = {};
|
||||||
|
SceneViewportToolMode toolMode = SceneViewportToolMode::Move;
|
||||||
|
|
||||||
|
SceneViewportToolCommand command = {};
|
||||||
|
command.triggered = true;
|
||||||
|
command.targetTool = SceneViewportToolMode::ViewMove;
|
||||||
|
|
||||||
|
ExecuteSceneViewportToolCommand(
|
||||||
|
command,
|
||||||
|
undoManager,
|
||||||
|
moveGizmo,
|
||||||
|
rotateGizmo,
|
||||||
|
scaleGizmo,
|
||||||
|
toolMode);
|
||||||
|
|
||||||
|
EXPECT_EQ(toolMode, SceneViewportToolMode::ViewMove);
|
||||||
|
}
|
||||||
@@ -28,16 +28,21 @@ using XCEngine::Editor::IProjectManager;
|
|||||||
using XCEngine::Editor::ISceneManager;
|
using XCEngine::Editor::ISceneManager;
|
||||||
using XCEngine::Editor::ISelectionManager;
|
using XCEngine::Editor::ISelectionManager;
|
||||||
using XCEngine::Editor::IViewportHostService;
|
using XCEngine::Editor::IViewportHostService;
|
||||||
|
using XCEngine::Editor::RefreshAndDrawSceneViewportPresentation;
|
||||||
using XCEngine::Editor::SceneSnapshot;
|
using XCEngine::Editor::SceneSnapshot;
|
||||||
|
using XCEngine::Editor::SceneViewportInteractionActions;
|
||||||
using XCEngine::Editor::SceneViewportInput;
|
using XCEngine::Editor::SceneViewportInput;
|
||||||
using XCEngine::Editor::SceneViewportInteractionFrameState;
|
using XCEngine::Editor::SceneViewportInteractionFrameState;
|
||||||
|
using XCEngine::Editor::SceneViewportNavigationUpdate;
|
||||||
using XCEngine::Editor::SceneViewportOrientationAxis;
|
using XCEngine::Editor::SceneViewportOrientationAxis;
|
||||||
using XCEngine::Editor::SceneViewportOverlayData;
|
using XCEngine::Editor::SceneViewportOverlayData;
|
||||||
using XCEngine::Editor::SceneViewportOverlayFrameData;
|
using XCEngine::Editor::SceneViewportOverlayFrameData;
|
||||||
using XCEngine::Editor::SceneViewportPivotMode;
|
using XCEngine::Editor::SceneViewportPivotMode;
|
||||||
|
using XCEngine::Editor::SceneViewportPresentationRequest;
|
||||||
using XCEngine::Editor::SceneViewportToolMode;
|
using XCEngine::Editor::SceneViewportToolMode;
|
||||||
using XCEngine::Editor::SceneViewportTransformGizmoOverlayState;
|
using XCEngine::Editor::SceneViewportTransformGizmoOverlayState;
|
||||||
using XCEngine::Editor::SceneViewportTransformSpaceMode;
|
using XCEngine::Editor::SceneViewportTransformSpaceMode;
|
||||||
|
using XCEngine::Editor::ShouldFocusSceneViewportAfterInteraction;
|
||||||
using XCEngine::Rendering::RenderContext;
|
using XCEngine::Rendering::RenderContext;
|
||||||
|
|
||||||
class EmptySelectionManager : public ISelectionManager {
|
class EmptySelectionManager : public ISelectionManager {
|
||||||
@@ -335,3 +340,82 @@ TEST(SceneViewportInteractionFrameTest, ResolveRequestCopiesFrameStateAndViewpor
|
|||||||
EXPECT_FLOAT_EQ(request.absoluteMousePosition.x, 180.0f);
|
EXPECT_FLOAT_EQ(request.absoluteMousePosition.x, 180.0f);
|
||||||
EXPECT_FLOAT_EQ(request.absoluteMousePosition.y, 260.0f);
|
EXPECT_FLOAT_EQ(request.absoluteMousePosition.y, 260.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(SceneViewportInteractionFrameTest, FocusHelperTracksToolActionsAndNavigationBegins) {
|
||||||
|
const SceneViewportInteractionActions noActions = {};
|
||||||
|
const SceneViewportNavigationUpdate noNavigation = {};
|
||||||
|
EXPECT_FALSE(ShouldFocusSceneViewportAfterInteraction(false, noActions, noNavigation));
|
||||||
|
EXPECT_TRUE(ShouldFocusSceneViewportAfterInteraction(true, noActions, noNavigation));
|
||||||
|
|
||||||
|
SceneViewportInteractionActions clickActions = {};
|
||||||
|
clickActions.sceneIconClick = true;
|
||||||
|
EXPECT_TRUE(ShouldFocusSceneViewportAfterInteraction(false, clickActions, noNavigation));
|
||||||
|
|
||||||
|
SceneViewportNavigationUpdate navigationUpdate = {};
|
||||||
|
navigationUpdate.beginPanDrag = true;
|
||||||
|
EXPECT_TRUE(ShouldFocusSceneViewportAfterInteraction(false, noActions, navigationUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SceneViewportInteractionFrameTest, PresentationHelperSkipsNonInteractiveViewport) {
|
||||||
|
StubEditorContext context = {};
|
||||||
|
StubViewportHostService viewportHostService = {};
|
||||||
|
context.viewportHostService = &viewportHostService;
|
||||||
|
|
||||||
|
XCEngine::Editor::SceneViewportMoveGizmo moveGizmo = {};
|
||||||
|
XCEngine::Editor::SceneViewportRotateGizmo rotateGizmo = {};
|
||||||
|
XCEngine::Editor::SceneViewportScaleGizmo scaleGizmo = {};
|
||||||
|
|
||||||
|
SceneViewportPresentationRequest request = {};
|
||||||
|
request.context = &context;
|
||||||
|
request.viewportHostService = &viewportHostService;
|
||||||
|
request.hasInteractiveViewport = false;
|
||||||
|
request.geometry = BuildSceneViewportFrameGeometry(
|
||||||
|
ImVec2(640.0f, 360.0f),
|
||||||
|
ImVec2(10.0f, 20.0f),
|
||||||
|
ImVec2(40.0f, 60.0f));
|
||||||
|
request.gizmoFrameOptions = BuildSceneViewportToolState(
|
||||||
|
SceneViewportToolMode::Move,
|
||||||
|
SceneViewportPivotMode::Pivot,
|
||||||
|
SceneViewportTransformSpaceMode::Global)
|
||||||
|
.gizmoFrameOptions;
|
||||||
|
request.moveGizmo = &moveGizmo;
|
||||||
|
request.rotateGizmo = &rotateGizmo;
|
||||||
|
request.scaleGizmo = &scaleGizmo;
|
||||||
|
|
||||||
|
RefreshAndDrawSceneViewportPresentation(request);
|
||||||
|
EXPECT_EQ(viewportHostService.submissionCount, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SceneViewportInteractionFrameTest, PresentationHelperRefreshesInteractiveViewportWithoutDrawList) {
|
||||||
|
StubEditorContext context = {};
|
||||||
|
StubViewportHostService viewportHostService = {};
|
||||||
|
context.viewportHostService = &viewportHostService;
|
||||||
|
viewportHostService.overlay.valid = true;
|
||||||
|
viewportHostService.overlay.verticalFovDegrees = 50.0f;
|
||||||
|
|
||||||
|
XCEngine::Editor::SceneViewportMoveGizmo moveGizmo = {};
|
||||||
|
XCEngine::Editor::SceneViewportRotateGizmo rotateGizmo = {};
|
||||||
|
XCEngine::Editor::SceneViewportScaleGizmo scaleGizmo = {};
|
||||||
|
|
||||||
|
SceneViewportPresentationRequest request = {};
|
||||||
|
request.context = &context;
|
||||||
|
request.viewportHostService = &viewportHostService;
|
||||||
|
request.hasInteractiveViewport = true;
|
||||||
|
request.geometry = BuildSceneViewportFrameGeometry(
|
||||||
|
ImVec2(640.0f, 360.0f),
|
||||||
|
ImVec2(10.0f, 20.0f),
|
||||||
|
ImVec2(40.0f, 60.0f));
|
||||||
|
request.gizmoFrameOptions = BuildSceneViewportToolState(
|
||||||
|
SceneViewportToolMode::Move,
|
||||||
|
SceneViewportPivotMode::Pivot,
|
||||||
|
SceneViewportTransformSpaceMode::Global)
|
||||||
|
.gizmoFrameOptions;
|
||||||
|
request.moveGizmo = &moveGizmo;
|
||||||
|
request.rotateGizmo = &rotateGizmo;
|
||||||
|
request.scaleGizmo = &scaleGizmo;
|
||||||
|
request.viewportMin = ImVec2(10.0f, 20.0f);
|
||||||
|
request.viewportMax = ImVec2(650.0f, 380.0f);
|
||||||
|
|
||||||
|
RefreshAndDrawSceneViewportPresentation(request);
|
||||||
|
EXPECT_EQ(viewportHostService.submissionCount, 1);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user