feat: add basic canvas / improve inspector

This commit is contained in:
2026-10-18 20:42:04 +02:00
parent 7df65f52b5
commit ad16daea7c
47 changed files with 3142 additions and 405 deletions
+167
View File
@@ -0,0 +1,167 @@
#include <destrum/Util/RectGizmo.h>
#include <array>
#include <cmath>
#include <destrum/Components/UI/Canvas.h>
#include <destrum/Components/UI/RectTransform.h>
#include <destrum/ObjectModel/GameObject.h>
#include <imgui.h>
namespace {
constexpr int MoveHandle = 0;
constexpr float HandleRadius = 5.0f;
constexpr std::array<glm::vec2, 8> HandleDirections{{
{-1.0f, -1.0f}, {0.0f, -1.0f}, {1.0f, -1.0f}, {1.0f, 0.0f},
{1.0f, 1.0f}, {0.0f, 1.0f}, {-1.0f, 1.0f}, {-1.0f, 0.0f},
}};
constexpr ImU32 OutlineColor = IM_COL32(60, 200, 255, 255);
constexpr ImU32 ActiveColor = IM_COL32(255, 200, 60, 255);
constexpr ImU32 HiddenColor = IM_COL32(150, 150, 150, 200);
constexpr ImU32 AnchorColor = IM_COL32(255, 255, 255, 200);
[[nodiscard]] ImVec2 ToImVec(const glm::vec2& value) { return ImVec2{value.x, value.y}; }
[[nodiscard]] glm::vec2 ToVec(const ImVec2& value) { return glm::vec2{value.x, value.y}; }
[[nodiscard]] ImGuiMouseCursor CursorFor(int handle)
{
if (handle == MoveHandle) {
return ImGuiMouseCursor_ResizeAll;
}
const glm::vec2 direction = HandleDirections[static_cast<std::size_t>(handle - 1)];
if (direction.x == 0.0f) {
return ImGuiMouseCursor_ResizeNS;
}
if (direction.y == 0.0f) {
return ImGuiMouseCursor_ResizeEW;
}
return direction.x == direction.y ? ImGuiMouseCursor_ResizeNWSE : ImGuiMouseCursor_ResizeNESW;
}
void DrawAnchor(ImDrawList& drawList, const glm::vec2& point, const glm::vec2& outwards)
{
constexpr float size = 8.0f;
const glm::vec2 tip = point;
const glm::vec2 a = point + glm::vec2{outwards.x * size, outwards.y * size * 0.4f};
const glm::vec2 b = point + glm::vec2{outwards.x * size * 0.4f, outwards.y * size};
drawList.AddTriangleFilled(ToImVec(tip), ToImVec(a), ToImVec(b), AnchorColor);
}
}
bool RectGizmo::CanManipulate(GameObject& object)
{
return object.GetComponent<RectTransform>() != nullptr && object.GetComponent<Canvas>() == nullptr &&
Canvas::FindFor(object) != nullptr;
}
bool RectGizmo::Manipulate(GameObject& object, const glm::vec2& drawImageSize)
{
auto* rectTransform = object.GetComponent<RectTransform>();
const Canvas* canvas = Canvas::FindFor(object);
if (!CanManipulate(object) || drawImageSize.x <= 0.0f || drawImageSize.y <= 0.0f) {
m_ActiveHandle = -1;
return false;
}
const ImGuiViewport* viewport = ImGui::GetMainViewport();
const glm::vec2 viewOrigin = ToVec(viewport->Pos);
const glm::vec2 viewScale = ToVec(viewport->Size) / drawImageSize;
const auto toView = [&](const glm::vec2& point) { return viewOrigin + point * viewScale; };
const UIRect& rect = rectTransform->GetScreenRect();
const glm::vec2 viewMin = toView(rect.min);
const glm::vec2 viewMax = toView(rect.Max());
const glm::vec2 viewCenter = (viewMin + viewMax) * 0.5f;
const glm::vec2 viewHalfSize = (viewMax - viewMin) * 0.5f;
const glm::vec2 mouse = ToVec(ImGui::GetIO().MousePos);
int hovered = -1;
const bool overImGui = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) || ImGui::IsAnyItemActive();
if (m_ActiveHandle < 0 && !overImGui) {
for (std::size_t i = 0; i < HandleDirections.size(); ++i) {
const glm::vec2 handle = viewCenter + HandleDirections[i] * viewHalfSize;
if (glm::length(mouse - handle) <= HandleRadius + 2.0f) {
hovered = static_cast<int>(i) + 1;
break;
}
}
if (hovered < 0 && mouse.x >= viewMin.x && mouse.y >= viewMin.y && mouse.x <= viewMax.x && mouse.y <= viewMax.y) {
hovered = MoveHandle;
}
if (hovered >= 0 && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
m_ActiveHandle = hovered;
m_DragStartMouse = mouse;
m_DragStartPosition = rectTransform->GetPosition();
m_DragStartSize = rectTransform->GetSize();
}
}
bool changed = false;
if (m_ActiveHandle >= 0) {
if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
m_ActiveHandle = -1;
} else {
const float canvasScale = canvas->GetScale(drawImageSize);
glm::vec2 delta = (mouse - m_DragStartMouse) / (viewScale * canvasScale);
if (snap && snapStep > 0.0f) {
delta = glm::round(delta / snapStep) * snapStep;
}
glm::vec2 position = m_DragStartPosition;
glm::vec2 size = m_DragStartSize;
if (m_ActiveHandle == MoveHandle) {
position += delta;
} else {
// The edge opposite the handle stays put, so the pivot moves by the pivot's share of the resize
const glm::vec2 direction = HandleDirections[static_cast<std::size_t>(m_ActiveHandle - 1)];
const glm::vec2& pivot = rectTransform->GetPivot();
for (int axis = 0; axis < 2; ++axis) {
if (direction[axis] == 0.0f) {
continue;
}
size[axis] += direction[axis] * delta[axis];
position[axis] += delta[axis] * (direction[axis] > 0.0f ? pivot[axis] : 1.0f - pivot[axis]);
}
}
if (position != rectTransform->GetPosition() || size != rectTransform->GetSize()) {
rectTransform->SetPosition(position);
rectTransform->SetSize(size);
changed = true;
}
}
}
const int highlighted = m_ActiveHandle >= 0 ? m_ActiveHandle : hovered;
if (highlighted >= 0) {
ImGui::SetMouseCursor(CursorFor(highlighted));
// Keeps the click from reaching the game, same as the 3D gizmo
ImGui::SetNextFrameWantCaptureMouse(true);
}
ImDrawList& drawList = *ImGui::GetBackgroundDrawList();
const ImU32 outlineColor = object.IsActiveInHierarchy() ? OutlineColor : HiddenColor;
const UIRect& parentRect = rectTransform->GetParentRect();
const glm::vec2 anchorMin = toView(parentRect.min + parentRect.size * rectTransform->GetAnchorMin());
const glm::vec2 anchorMax = toView(parentRect.min + parentRect.size * rectTransform->GetAnchorMax());
DrawAnchor(drawList, {anchorMin.x, anchorMin.y}, {-1.0f, -1.0f});
DrawAnchor(drawList, {anchorMax.x, anchorMin.y}, {1.0f, -1.0f});
DrawAnchor(drawList, {anchorMax.x, anchorMax.y}, {1.0f, 1.0f});
DrawAnchor(drawList, {anchorMin.x, anchorMax.y}, {-1.0f, 1.0f});
drawList.AddRect(ToImVec(viewMin), ToImVec(viewMax), highlighted == MoveHandle ? ActiveColor : outlineColor, 0.0f, 0, 1.5f);
const glm::vec2 pivot = viewMin + (viewMax - viewMin) * rectTransform->GetPivot();
drawList.AddCircle(ToImVec(pivot), 4.0f, outlineColor, 12, 1.5f);
for (std::size_t i = 0; i < HandleDirections.size(); ++i) {
const glm::vec2 handle = viewCenter + HandleDirections[i] * viewHalfSize;
const ImU32 color = highlighted == static_cast<int>(i) + 1 ? ActiveColor : outlineColor;
drawList.AddRectFilled(ToImVec(handle - glm::vec2{HandleRadius}), ToImVec(handle + glm::vec2{HandleRadius}), color);
}
return changed;
}