feat: add canvas rework

This commit is contained in:
2026-11-01 23:16:58 +01:00
parent 6a85d17899
commit d26f61a16f
15 changed files with 393 additions and 12 deletions
+5
View File
@@ -1,6 +1,10 @@
#ifndef DESTRUM_APPLAYER_H
#define DESTRUM_APPLAYER_H
#include <optional>
#include <destrum/Components/UI/CanvasFrame.h>
class Camera;
class GameRenderer;
@@ -25,6 +29,7 @@ public:
[[nodiscard]] virtual Camera* viewCamera() { return nullptr; }
[[nodiscard]] virtual bool hostsViewport() const { return false; }
[[nodiscard]] virtual InputFocus gameInputFocus() const { return {}; }
[[nodiscard]] virtual std::optional<CanvasFrame> canvasFrame() const { return std::nullopt; }
};
#endif // DESTRUM_APPLAYER_H
@@ -1,6 +1,7 @@
#ifndef DESTRUM_CANVAS_H
#define DESTRUM_CANVAS_H
#include <destrum/Components/UI/CanvasFrame.h>
#include <destrum/Components/UI/RectTransform.h>
#include <destrum/ObjectModel/Component.h>
@@ -22,6 +23,7 @@ public:
void Start() override;
void Render(const RenderContext& ctx) override;
void Draw(const glm::vec2& screenSize);
void Draw(const CanvasFrame& frame);
void Reflect(FieldVisitor& v) override;
@@ -0,0 +1,13 @@
#ifndef DESTRUM_CANVASFRAME_H
#define DESTRUM_CANVASFRAME_H
#include <glm/glm.hpp>
// Where canvases lay out, in draw image pixels: a resolution sized screen at min, drawn zoom times larger
struct CanvasFrame {
glm::vec2 min{0.0f};
glm::vec2 resolution{0.0f};
float zoom{1.0f};
};
#endif // DESTRUM_CANVASFRAME_H
@@ -43,6 +43,8 @@ public:
// Where the canvas last drew this object
[[nodiscard]] const UIRect& GetScreenRect() const { return m_ScreenRect; }
[[nodiscard]] const UIRect& GetParentRect() const { return m_ParentRect; }
// Screen pixels per canvas unit at the last draw
[[nodiscard]] float GetScreenScale() const { return m_LastScale; }
void SetScreenRect(const UIRect& rect, const UIRect& parentRect, float scale)
{
m_ScreenRect = rect;
@@ -1,12 +1,17 @@
#ifndef RENDERCONTEXT_H
#define RENDERCONTEXT_H
#include <optional>
#include <destrum/Components/UI/CanvasFrame.h>
#include <destrum/Graphics/Renderer.h>
struct RenderContext {
GameRenderer& renderer;
const Camera& camera;
const GameRenderer::SceneData& sceneData;
// Unset: canvases fill the draw image
std::optional<CanvasFrame> canvasFrame{};
};
#endif //RENDERCONTEXT_H
+1
View File
@@ -378,6 +378,7 @@ void App::renderFrame()
.renderer = renderer,
.camera = camera,
.sceneData = sceneData,
.canvasFrame = layer ? layer->canvasFrame() : std::nullopt,
});
renderer.endDrawing();
renderer.draw(cmd, gfxDevice, camera, sceneData);
+13 -3
View File
@@ -54,19 +54,29 @@ Canvas* Canvas::FindFor(GameObject& object)
void Canvas::Render(const RenderContext& ctx)
{
if (ctx.canvasFrame) {
Draw(*ctx.canvasFrame);
return;
}
const VkExtent3D extent = ctx.renderer.getDrawImage().extent;
Draw(glm::vec2{static_cast<float>(extent.width), static_cast<float>(extent.height)});
}
void Canvas::Draw(const glm::vec2& screenSize)
{
Draw(CanvasFrame{.resolution = screenSize});
}
void Canvas::Draw(const CanvasFrame& frame)
{
ZoneScopedN("Canvas::Draw");
const UIRect screenRect{.min = glm::vec2{0.0f}, .size = screenSize};
const UIRect screenRect{.min = frame.min, .size = frame.resolution * frame.zoom};
const float scale = GetScale(frame.resolution) * frame.zoom;
if (auto* rectTransform = GetGameObject()->GetComponent<RectTransform>()) {
rectTransform->SetScreenRect(screenRect, screenRect, GetScale(screenSize));
rectTransform->SetScreenRect(screenRect, screenRect, scale);
}
DrawObject(*GetGameObject(), screenRect, GetScale(screenSize), true);
DrawObject(*GetGameObject(), screenRect, scale, true);
}
float Canvas::GetScale(const glm::vec2& screenSize) const
+1
View File
@@ -11,6 +11,7 @@ set(EDITOR_SRC
src/AssetBrowser.cpp
src/TransformGizmo.cpp
src/RectGizmo.cpp
src/CanvasView.cpp
src/ResourcesInspector.cpp
)
+45
View File
@@ -0,0 +1,45 @@
#ifndef EDITOR_CANVASVIEW_H
#define EDITOR_CANVASVIEW_H
#include <glm/glm.hpp>
#include <destrum/Components/UI/CanvasFrame.h>
class GameObject;
class RectGizmo;
class Scene;
struct ImDrawList;
class CanvasView final {
public:
[[nodiscard]] bool IsEnabled() const { return m_Enabled; }
void SetEnabled(bool enabled);
void Toggle() { SetEnabled(!m_Enabled); }
[[nodiscard]] bool ShowsScene() const { return m_ShowScene; }
[[nodiscard]] bool IsPanning() const { return m_Panning; }
[[nodiscard]] CanvasFrame GetFrame() const;
void SetDrawImageSize(const glm::vec2& size);
void Fit();
bool HandleInput(const glm::vec2& mousePixel, const glm::vec2& pixelsPerPoint, bool hovered);
void SubmitBackdrop() const;
void DrawOverlay(ImDrawList& drawList, const glm::vec2& viewMin, const glm::vec2& viewScale) const;
void DrawToolbar(RectGizmo& gizmo);
[[nodiscard]] static GameObject* Pick(Scene& scene, const glm::vec2& pixel);
private:
bool m_Enabled{false};
bool m_ShowScene{false};
bool m_Fitted{true};
bool m_Panning{false};
int m_Preset{0};
glm::ivec2 m_Resolution{1920, 1080};
glm::vec2 m_DrawImageSize{1.0f};
glm::vec2 m_FrameMin{0.0f};
float m_Zoom{1.0f};
};
#endif // EDITOR_CANVASVIEW_H
+5 -1
View File
@@ -11,6 +11,7 @@
#include <destrum/AppLayer.h>
#include <editor/AssetBrowser.h>
#include <editor/CanvasView.h>
#include <editor/EditorCamera.h>
#include <editor/PlayMode.h>
#include <editor/RectGizmo.h>
@@ -44,6 +45,7 @@ public:
[[nodiscard]] Camera* viewCamera() override;
[[nodiscard]] bool hostsViewport() const override { return true; }
[[nodiscard]] InputFocus gameInputFocus() const override;
[[nodiscard]] std::optional<CanvasFrame> canvasFrame() const override;
bool OpenScene(std::string_view path);
bool SaveScene();
@@ -81,7 +83,8 @@ private:
// Editor.cpp
[[nodiscard]] Scene* CurrentScene() const;
[[nodiscard]] bool UsesEditorCamera();
[[nodiscard]] bool UsesEditorCamera() const;
[[nodiscard]] bool CanvasViewActive() const;
void HandleShortcuts();
void Undo();
void Redo();
@@ -126,6 +129,7 @@ private:
ResourcesInspector m_ResourcesInspector;
TransformGizmo m_TransformGizmo;
RectGizmo m_RectGizmo;
CanvasView m_CanvasView;
ShaderHotReload m_ShaderHotReload;
Panels m_Panels;
+238
View File
@@ -0,0 +1,238 @@
#include <editor/CanvasView.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdio>
#include <imgui.h>
#include <destrum/Components/UI/Canvas.h>
#include <destrum/Components/UI/RectTransform.h>
#include <destrum/Components/UI/UIGraphic.h>
#include <destrum/Graphics/Managers/Renderer2D.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Scene/Scene.h>
#include <editor/RectGizmo.h>
namespace {
struct ResolutionPreset {
const char* label;
glm::ivec2 size;
};
constexpr std::array<ResolutionPreset, 6> Presets{{
{"1920 x 1080 (16:9)", {1920, 1080}},
{"1280 x 720 (16:9)", {1280, 720}},
{"2560 x 1440 (16:9)", {2560, 1440}},
{"2560 x 1080 (21:9)", {2560, 1080}},
{"1024 x 768 (4:3)", {1024, 768}},
{"1080 x 1920 (portrait)", {1080, 1920}},
}};
constexpr int CustomPreset = static_cast<int>(Presets.size());
constexpr float MinZoom = 0.05f;
constexpr float MaxZoom = 16.0f;
constexpr float FitMargin = 24.0f;
constexpr glm::vec4 BackdropColor{0.11f, 0.11f, 0.12f, 1.0f};
constexpr glm::vec4 FrameColor{0.21f, 0.21f, 0.23f, 1.0f};
constexpr ImU32 FrameOutlineColor = IM_COL32(255, 255, 255, 90);
constexpr ImU32 LabelColor = IM_COL32(255, 255, 255, 150);
[[nodiscard]] bool HasGraphic(const GameObject& object)
{
for (const auto& component : object.GetComponents()) {
const auto* graphic = dynamic_cast<const UIGraphic*>(component.get());
if (graphic != nullptr && graphic->isEnabled()) {
return true;
}
}
return false;
}
struct Hit {
GameObject* object{nullptr};
bool hasGraphic{false};
};
// Walks in the canvas's draw order, so the last hit is the one on top. Empty layout objects only count when no
// graphic is under the mouse, otherwise a stretched container would swallow every click
void CollectHits(GameObject& object, const glm::vec2& pixel, bool isCanvasRoot, Hit& hit)
{
if (object.IsBeingDestroyed() || !object.IsActive()) {
return;
}
if (!isCanvasRoot) {
if (object.HasComponent<Canvas>()) {
return;
}
if (const auto* rectTransform = object.GetComponent<RectTransform>();
rectTransform != nullptr && rectTransform->GetScreenRect().Contains(pixel)) {
const bool graphic = HasGraphic(object);
if (graphic || !hit.hasGraphic) {
hit = {.object = &object, .hasGraphic = graphic};
}
}
}
for (const Transform* child : object.GetTransform().GetChildren()) {
if (child != nullptr && child->GetOwner() != nullptr) {
CollectHits(*child->GetOwner(), pixel, false, hit);
}
}
}
}
void CanvasView::SetEnabled(bool enabled)
{
if (enabled && !m_Enabled) {
Fit();
}
m_Enabled = enabled;
m_Panning = m_Panning && enabled;
}
CanvasFrame CanvasView::GetFrame() const
{
return CanvasFrame{.min = m_FrameMin, .resolution = glm::vec2{m_Resolution}, .zoom = m_Zoom};
}
void CanvasView::SetDrawImageSize(const glm::vec2& size)
{
if (size == m_DrawImageSize) {
return;
}
m_DrawImageSize = size;
if (m_Fitted) {
Fit();
}
}
void CanvasView::Fit()
{
const glm::vec2 resolution{m_Resolution};
const glm::vec2 available = glm::max(m_DrawImageSize - glm::vec2{FitMargin * 2.0f}, glm::vec2{1.0f});
m_Zoom = std::clamp(std::min(available.x / resolution.x, available.y / resolution.y), MinZoom, MaxZoom);
m_FrameMin = glm::round((m_DrawImageSize - resolution * m_Zoom) * 0.5f);
m_Fitted = true;
}
bool CanvasView::HandleInput(const glm::vec2& mousePixel, const glm::vec2& pixelsPerPoint, bool hovered)
{
const ImGuiIO& io = ImGui::GetIO();
if (hovered && io.MouseWheel != 0.0f) {
const glm::vec2 canvasPoint = (mousePixel - m_FrameMin) / m_Zoom;
m_Zoom = std::clamp(m_Zoom * std::pow(1.15f, io.MouseWheel), MinZoom, MaxZoom);
m_FrameMin = mousePixel - canvasPoint * m_Zoom;
m_Fitted = false;
}
if (hovered && (ImGui::IsMouseClicked(ImGuiMouseButton_Right) || ImGui::IsMouseClicked(ImGuiMouseButton_Middle))) {
m_Panning = true;
}
if (m_Panning && !ImGui::IsMouseDown(ImGuiMouseButton_Right) && !ImGui::IsMouseDown(ImGuiMouseButton_Middle)) {
m_Panning = false;
}
if (m_Panning && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) {
m_FrameMin += glm::vec2{io.MouseDelta.x, io.MouseDelta.y} * pixelsPerPoint;
m_Fitted = false;
}
return m_Panning;
}
void CanvasView::SubmitBackdrop() const
{
if (m_ShowScene) {
return;
}
auto& renderer2D = Renderer2D::GetInstance();
renderer2D.DrawRect(glm::vec2{0.0f}, m_DrawImageSize, BackdropColor);
renderer2D.DrawRect(m_FrameMin, glm::vec2{m_Resolution} * m_Zoom, FrameColor);
}
void CanvasView::DrawOverlay(ImDrawList& drawList, const glm::vec2& viewMin, const glm::vec2& viewScale) const
{
const glm::vec2 min = viewMin + m_FrameMin * viewScale;
const glm::vec2 max = viewMin + (m_FrameMin + glm::vec2{m_Resolution} * m_Zoom) * viewScale;
drawList.AddRect(ImVec2{min.x - 1.0f, min.y - 1.0f}, ImVec2{max.x + 1.0f, max.y + 1.0f}, FrameOutlineColor);
char label[64];
std::snprintf(label, sizeof(label), "%d x %d %.0f%%", m_Resolution.x, m_Resolution.y, m_Zoom * 100.0f);
drawList.AddText(ImVec2{min.x, min.y - ImGui::GetTextLineHeight() - 2.0f}, LabelColor, label);
}
void CanvasView::DrawToolbar(RectGizmo& gizmo)
{
if (!ImGui::Begin("Canvas", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::End();
return;
}
bool enabled = m_Enabled;
if (ImGui::Checkbox("Canvas mode (2)", &enabled)) {
SetEnabled(enabled);
}
if (m_Enabled) {
ImGui::SetNextItemWidth(180.0f);
const char* preview = m_Preset < CustomPreset ? Presets[static_cast<std::size_t>(m_Preset)].label : "Custom";
if (ImGui::BeginCombo("Resolution", preview)) {
for (int i = 0; i < CustomPreset; ++i) {
if (ImGui::Selectable(Presets[static_cast<std::size_t>(i)].label, m_Preset == i)) {
m_Preset = i;
m_Resolution = Presets[static_cast<std::size_t>(i)].size;
Fit();
}
}
if (ImGui::Selectable("Custom", m_Preset == CustomPreset)) {
m_Preset = CustomPreset;
}
ImGui::EndCombo();
}
if (m_Preset == CustomPreset) {
ImGui::SetNextItemWidth(180.0f);
glm::ivec2 resolution = m_Resolution;
if (ImGui::InputInt2("Size", &resolution.x, ImGuiInputTextFlags_EnterReturnsTrue)) {
m_Resolution = glm::clamp(resolution, glm::ivec2{16}, glm::ivec2{16384});
Fit();
}
}
if (ImGui::Button("Fit (F)")) {
Fit();
}
ImGui::SameLine();
if (ImGui::Button("100%")) {
const glm::vec2 center = m_FrameMin + glm::vec2{m_Resolution} * m_Zoom * 0.5f;
m_Zoom = 1.0f;
m_FrameMin = glm::round(center - glm::vec2{m_Resolution} * 0.5f);
m_Fitted = false;
}
ImGui::SameLine();
ImGui::Text("%.0f%%", m_Zoom * 100.0f);
ImGui::Checkbox("Show scene", &m_ShowScene);
ImGui::TextDisabled("Wheel zoom, right/middle drag pan");
}
ImGui::Separator();
ImGui::Checkbox("Snap", &gizmo.snap);
if (gizmo.snap) {
ImGui::SameLine();
ImGui::SetNextItemWidth(80.0f);
ImGui::DragFloat("Step", &gizmo.snapStep, 0.5f, 1.0f, 100.0f, "%.0f px");
}
ImGui::End();
}
GameObject* CanvasView::Pick(Scene& scene, const glm::vec2& pixel)
{
Hit hit;
for (const auto& object : scene.GetObjects()) {
if (object != nullptr && !object->IsBeingDestroyed() && object->HasComponent<Canvas>() && object->IsActiveInHierarchy()) {
CollectHits(*object, pixel, true, hit);
}
}
return hit.object;
}
+31 -6
View File
@@ -73,11 +73,22 @@ Scene* Editor::CurrentScene() const
return sceneManager.GetActiveScenes().empty() ? nullptr : &sceneManager.GetCurrentScene();
}
bool Editor::UsesEditorCamera()
bool Editor::UsesEditorCamera() const
{
return m_PlayMode.IsEditing() || m_PlayMode.IsPaused() || m_App.gameCamera() == nullptr;
}
// While playing the UI fills the screen like it would in the game
bool Editor::CanvasViewActive() const
{
return m_CanvasView.IsEnabled() && UsesEditorCamera();
}
std::optional<CanvasFrame> Editor::canvasFrame() const
{
return CanvasViewActive() ? std::optional{m_CanvasView.GetFrame()} : std::nullopt;
}
Camera* Editor::viewCamera()
{
return UsesEditorCamera() ? &m_Camera.GetCamera() : nullptr;
@@ -102,7 +113,7 @@ void Editor::onImGuiBegin(float dt)
ApplyPick();
HandleShortcuts();
m_CameraHadInput = UsesEditorCamera() && (m_Viewport.hovered || m_Viewport.focused || m_Camera.IsLooking());
m_CameraHadInput = UsesEditorCamera() && !CanvasViewActive() && (m_Viewport.hovered || m_Viewport.focused || m_Camera.IsLooking());
if (UsesEditorCamera()) {
m_Camera.Update(dt, m_CameraHadInput);
}
@@ -143,9 +154,12 @@ void Editor::onImGui(float)
m_ResourcesInspector.drawImageCacheWindow(&m_Panels.imageCache, m_App.getResources(), m_App.getImguiPass());
}
if (scene != nullptr && m_View.gizmos && UsesEditorCamera()) {
if (scene != nullptr && UsesEditorCamera()) {
GameObject* selected = scene->GetSelectedObject();
if (selected != nullptr && !RectGizmo::CanManipulate(*selected)) {
const bool rectSelected = selected != nullptr && RectGizmo::CanManipulate(*selected);
if (CanvasViewActive() || (m_View.gizmos && rectSelected)) {
m_CanvasView.DrawToolbar(m_RectGizmo);
} else if (m_View.gizmos && selected != nullptr) {
m_TransformGizmo.DrawToolbar();
}
}
@@ -176,7 +190,11 @@ void Editor::onRender(GameRenderer& renderer)
{
const bool picking = m_PlayMode.IsEditing() || m_PlayMode.IsPaused();
renderer.setSelectionPassEnabled(picking);
renderer.setHoverPixel(picking ? m_Viewport.hoverPixel : std::nullopt);
const bool sceneHidden = CanvasViewActive() && !m_CanvasView.ShowsScene();
renderer.setHoverPixel(picking && !sceneHidden ? m_Viewport.hoverPixel : std::nullopt);
if (CanvasViewActive()) {
m_CanvasView.SubmitBackdrop();
}
std::vector<std::uint32_t> highlighted;
Scene* scene = CurrentScene();
@@ -231,7 +249,10 @@ void Editor::HandleShortcuts()
if (!m_Viewport.focused && !m_Viewport.hovered) {
return;
}
if (UsesEditorCamera() && !m_Camera.IsLooking()) {
if (UsesEditorCamera() && !m_Camera.IsLooking() && ImGui::IsKeyPressed(ImGuiKey_2, false) && !io.KeyCtrl) {
m_CanvasView.Toggle();
}
if (UsesEditorCamera() && !m_Camera.IsLooking() && !CanvasViewActive()) {
m_TransformGizmo.HandleShortcuts();
}
if (ImGui::IsKeyPressed(ImGuiKey_F, false) && !io.KeyCtrl) {
@@ -317,6 +338,10 @@ void Editor::DuplicateSelection()
void Editor::FocusSelection()
{
if (CanvasViewActive()) {
m_CanvasView.Fit();
return;
}
Scene* scene = CurrentScene();
if (GameObject* selected = scene != nullptr ? scene->GetSelectedObject() : nullptr) {
m_Camera.Focus(ComputeWorldBounds(*selected, m_App.getResources()));
+3
View File
@@ -198,6 +198,9 @@ void Editor::DrawViewMenu()
ImGui::SeparatorText("Scene View");
GameRenderer& renderer = m_App.getRenderer();
if (bool canvas = m_CanvasView.IsEnabled(); ImGui::MenuItem("Canvas Mode", "2", &canvas)) {
m_CanvasView.SetEnabled(canvas);
}
ImGui::MenuItem("Gizmos", nullptr, &m_View.gizmos);
ImGui::MenuItem("Colliders", nullptr, &m_View.colliders);
if (bool wireframe = renderer.getRenderWireframe(); ImGui::MenuItem("Wireframe", nullptr, &wireframe)) {
+27
View File
@@ -10,6 +10,7 @@
#include <destrum/App.h>
#include <destrum/Assets/AssetTypes.h>
#include <destrum/Components/AudioSource.h>
#include <destrum/Components/UI/RectTransform.h>
#include <destrum/Components/MeshRendererComponent.h>
#include <destrum/Components/Physics/Collider.h>
#include <destrum/Components/Physics/Rigidbody.h>
@@ -92,6 +93,15 @@ void Editor::DrawViewport()
m_Viewport.hoverPixel = glm::ivec2{uv * glm::vec2{static_cast<float>(extent.width), static_cast<float>(extent.height)}};
}
const glm::vec2 drawImageSize{static_cast<float>(drawImage.extent.width), static_cast<float>(drawImage.extent.height)};
m_CanvasView.SetDrawImageSize(drawImageSize);
if (CanvasViewActive()) {
const glm::vec2 pixelsPerPoint = drawImageSize / m_Viewport.size;
const glm::vec2 mousePixel = (glm::vec2{mouse.x, mouse.y} - m_Viewport.min) * pixelsPerPoint;
m_Viewport.mouseClaimed = m_CanvasView.HandleInput(mousePixel, pixelsPerPoint, m_Viewport.hovered);
m_CanvasView.DrawOverlay(*ImGui::GetWindowDrawList(), m_Viewport.min, m_Viewport.size / drawImageSize);
}
ImGui::SetCursorScreenPos(origin);
if (Scene* scene = CurrentScene(); scene != nullptr && m_View.gizmos && UsesEditorCamera()) {
DrawGizmos(*scene);
@@ -115,6 +125,9 @@ void Editor::DrawGizmos(Scene& scene)
m_Viewport.mouseClaimed = m_Viewport.mouseClaimed || m_RectGizmo.IsInteracting();
return;
}
if (CanvasViewActive()) {
return;
}
m_TransformGizmo.Manipulate(*selected, m_Camera.GetCamera(), m_Viewport.min, m_Viewport.size);
m_Viewport.mouseClaimed = m_Viewport.mouseClaimed || m_TransformGizmo.IsOver() || m_TransformGizmo.IsUsing();
@@ -126,6 +139,20 @@ void Editor::RequestPick()
if (!picking || !m_Viewport.hoverPixel || m_Viewport.mouseClaimed || !ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
return;
}
// GPU picking only sees meshes, UI is hit tested against the rects the canvases laid out last frame
if (CanvasViewActive()) {
if (Scene* scene = CurrentScene()) {
GameObject* object = CanvasView::Pick(*scene, glm::vec2{*m_Viewport.hoverPixel});
if (object != nullptr && ImGui::GetIO().KeyAlt) {
if (const Transform* parent = object->GetTransform().GetParent();
parent != nullptr && parent->GetOwner() != nullptr && parent->GetOwner()->GetComponent<RectTransform>() != nullptr) {
object = parent->GetOwner();
}
}
scene->SetSelectedObject(object != nullptr ? object->GetId() : InvalidObjectId);
}
return;
}
m_App.getRenderer().requestSelectionPick(*m_Viewport.hoverPixel);
m_Viewport.pickSelectsParent = ImGui::GetIO().KeyAlt;
}
+2 -2
View File
@@ -1,5 +1,6 @@
#include <editor/RectGizmo.h>
#include <algorithm>
#include <array>
#include <cmath>
@@ -60,7 +61,6 @@ bool RectGizmo::Manipulate(GameObject& object, const glm::vec2& drawImageSize, c
{
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;
@@ -103,7 +103,7 @@ bool RectGizmo::Manipulate(GameObject& object, const glm::vec2& drawImageSize, c
if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
m_ActiveHandle = -1;
} else {
const float canvasScale = canvas->GetScale(drawImageSize);
const float canvasScale = std::max(rectTransform->GetScreenScale(), 0.0001f);
glm::vec2 delta = (mouse - m_DragStartMouse) / (viewScale * canvasScale);
if (snap && snapStep > 0.0f) {
delta = glm::round(delta / snapStep) * snapStep;