Refactor new editor scene viewport tools
This commit is contained in:
487
new_editor/app/Features/Scene/SceneViewportController.cpp
Normal file
487
new_editor/app/Features/Scene/SceneViewportController.cpp
Normal file
@@ -0,0 +1,487 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user