167 lines
7.1 KiB
C++
167 lines
7.1 KiB
C++
#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, const glm::vec2& viewMin, const glm::vec2& viewSize)
|
|
{
|
|
m_Hovered = false;
|
|
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 glm::vec2 viewScale = viewSize / drawImageSize;
|
|
const auto toView = [&](const glm::vec2& point) { return viewMin + point * viewScale; };
|
|
|
|
const UIRect& rect = rectTransform->GetScreenRect();
|
|
const glm::vec2 rectMin = toView(rect.min);
|
|
const glm::vec2 rectMax = toView(rect.Max());
|
|
const glm::vec2 viewCenter = (rectMin + rectMax) * 0.5f;
|
|
const glm::vec2 viewHalfSize = (rectMax - rectMin) * 0.5f;
|
|
|
|
const glm::vec2 mouse = ToVec(ImGui::GetIO().MousePos);
|
|
int hovered = -1;
|
|
// Called inside the window that shows the scene, anything on top of it blocks the handles
|
|
const bool overImGui = !ImGui::IsWindowHovered() || 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 >= rectMin.x && mouse.y >= rectMin.y && mouse.x <= rectMax.x && mouse.y <= rectMax.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;
|
|
m_Hovered = highlighted >= 0;
|
|
if (highlighted >= 0) {
|
|
ImGui::SetMouseCursor(CursorFor(highlighted));
|
|
}
|
|
|
|
ImDrawList& drawList = *ImGui::GetWindowDrawList();
|
|
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(rectMin), ToImVec(rectMax), highlighted == MoveHandle ? ActiveColor : outlineColor, 0.0f, 0, 1.5f);
|
|
|
|
const glm::vec2 pivot = rectMin + (rectMax - rectMin) * 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;
|
|
}
|