Files
XCEngine/new_editor/app/Features/Scene/SceneViewportController.cpp

441 lines
15 KiB
C++
Raw Normal View History

#include "Features/Scene/SceneViewportController.h"
#include "Rendering/Viewport/ViewportHostService.h"
#include "Scene/EditorSceneRuntime.h"
#include "Composition/EditorPanelIds.h"
#include <XCEditor/Viewport/UIEditorViewportInputBridge.h>
#include <XCEditor/Viewport/UIEditorViewportSlot.h>
#include <XCEngine/Input/InputTypes.h>
#include <algorithm>
namespace XCEngine::UI::Editor::App {
namespace {
using ::XCEngine::Input::KeyCode;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIPointerButton;
using ::XCEngine::UI::UIRect;
constexpr float kWheelDeltaPerStep = 120.0f;
bool ContainsPoint(const UIRect& rect, const UIPoint& point) {
return point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height;
}
bool IsKeyDown(
const UIEditorViewportInputBridgeState& state,
KeyCode keyCode) {
return IsUIEditorViewportInputBridgeKeyDown(
state,
static_cast<std::int32_t>(keyCode));
}
bool WasKeyPressed(
const UIEditorViewportInputBridgeFrame& frame,
KeyCode keyCode) {
const std::int32_t expected = static_cast<std::int32_t>(keyCode);
return std::find(
frame.pressedKeyCodes.begin(),
frame.pressedKeyCodes.end(),
expected) != frame.pressedKeyCodes.end();
}
bool HasCameraInput(const SceneViewportCameraInputState& input) {
return input.lookDeltaX != 0.0f ||
input.lookDeltaY != 0.0f ||
input.panDeltaX != 0.0f ||
input.panDeltaY != 0.0f ||
input.zoomDelta != 0.0f ||
input.flySpeedDelta != 0.0f ||
input.moveForward != 0.0f ||
input.moveRight != 0.0f ||
input.moveUp != 0.0f;
}
float NormalizeWheelDelta(float wheelDelta) {
return wheelDelta / kWheelDeltaPerStep;
}
2026-04-18 23:56:17 +08:00
bool IsViewMoveTool(SceneToolMode mode) {
return mode == SceneToolMode::View;
}
bool IsPanDragButtonDown(
const UIEditorViewportInputBridgeState& state,
UIPointerButton button) {
return button != UIPointerButton::None &&
IsUIEditorViewportInputBridgePointerButtonDown(state, button);
}
bool TryResolveToolModeShortcut(
const UIEditorViewportInputBridgeFrame& inputFrame,
SceneToolMode& outMode) {
if (inputFrame.modifiers.control ||
inputFrame.modifiers.alt ||
inputFrame.modifiers.super) {
return false;
}
if (WasKeyPressed(inputFrame, KeyCode::Q)) {
outMode = SceneToolMode::View;
return true;
}
if (WasKeyPressed(inputFrame, KeyCode::W)) {
outMode = SceneToolMode::Translate;
return true;
}
if (WasKeyPressed(inputFrame, KeyCode::E)) {
outMode = SceneToolMode::Rotate;
return true;
}
if (WasKeyPressed(inputFrame, KeyCode::R)) {
outMode = SceneToolMode::Scale;
return true;
}
return false;
}
void ApplySceneToolMode(
SceneToolMode mode,
EditorSceneRuntime& sceneRuntime,
LegacySceneViewportGizmo& legacyGizmo) {
if (sceneRuntime.GetToolMode() == mode) {
return;
}
legacyGizmo.CancelDrag(sceneRuntime);
sceneRuntime.SetToolMode(mode);
}
} // namespace
void SceneViewportController::Initialize(
const std::filesystem::path& repoRoot,
Host::TextureHost& renderer) {
m_toolOverlay.Initialize(repoRoot, renderer);
ResetInteractionState();
}
void SceneViewportController::Shutdown(Host::TextureHost& renderer) {
m_toolOverlay.Shutdown(renderer);
ResetInteractionState();
}
void SceneViewportController::ResetInteractionState() {
ResetFrameState();
m_toolOverlay.ResetFrame();
m_legacyGizmo.ResetVisualState();
}
void SceneViewportController::Update(
EditorSceneRuntime& sceneRuntime,
ViewportHostService& viewportHostService,
const UIEditorWorkspaceComposeState& composeState,
const UIEditorWorkspaceComposeFrame& composeFrame) {
const UIEditorWorkspaceViewportComposeFrame* viewportFrame =
FindUIEditorWorkspaceViewportPresentationFrame(composeFrame, kScenePanelId);
const UIEditorWorkspacePanelPresentationState* panelState =
FindUIEditorWorkspacePanelPresentationState(composeState, kScenePanelId);
if (viewportFrame == nullptr || panelState == nullptr) {
if (m_legacyGizmo.IsActive()) {
m_legacyGizmo.CancelDrag(sceneRuntime);
}
sceneRuntime.SetHoveredToolHandle(SceneToolHandle::None);
ResetInteractionState();
return;
}
const auto& inputFrame = viewportFrame->viewportShellFrame.inputFrame;
const auto& inputState = panelState->viewportShellState.inputBridgeState;
const auto& slotLayout = viewportFrame->viewportShellFrame.slotLayout;
const bool leftMouseDown = IsUIEditorViewportInputBridgePointerButtonDown(
inputState,
UIPointerButton::Left);
const bool rightMouseDown = IsUIEditorViewportInputBridgePointerButtonDown(
inputState,
UIPointerButton::Right);
const bool middleMouseDown = IsUIEditorViewportInputBridgePointerButtonDown(
inputState,
UIPointerButton::Middle);
const UIPoint pointerScreen = inputState.hasPointerPosition
? inputState.lastScreenPointerPosition
: inputFrame.screenPointerPosition;
if (inputFrame.focusLost) {
m_navigationState = {};
m_hoveredToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex;
m_activeToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex;
if (m_legacyGizmo.IsActive()) {
m_legacyGizmo.CancelDrag(sceneRuntime);
}
}
m_toolOverlay.BuildFrame(
slotLayout.inputRect,
sceneRuntime.GetToolMode(),
kSceneViewportToolOverlayInvalidIndex,
kSceneViewportToolOverlayInvalidIndex);
const bool pointerOverToolOverlay =
inputState.hasPointerPosition &&
m_toolOverlay.Contains(pointerScreen);
const std::size_t hoveredToolOverlayIndex =
inputState.hasPointerPosition
? m_toolOverlay.HitTest(pointerScreen)
: kSceneViewportToolOverlayInvalidIndex;
2026-04-18 23:56:17 +08:00
for (const auto& transition : inputFrame.pointerButtonTransitions) {
if (transition.button != UIPointerButton::Left) {
continue;
}
const std::size_t transitionHoveredToolOverlayIndex =
m_toolOverlay.HitTest(transition.screenPosition);
if (transition.pressed &&
transitionHoveredToolOverlayIndex !=
kSceneViewportToolOverlayInvalidIndex &&
!m_legacyGizmo.IsActive()) {
m_activeToolOverlayIndex = transitionHoveredToolOverlayIndex;
if (m_activeToolOverlayIndex < m_toolOverlay.GetFrame().buttons.size()) {
ApplySceneToolMode(
m_toolOverlay.GetFrame().buttons[m_activeToolOverlayIndex].mode,
sceneRuntime,
m_legacyGizmo);
}
continue;
}
2026-04-18 23:56:17 +08:00
m_activeToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex;
2026-04-18 23:56:17 +08:00
}
if (!leftMouseDown) {
m_activeToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex;
}
m_hoveredToolOverlayIndex = hoveredToolOverlayIndex;
m_toolOverlay.BuildFrame(
slotLayout.inputRect,
sceneRuntime.GetToolMode(),
m_hoveredToolOverlayIndex,
m_activeToolOverlayIndex);
const bool toolOverlayInteractionActive =
m_activeToolOverlayIndex != kSceneViewportToolOverlayInvalidIndex;
2026-04-18 23:56:17 +08:00
const bool usingViewMoveTool = IsViewMoveTool(sceneRuntime.GetToolMode());
if (!m_legacyGizmo.IsActive()) {
for (const auto& transition : inputFrame.pointerButtonTransitions) {
if (!transition.pressed ||
!ContainsPoint(slotLayout.inputRect, transition.screenPosition) ||
m_toolOverlay.Contains(transition.screenPosition)) {
continue;
}
if (usingViewMoveTool &&
transition.button == UIPointerButton::Left) {
m_navigationState.panDragging = true;
m_navigationState.lookDragging = false;
m_navigationState.panDragButton = UIPointerButton::Left;
} else if (transition.button == UIPointerButton::Right) {
m_navigationState.lookDragging = true;
m_navigationState.panDragging = false;
m_navigationState.panDragButton = UIPointerButton::None;
} else if (transition.button == UIPointerButton::Middle) {
m_navigationState.panDragging = true;
m_navigationState.lookDragging = false;
m_navigationState.panDragButton = UIPointerButton::Middle;
}
}
}
if (m_navigationState.lookDragging && !rightMouseDown) {
m_navigationState.lookDragging = false;
}
2026-04-18 23:56:17 +08:00
if (m_navigationState.panDragging &&
!IsPanDragButtonDown(inputState, m_navigationState.panDragButton)) {
m_navigationState.panDragging = false;
2026-04-18 23:56:17 +08:00
m_navigationState.panDragButton = UIPointerButton::None;
}
const bool viewportHoverEligible =
inputState.hasPointerPosition &&
ContainsPoint(slotLayout.inputRect, pointerScreen) &&
!pointerOverToolOverlay &&
!toolOverlayInteractionActive &&
!m_navigationState.lookDragging &&
!m_navigationState.panDragging;
if (!m_legacyGizmo.IsActive() &&
!toolOverlayInteractionActive &&
2026-04-18 23:56:17 +08:00
inputFrame.focused &&
!m_navigationState.lookDragging &&
!m_navigationState.panDragging) {
SceneToolMode shortcutMode = SceneToolMode::View;
if (TryResolveToolModeShortcut(inputFrame, shortcutMode)) {
ApplySceneToolMode(shortcutMode, sceneRuntime, m_legacyGizmo);
}
}
if (inputFrame.focused &&
!m_legacyGizmo.IsActive() &&
WasKeyPressed(inputFrame, KeyCode::F)) {
sceneRuntime.FocusSceneSelection();
}
m_legacyGizmo.Refresh(
sceneRuntime,
slotLayout.inputRect,
pointerScreen,
viewportHoverEligible);
sceneRuntime.SetHoveredToolHandle(SceneToolHandle::None);
if (m_legacyGizmo.IsActive()) {
if (WasKeyPressed(inputFrame, KeyCode::Escape)) {
m_legacyGizmo.CancelDrag(sceneRuntime);
m_legacyGizmo.Refresh(
sceneRuntime,
slotLayout.inputRect,
pointerScreen,
viewportHoverEligible);
return;
}
2026-04-18 23:56:17 +08:00
if (!leftMouseDown) {
m_legacyGizmo.EndDrag(sceneRuntime);
m_legacyGizmo.Refresh(
sceneRuntime,
slotLayout.inputRect,
pointerScreen,
viewportHoverEligible);
return;
}
m_legacyGizmo.UpdateDrag(sceneRuntime);
m_legacyGizmo.Refresh(
sceneRuntime,
slotLayout.inputRect,
pointerScreen,
viewportHoverEligible);
return;
}
2026-04-18 23:56:17 +08:00
if (!usingViewMoveTool) {
for (const auto& transition : inputFrame.pointerButtonTransitions) {
if (!transition.pressed ||
transition.button != UIPointerButton::Left ||
!ContainsPoint(slotLayout.inputRect, transition.screenPosition) ||
m_toolOverlay.Contains(transition.screenPosition)) {
continue;
}
2026-04-18 23:56:17 +08:00
m_legacyGizmo.Refresh(
sceneRuntime,
slotLayout.inputRect,
transition.screenPosition,
true);
if (m_legacyGizmo.IsHoveringHandle() &&
m_legacyGizmo.TryBeginDrag(sceneRuntime)) {
m_navigationState = {};
m_legacyGizmo.Refresh(
sceneRuntime,
slotLayout.inputRect,
pointerScreen,
viewportHoverEligible);
return;
}
2026-04-18 23:56:17 +08:00
const ViewportObjectIdPickResult pickResult =
viewportHostService.PickSceneViewportObject(
viewportFrame->viewportShellFrame.requestedViewportSize,
transition.localPointerPosition);
if (pickResult.status == ViewportObjectIdPickStatus::Success) {
if (pickResult.entityId != 0u) {
sceneRuntime.SetSelection(pickResult.entityId);
} else {
sceneRuntime.ClearSelection();
}
}
m_legacyGizmo.Refresh(
sceneRuntime,
slotLayout.inputRect,
pointerScreen,
viewportHoverEligible);
}
}
2026-04-18 23:56:17 +08:00
if (toolOverlayInteractionActive) {
return;
}
SceneViewportCameraInputState input = {};
input.deltaTime = ConsumeDeltaTimeSeconds();
input.viewportHeight =
viewportFrame->viewportShellFrame.requestedViewportSize.height;
if (m_navigationState.lookDragging) {
input.lookDeltaX = inputFrame.pointerDelta.x;
input.lookDeltaY = inputFrame.pointerDelta.y;
input.flySpeedDelta = NormalizeWheelDelta(inputFrame.wheelDelta);
input.fastMove = inputFrame.modifiers.shift;
input.moveForward =
(IsKeyDown(inputState, KeyCode::W) ? 1.0f : 0.0f) -
(IsKeyDown(inputState, KeyCode::S) ? 1.0f : 0.0f);
input.moveRight =
(IsKeyDown(inputState, KeyCode::D) ? 1.0f : 0.0f) -
(IsKeyDown(inputState, KeyCode::A) ? 1.0f : 0.0f);
input.moveUp =
(IsKeyDown(inputState, KeyCode::E) ? 1.0f : 0.0f) -
(IsKeyDown(inputState, KeyCode::Q) ? 1.0f : 0.0f);
} else if (m_navigationState.panDragging) {
input.panDeltaX = inputFrame.pointerDelta.x;
input.panDeltaY = inputFrame.pointerDelta.y;
2026-04-18 23:56:17 +08:00
} else if (ContainsPoint(slotLayout.inputRect, pointerScreen) && !pointerOverToolOverlay) {
input.zoomDelta = NormalizeWheelDelta(inputFrame.wheelDelta);
}
if (HasCameraInput(input)) {
sceneRuntime.ApplySceneViewportCameraInput(input);
m_legacyGizmo.Refresh(
sceneRuntime,
slotLayout.inputRect,
pointerScreen,
viewportHoverEligible);
}
}
void SceneViewportController::Append(::XCEngine::UI::UIDrawList& drawList) const {
AppendLegacySceneViewportGizmo(drawList, m_legacyGizmo.GetFrame());
AppendSceneViewportToolOverlay(drawList, m_toolOverlay.GetFrame());
}
void SceneViewportController::ResetFrameState() {
m_navigationState = {};
m_hoveredToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex;
m_activeToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex;
m_lastUpdateTime = {};
m_hasLastUpdateTime = false;
}
float SceneViewportController::ConsumeDeltaTimeSeconds() {
const auto now = std::chrono::steady_clock::now();
if (!m_hasLastUpdateTime) {
m_lastUpdateTime = now;
m_hasLastUpdateTime = true;
return 0.0f;
}
const float deltaTime =
std::chrono::duration<float>(now - m_lastUpdateTime).count();
m_lastUpdateTime = now;
return std::clamp(deltaTime, 0.0f, 0.1f);
}
} // namespace XCEngine::UI::Editor::App