#include #include #include #include #include #include #include namespace { constexpr int MoveHandle = 0; constexpr float HandleRadius = 5.0f; constexpr std::array 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(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() != nullptr && object.GetComponent() == 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(); 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(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(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(i) + 1 ? ActiveColor : outlineColor; drawList.AddRectFilled(ToImVec(handle - glm::vec2{HandleRadius}), ToImVec(handle + glm::vec2{HandleRadius}), color); } return changed; }