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

488 lines
17 KiB
C++
Raw Normal View History

#include "Features/Scene/SceneViewportController.h"
#include "Rendering/Viewport/ViewportHostService.h"
#include "Scene/EditorSceneRuntime.h"
#include <Rendering/Native/NativeRenderer.h>
#include <XCEditor/App/EditorPanelIds.h>
#include <XCEditor/Viewport/UIEditorViewportInputBridge.h>
#include <XCEditor/Viewport/UIEditorViewportSlot.h>
#include <XCEngine/Input/InputTypes.h>
#include <algorithm>
#include <string_view>
namespace XCEngine::UI::Editor::App {
namespace {
using ::XCEngine::Input::KeyCode;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIPointerButton;
using ::XCEngine::UI::UIRect;
using ::XCEngine::UI::Editor::Widgets::HitTestUIEditorViewportSlot;
using ::XCEngine::UI::Editor::Widgets::UIEditorViewportSlotHitTarget;
using ::XCEngine::UI::Editor::Widgets::UIEditorViewportSlotHitTargetKind;
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;
}
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;
}
bool ApplySceneToolbarCommand(
std::string_view itemId,
EditorSceneRuntime& sceneRuntime) {
if (itemId == "scene.pivot.pivot") {
sceneRuntime.SetToolPivotMode(SceneToolPivotMode::Pivot);
return true;
}
if (itemId == "scene.pivot.center") {
sceneRuntime.SetToolPivotMode(SceneToolPivotMode::Center);
return true;
}
if (itemId == "scene.space.world") {
sceneRuntime.SetToolSpaceMode(SceneToolSpaceMode::World);
return true;
}
if (itemId == "scene.space.local") {
sceneRuntime.SetToolSpaceMode(SceneToolSpaceMode::Local);
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::NativeRenderer& renderer) {
m_toolOverlay.Initialize(repoRoot, renderer);
ResetInteractionState();
}
void SceneViewportController::Shutdown(Host::NativeRenderer& 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.ClearToolbarInteraction();
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 auto& toolItems = viewportFrame->viewportShellModel.spec.toolItems;
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);
}
sceneRuntime.ClearToolbarInteraction();
}
std::size_t hoveredToolbarIndex = kSceneToolInvalidToolbarIndex;
if (!toolItems.empty() && inputState.hasPointerPosition) {
const UIEditorViewportSlotHitTarget toolbarHit =
HitTestUIEditorViewportSlot(slotLayout, pointerScreen);
if (toolbarHit.kind == UIEditorViewportSlotHitTargetKind::ToolItem &&
toolbarHit.index < toolItems.size() &&
toolItems[toolbarHit.index].enabled) {
hoveredToolbarIndex = toolbarHit.index;
}
}
if (m_legacyGizmo.IsActive()) {
sceneRuntime.ClearToolbarInteraction();
} else {
sceneRuntime.SetToolbarHoveredIndex(hoveredToolbarIndex);
if (inputFrame.changedPointerButton == UIPointerButton::Left &&
leftMouseDown &&
hoveredToolbarIndex != kSceneToolInvalidToolbarIndex) {
sceneRuntime.SetToolbarActiveIndex(hoveredToolbarIndex);
sceneRuntime.SetToolInteractionLock(SceneToolInteractionLock::Toolbar);
} else if (inputFrame.changedPointerButton == UIPointerButton::Left &&
!leftMouseDown) {
const std::size_t activeToolbarIndex =
sceneRuntime.GetToolState().toolbarActiveIndex;
const bool applyCommand =
activeToolbarIndex != kSceneToolInvalidToolbarIndex &&
activeToolbarIndex == hoveredToolbarIndex &&
activeToolbarIndex < toolItems.size() &&
toolItems[activeToolbarIndex].enabled;
sceneRuntime.ClearToolbarInteraction();
if (applyCommand) {
ApplySceneToolbarCommand(
toolItems[activeToolbarIndex].itemId,
sceneRuntime);
}
} else if (!leftMouseDown &&
sceneRuntime.GetToolState().interactionLock ==
SceneToolInteractionLock::Toolbar) {
sceneRuntime.ClearToolbarInteraction();
}
}
const bool toolbarInteractionActive =
sceneRuntime.GetToolState().interactionLock ==
SceneToolInteractionLock::Toolbar;
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;
if (inputFrame.changedPointerButton == UIPointerButton::Left &&
leftMouseDown &&
hoveredToolOverlayIndex != kSceneViewportToolOverlayInvalidIndex &&
!toolbarInteractionActive &&
!m_legacyGizmo.IsActive()) {
m_activeToolOverlayIndex = hoveredToolOverlayIndex;
} else if (inputFrame.changedPointerButton == UIPointerButton::Left &&
!leftMouseDown) {
if (m_activeToolOverlayIndex != kSceneViewportToolOverlayInvalidIndex &&
m_activeToolOverlayIndex == hoveredToolOverlayIndex &&
m_activeToolOverlayIndex < m_toolOverlay.GetFrame().buttons.size()) {
ApplySceneToolMode(
m_toolOverlay.GetFrame().buttons[m_activeToolOverlayIndex].mode,
sceneRuntime,
m_legacyGizmo);
}
m_activeToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex;
} else 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;
if (!toolbarInteractionActive &&
!toolOverlayInteractionActive &&
!m_legacyGizmo.IsActive()) {
if (inputFrame.pointerPressedInside &&
inputFrame.changedPointerButton == UIPointerButton::Right &&
!pointerOverToolOverlay) {
m_navigationState.lookDragging = true;
m_navigationState.panDragging = false;
}
if (inputFrame.pointerPressedInside &&
inputFrame.changedPointerButton == UIPointerButton::Middle &&
!pointerOverToolOverlay) {
m_navigationState.panDragging = true;
m_navigationState.lookDragging = false;
}
}
if (m_navigationState.lookDragging && !rightMouseDown) {
m_navigationState.lookDragging = false;
}
if (m_navigationState.panDragging && !middleMouseDown) {
m_navigationState.panDragging = false;
}
const bool viewportHoverEligible =
inputState.hasPointerPosition &&
ContainsPoint(slotLayout.inputRect, pointerScreen) &&
!pointerOverToolOverlay &&
!toolbarInteractionActive &&
!toolOverlayInteractionActive &&
!m_navigationState.lookDragging &&
!m_navigationState.panDragging;
if (!m_legacyGizmo.IsActive() &&
!toolbarInteractionActive &&
!toolOverlayInteractionActive &&
inputFrame.focused) {
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;
}
if (inputFrame.changedPointerButton == UIPointerButton::Left &&
!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;
}
const bool shouldStartTransformDrag =
inputFrame.pointerPressedInside &&
inputFrame.changedPointerButton == UIPointerButton::Left &&
viewportHoverEligible &&
m_legacyGizmo.IsHoveringHandle();
if (shouldStartTransformDrag &&
m_legacyGizmo.TryBeginDrag(sceneRuntime)) {
m_navigationState = {};
m_legacyGizmo.Refresh(
sceneRuntime,
slotLayout.inputRect,
pointerScreen,
viewportHoverEligible);
return;
}
const bool shouldPickSelection =
inputFrame.pointerPressedInside &&
inputFrame.changedPointerButton == UIPointerButton::Left &&
viewportHoverEligible &&
!m_legacyGizmo.IsHoveringHandle();
if (shouldPickSelection) {
const ViewportObjectIdPickResult pickResult =
viewportHostService.PickSceneViewportObject(
viewportFrame->viewportShellFrame.requestedViewportSize,
inputFrame.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);
}
if (toolbarInteractionActive || 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;
} else if (inputFrame.hovered && !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