459 lines
16 KiB
C++
459 lines
16 KiB
C++
#include "Features/Scene/SceneViewportController.h"
|
|
|
|
#include "Rendering/Viewport/ViewportHostService.h"
|
|
#include "Scene/EditorSceneRuntime.h"
|
|
#include "State/EditorCommandFocusService.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;
|
|
}
|
|
|
|
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::SetCommandFocusService(
|
|
EditorCommandFocusService* commandFocusService) {
|
|
m_commandFocusService = commandFocusService;
|
|
}
|
|
|
|
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 (m_commandFocusService != nullptr &&
|
|
(inputFrame.focused ||
|
|
std::any_of(
|
|
inputFrame.pointerButtonTransitions.begin(),
|
|
inputFrame.pointerButtonTransitions.end(),
|
|
[&](const auto& transition) {
|
|
return transition.pressed &&
|
|
ContainsPoint(slotLayout.inputRect, transition.screenPosition);
|
|
}))) {
|
|
m_commandFocusService->ClaimFocus(EditorActionRoute::Scene);
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
m_activeToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex;
|
|
}
|
|
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;
|
|
|
|
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;
|
|
}
|
|
if (m_navigationState.panDragging &&
|
|
!IsPanDragButtonDown(inputState, m_navigationState.panDragButton)) {
|
|
m_navigationState.panDragging = false;
|
|
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 &&
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
} 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
|