关键节点
This commit is contained in:
442
editor/app/Features/Scene/SceneViewportToolOverlay.cpp
Normal file
442
editor/app/Features/Scene/SceneViewportToolOverlay.cpp
Normal file
@@ -0,0 +1,442 @@
|
||||
#include "Features/Scene/SceneViewportToolOverlay.h"
|
||||
|
||||
#include "Rendering/Host/UiTextureHost.h"
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorTextLayout.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::UI::UIColor;
|
||||
using ::XCEngine::UI::UIDrawList;
|
||||
using ::XCEngine::UI::UIPoint;
|
||||
using ::XCEngine::UI::UIRect;
|
||||
using Widgets::MeasureUIEditorTextWidth;
|
||||
using Widgets::ResolveUIEditorTextClipRect;
|
||||
using Widgets::ResolveUIEditorTextTop;
|
||||
|
||||
constexpr float kButtonExtent = 26.0f;
|
||||
constexpr float kButtonSpacing = 4.0f;
|
||||
constexpr float kPanelPadding = 5.0f;
|
||||
constexpr float kViewportInset = 10.0f;
|
||||
constexpr float kIconInset = 3.0f;
|
||||
constexpr float kPanelCornerRounding = 6.0f;
|
||||
constexpr float kButtonCornerRounding = 4.0f;
|
||||
constexpr float kFallbackFontSize = 10.0f;
|
||||
constexpr float kToggleFontSize = 13.0f;
|
||||
constexpr float kToggleHorizontalPadding = 8.0f;
|
||||
constexpr float kTopBarButtonInsetX = 0.0f;
|
||||
constexpr float kTopBarButtonInsetY = 3.0f;
|
||||
constexpr float kToggleButtonSpacing = 6.0f;
|
||||
constexpr float kToggleButtonCornerRounding = 5.0f;
|
||||
constexpr float kToggleMinWidth = 60.0f;
|
||||
|
||||
struct ToolButtonSpec {
|
||||
SceneToolMode mode = SceneToolMode::View;
|
||||
const char* label = "";
|
||||
const char* inactiveFile = "";
|
||||
const char* activeFile = "";
|
||||
};
|
||||
|
||||
constexpr std::array<ToolButtonSpec, 5> kToolButtonSpecs = {{
|
||||
{ SceneToolMode::View, "View", "view_move_tool.png", "view_move_tool_on.png" },
|
||||
{ SceneToolMode::Translate, "Move", "move_tool.png", "move_tool_on.png" },
|
||||
{ SceneToolMode::Rotate, "Rotate", "rotate_tool.png", "rotate_tool_on.png" },
|
||||
{ SceneToolMode::Scale, "Scale", "scale_tool.png", "scale_tool_on.png" },
|
||||
{ SceneToolMode::Transform, "Transform", "transform_tool.png", "transform_tool_on.png" }
|
||||
}};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::string_view ResolveToggleLabel(
|
||||
SceneViewportToggleButtonKind kind,
|
||||
SceneToolPivotMode pivotMode,
|
||||
SceneToolSpaceMode spaceMode) {
|
||||
switch (kind) {
|
||||
case SceneViewportToggleButtonKind::Pivot:
|
||||
return pivotMode == SceneToolPivotMode::Center
|
||||
? std::string_view("Center")
|
||||
: std::string_view("Pivot");
|
||||
case SceneViewportToggleButtonKind::Space:
|
||||
return spaceMode == SceneToolSpaceMode::Local
|
||||
? std::string_view("Local")
|
||||
: std::string_view("Global");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
float ResolveToggleWidth(std::string_view firstLabel, std::string_view secondLabel) {
|
||||
const float firstWidth =
|
||||
MeasureUIEditorTextWidth(std::string(firstLabel), kToggleFontSize);
|
||||
const float secondWidth =
|
||||
MeasureUIEditorTextWidth(std::string(secondLabel), kToggleFontSize);
|
||||
return (std::max)(
|
||||
kToggleMinWidth,
|
||||
std::ceil((std::max)(firstWidth, secondWidth) + kToggleHorizontalPadding * 2.0f));
|
||||
}
|
||||
|
||||
float ResolveToggleWidth(SceneViewportToggleButtonKind kind) {
|
||||
switch (kind) {
|
||||
case SceneViewportToggleButtonKind::Pivot:
|
||||
return ResolveToggleWidth("Pivot", "Center");
|
||||
case SceneViewportToggleButtonKind::Space:
|
||||
return ResolveToggleWidth("Global", "Local");
|
||||
default:
|
||||
return kToggleMinWidth;
|
||||
}
|
||||
}
|
||||
|
||||
UIRect BuildButtonRect(const UIRect& panelRect, std::size_t index) {
|
||||
return UIRect(
|
||||
panelRect.x + kPanelPadding,
|
||||
panelRect.y + kPanelPadding + static_cast<float>(index) * (kButtonExtent + kButtonSpacing),
|
||||
kButtonExtent,
|
||||
kButtonExtent);
|
||||
}
|
||||
|
||||
UIRect BuildToggleButtonRect(
|
||||
const UIRect& topBarRect,
|
||||
std::size_t index,
|
||||
float width) {
|
||||
const float buttonHeight =
|
||||
(std::max)(topBarRect.height - kTopBarButtonInsetY * 2.0f, 0.0f);
|
||||
return UIRect(
|
||||
topBarRect.x + kTopBarButtonInsetX + static_cast<float>(index) * (width + kToggleButtonSpacing),
|
||||
topBarRect.y + kTopBarButtonInsetY,
|
||||
width,
|
||||
buttonHeight);
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIColor ResolveToolButtonFill(
|
||||
bool active,
|
||||
bool hovered,
|
||||
bool pressed) {
|
||||
constexpr UIColor kButtonIdle(
|
||||
44.0f / 255.0f,
|
||||
44.0f / 255.0f,
|
||||
44.0f / 255.0f,
|
||||
230.0f / 255.0f);
|
||||
constexpr UIColor kButtonHover(
|
||||
66.0f / 255.0f,
|
||||
66.0f / 255.0f,
|
||||
66.0f / 255.0f,
|
||||
240.0f / 255.0f);
|
||||
constexpr UIColor kButtonPressed(
|
||||
92.0f / 255.0f,
|
||||
92.0f / 255.0f,
|
||||
92.0f / 255.0f,
|
||||
245.0f / 255.0f);
|
||||
constexpr UIColor kButtonActive(
|
||||
84.0f / 255.0f,
|
||||
84.0f / 255.0f,
|
||||
84.0f / 255.0f,
|
||||
245.0f / 255.0f);
|
||||
|
||||
if (pressed) {
|
||||
return kButtonPressed;
|
||||
}
|
||||
if (hovered) {
|
||||
return active ? kButtonActive : kButtonHover;
|
||||
}
|
||||
return active ? kButtonActive : kButtonIdle;
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIColor ResolveToolIconTint(
|
||||
bool active,
|
||||
bool hovered,
|
||||
bool pressed) {
|
||||
if (pressed) {
|
||||
return UIColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
if (active) {
|
||||
return hovered
|
||||
? UIColor(1.0f, 1.0f, 1.0f, 1.0f)
|
||||
: UIColor(0.98f, 0.98f, 0.98f, 1.0f);
|
||||
}
|
||||
if (hovered) {
|
||||
return UIColor(0.90f, 0.90f, 0.90f, 1.0f);
|
||||
}
|
||||
return UIColor(0.80f, 0.80f, 0.80f, 1.0f);
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIColor ResolveToggleButtonFill(
|
||||
bool hovered,
|
||||
bool pressed) {
|
||||
constexpr UIColor kToggleIdle(
|
||||
38.0f / 255.0f,
|
||||
38.0f / 255.0f,
|
||||
38.0f / 255.0f,
|
||||
1.0f);
|
||||
constexpr UIColor kToggleHover(
|
||||
47.0f / 255.0f,
|
||||
47.0f / 255.0f,
|
||||
47.0f / 255.0f,
|
||||
1.0f);
|
||||
constexpr UIColor kTogglePressed(
|
||||
56.0f / 255.0f,
|
||||
56.0f / 255.0f,
|
||||
56.0f / 255.0f,
|
||||
1.0f);
|
||||
|
||||
if (pressed) {
|
||||
return kTogglePressed;
|
||||
}
|
||||
if (hovered) {
|
||||
return kToggleHover;
|
||||
}
|
||||
return kToggleIdle;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool SceneViewportToolOverlay::Initialize(
|
||||
const std::filesystem::path& repoRoot,
|
||||
Rendering::Host::UiTextureHost& renderer) {
|
||||
Shutdown(renderer);
|
||||
|
||||
const std::filesystem::path iconRoot =
|
||||
(repoRoot / "editor" / "resources" / "Icons").lexically_normal();
|
||||
bool loadedAnyTexture = false;
|
||||
for (std::size_t index = 0; index < kToolButtonSpecs.size(); ++index) {
|
||||
const ToolButtonSpec& path = kToolButtonSpecs[index];
|
||||
ToolTextureSet& textureSet = m_toolTextures[index];
|
||||
textureSet = {};
|
||||
textureSet.mode = path.mode;
|
||||
textureSet.label = path.label;
|
||||
|
||||
std::string error = {};
|
||||
loadedAnyTexture =
|
||||
renderer.LoadTextureFromFile(
|
||||
iconRoot / path.inactiveFile,
|
||||
textureSet.inactiveTexture,
|
||||
error) || loadedAnyTexture;
|
||||
error.clear();
|
||||
loadedAnyTexture =
|
||||
renderer.LoadTextureFromFile(
|
||||
iconRoot / path.activeFile,
|
||||
textureSet.activeTexture,
|
||||
error) || loadedAnyTexture;
|
||||
}
|
||||
|
||||
return loadedAnyTexture;
|
||||
}
|
||||
|
||||
void SceneViewportToolOverlay::Shutdown(Rendering::Host::UiTextureHost& renderer) {
|
||||
for (ToolTextureSet& textureSet : m_toolTextures) {
|
||||
renderer.ReleaseTexture(textureSet.inactiveTexture);
|
||||
renderer.ReleaseTexture(textureSet.activeTexture);
|
||||
textureSet = {};
|
||||
}
|
||||
|
||||
ResetFrame();
|
||||
}
|
||||
|
||||
void SceneViewportToolOverlay::ResetFrame() {
|
||||
m_frame = {};
|
||||
}
|
||||
|
||||
void SceneViewportToolOverlay::BuildFrame(
|
||||
const UIRect& viewportRect,
|
||||
const UIRect& topBarRect,
|
||||
const UIRect& clipRect,
|
||||
SceneToolMode activeMode,
|
||||
SceneToolPivotMode pivotMode,
|
||||
SceneToolSpaceMode spaceMode,
|
||||
std::size_t hoveredIndex,
|
||||
std::size_t pressedIndex,
|
||||
std::size_t hoveredToggleIndex,
|
||||
std::size_t pressedToggleIndex) {
|
||||
m_frame = {};
|
||||
if (viewportRect.width <= 1.0f || viewportRect.height <= 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_frame.visible = true;
|
||||
m_frame.clipRect = clipRect;
|
||||
m_frame.toggleFrame.visible =
|
||||
topBarRect.width > 1.0f && topBarRect.height > 1.0f;
|
||||
const float pivotToggleWidth =
|
||||
ResolveToggleWidth(SceneViewportToggleButtonKind::Pivot);
|
||||
const float spaceToggleWidth =
|
||||
ResolveToggleWidth(SceneViewportToggleButtonKind::Space);
|
||||
m_frame.toggleFrame.panelRect = UIRect(
|
||||
topBarRect.x + kTopBarButtonInsetX,
|
||||
topBarRect.y,
|
||||
pivotToggleWidth +
|
||||
spaceToggleWidth +
|
||||
kToggleButtonSpacing,
|
||||
topBarRect.height);
|
||||
for (std::size_t index = 0; index < m_frame.toggleFrame.buttons.size(); ++index) {
|
||||
SceneViewportToggleButtonFrame& button =
|
||||
m_frame.toggleFrame.buttons[index];
|
||||
button = {};
|
||||
button.kind = index == 0u
|
||||
? SceneViewportToggleButtonKind::Pivot
|
||||
: SceneViewportToggleButtonKind::Space;
|
||||
button.label = std::string(
|
||||
ResolveToggleLabel(button.kind, pivotMode, spaceMode));
|
||||
button.rect = BuildToggleButtonRect(
|
||||
topBarRect,
|
||||
index,
|
||||
index == 0u ? pivotToggleWidth : spaceToggleWidth);
|
||||
button.active = true;
|
||||
button.hovered = index == hoveredToggleIndex;
|
||||
button.pressed = index == pressedToggleIndex;
|
||||
}
|
||||
|
||||
m_frame.panelRect = UIRect(
|
||||
viewportRect.x + kViewportInset,
|
||||
viewportRect.y + kViewportInset,
|
||||
kPanelPadding * 2.0f + kButtonExtent,
|
||||
kPanelPadding * 2.0f +
|
||||
static_cast<float>(m_frame.buttons.size()) * kButtonExtent +
|
||||
static_cast<float>(m_frame.buttons.size() - 1u) * kButtonSpacing);
|
||||
|
||||
for (std::size_t index = 0; index < m_frame.buttons.size(); ++index) {
|
||||
const ToolButtonSpec& spec = kToolButtonSpecs[index];
|
||||
const ToolTextureSet& textureSet = m_toolTextures[index];
|
||||
SceneViewportToolOverlayButtonFrame& button = m_frame.buttons[index];
|
||||
button = {};
|
||||
button.mode = spec.mode;
|
||||
button.label = spec.label;
|
||||
button.rect = BuildButtonRect(m_frame.panelRect, index);
|
||||
button.active = spec.mode == activeMode;
|
||||
button.hovered = index == hoveredIndex;
|
||||
button.pressed = index == pressedIndex;
|
||||
button.texture = spec.mode == activeMode
|
||||
? textureSet.activeTexture
|
||||
: textureSet.inactiveTexture;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t SceneViewportToolOverlay::HitTest(const UIPoint& point) const {
|
||||
if (!m_frame.visible || !ContainsPoint(m_frame.panelRect, point)) {
|
||||
return kSceneViewportToolOverlayInvalidIndex;
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < m_frame.buttons.size(); ++index) {
|
||||
if (ContainsPoint(m_frame.buttons[index].rect, point)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return kSceneViewportToolOverlayInvalidIndex;
|
||||
}
|
||||
|
||||
std::size_t SceneViewportToolOverlay::HitTestToggle(const UIPoint& point) const {
|
||||
if (!m_frame.visible ||
|
||||
!m_frame.toggleFrame.visible ||
|
||||
!ContainsPoint(m_frame.toggleFrame.panelRect, point)) {
|
||||
return kSceneViewportToolOverlayInvalidIndex;
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < m_frame.toggleFrame.buttons.size(); ++index) {
|
||||
if (ContainsPoint(m_frame.toggleFrame.buttons[index].rect, point)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return kSceneViewportToolOverlayInvalidIndex;
|
||||
}
|
||||
|
||||
bool SceneViewportToolOverlay::Contains(const UIPoint& point) const {
|
||||
return m_frame.visible &&
|
||||
(ContainsPoint(m_frame.panelRect, point) ||
|
||||
(m_frame.toggleFrame.visible &&
|
||||
ContainsPoint(m_frame.toggleFrame.panelRect, point)));
|
||||
}
|
||||
|
||||
const SceneViewportToolOverlayFrame& SceneViewportToolOverlay::GetFrame() const {
|
||||
return m_frame;
|
||||
}
|
||||
|
||||
void AppendSceneViewportToolOverlay(
|
||||
UIDrawList& drawList,
|
||||
const SceneViewportToolOverlayFrame& frame) {
|
||||
if (!frame.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr UIColor kPanelFill(24.0f / 255.0f, 26.0f / 255.0f, 29.0f / 255.0f, 220.0f / 255.0f);
|
||||
constexpr UIColor kPanelOutline(1.0f, 1.0f, 1.0f, 28.0f / 255.0f);
|
||||
constexpr UIColor kButtonOutline(1.0f, 1.0f, 1.0f, 24.0f / 255.0f);
|
||||
constexpr UIColor kButtonActiveOutline(1.0f, 1.0f, 1.0f, 64.0f / 255.0f);
|
||||
constexpr UIColor kToggleOutline(1.0f, 1.0f, 1.0f, 24.0f / 255.0f);
|
||||
constexpr UIColor kToggleHoverOutline(1.0f, 1.0f, 1.0f, 40.0f / 255.0f);
|
||||
constexpr UIColor kFallbackText(230.0f / 255.0f, 230.0f / 255.0f, 230.0f / 255.0f, 1.0f);
|
||||
|
||||
drawList.PushClipRect(frame.clipRect);
|
||||
if (frame.toggleFrame.visible) {
|
||||
for (const SceneViewportToggleButtonFrame& button : frame.toggleFrame.buttons) {
|
||||
drawList.AddFilledRect(
|
||||
button.rect,
|
||||
ResolveToggleButtonFill(button.hovered, button.pressed),
|
||||
kToggleButtonCornerRounding);
|
||||
drawList.AddRectOutline(
|
||||
button.rect,
|
||||
button.hovered || button.pressed
|
||||
? kToggleHoverOutline
|
||||
: kToggleOutline,
|
||||
1.0f,
|
||||
kToggleButtonCornerRounding);
|
||||
const float textWidth =
|
||||
MeasureUIEditorTextWidth(button.label, kToggleFontSize);
|
||||
const float textLeft = button.rect.x +
|
||||
(std::max)(0.0f, std::floor((button.rect.width - textWidth) * 0.5f));
|
||||
drawList.AddText(
|
||||
UIPoint(textLeft, ResolveUIEditorTextTop(button.rect, kToggleFontSize)),
|
||||
button.label,
|
||||
kFallbackText,
|
||||
kToggleFontSize);
|
||||
}
|
||||
}
|
||||
|
||||
drawList.AddFilledRect(frame.panelRect, kPanelFill, kPanelCornerRounding);
|
||||
drawList.AddRectOutline(frame.panelRect, kPanelOutline, 1.0f, kPanelCornerRounding);
|
||||
|
||||
for (const SceneViewportToolOverlayButtonFrame& button : frame.buttons) {
|
||||
drawList.AddFilledRect(
|
||||
button.rect,
|
||||
ResolveToolButtonFill(button.active, button.hovered, button.pressed),
|
||||
kButtonCornerRounding);
|
||||
drawList.AddRectOutline(
|
||||
button.rect,
|
||||
button.active ? kButtonActiveOutline : kButtonOutline,
|
||||
1.0f,
|
||||
kButtonCornerRounding);
|
||||
|
||||
if (button.texture.IsValid()) {
|
||||
const UIRect iconRect(
|
||||
button.rect.x + kIconInset,
|
||||
button.rect.y + kIconInset,
|
||||
button.rect.width - kIconInset * 2.0f,
|
||||
button.rect.height - kIconInset * 2.0f);
|
||||
drawList.AddImage(
|
||||
iconRect,
|
||||
button.texture,
|
||||
ResolveToolIconTint(button.active, button.hovered, button.pressed));
|
||||
continue;
|
||||
}
|
||||
|
||||
drawList.AddText(
|
||||
UIPoint(button.rect.x + 7.0f, button.rect.y + 9.0f),
|
||||
button.label,
|
||||
kFallbackText,
|
||||
kFallbackFontSize);
|
||||
}
|
||||
|
||||
drawList.PopClipRect();
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
Reference in New Issue
Block a user