From d26f61a16f8e9f70d3d6c3f0ffb1e32baceebd22 Mon Sep 17 00:00:00 2001 From: Bram Verhulst Date: Sun, 1 Nov 2026 23:16:58 +0100 Subject: [PATCH] feat: add canvas rework --- destrum/include/destrum/AppLayer.h | 5 + .../include/destrum/Components/UI/Canvas.h | 2 + .../destrum/Components/UI/CanvasFrame.h | 13 + .../destrum/Components/UI/RectTransform.h | 2 + .../include/destrum/Graphics/RenderContext.h | 5 + destrum/src/App.cpp | 1 + destrum/src/Components/UI/Canvas.cpp | 16 +- editor/CMakeLists.txt | 1 + editor/include/editor/CanvasView.h | 45 ++++ editor/include/editor/Editor.h | 6 +- editor/src/CanvasView.cpp | 238 ++++++++++++++++++ editor/src/Editor.cpp | 37 ++- editor/src/EditorMenus.cpp | 3 + editor/src/EditorViewport.cpp | 27 ++ editor/src/RectGizmo.cpp | 4 +- 15 files changed, 393 insertions(+), 12 deletions(-) create mode 100644 destrum/include/destrum/Components/UI/CanvasFrame.h create mode 100644 editor/include/editor/CanvasView.h create mode 100644 editor/src/CanvasView.cpp diff --git a/destrum/include/destrum/AppLayer.h b/destrum/include/destrum/AppLayer.h index aa064d1..23e58ba 100644 --- a/destrum/include/destrum/AppLayer.h +++ b/destrum/include/destrum/AppLayer.h @@ -1,6 +1,10 @@ #ifndef DESTRUM_APPLAYER_H #define DESTRUM_APPLAYER_H +#include + +#include + 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() const { return std::nullopt; } }; #endif // DESTRUM_APPLAYER_H diff --git a/destrum/include/destrum/Components/UI/Canvas.h b/destrum/include/destrum/Components/UI/Canvas.h index e73cb0f..397b6a9 100644 --- a/destrum/include/destrum/Components/UI/Canvas.h +++ b/destrum/include/destrum/Components/UI/Canvas.h @@ -1,6 +1,7 @@ #ifndef DESTRUM_CANVAS_H #define DESTRUM_CANVAS_H +#include #include #include @@ -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; diff --git a/destrum/include/destrum/Components/UI/CanvasFrame.h b/destrum/include/destrum/Components/UI/CanvasFrame.h new file mode 100644 index 0000000..4c0c2b3 --- /dev/null +++ b/destrum/include/destrum/Components/UI/CanvasFrame.h @@ -0,0 +1,13 @@ +#ifndef DESTRUM_CANVASFRAME_H +#define DESTRUM_CANVASFRAME_H + +#include + +// 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 diff --git a/destrum/include/destrum/Components/UI/RectTransform.h b/destrum/include/destrum/Components/UI/RectTransform.h index 1201273..a5b4c56 100644 --- a/destrum/include/destrum/Components/UI/RectTransform.h +++ b/destrum/include/destrum/Components/UI/RectTransform.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; diff --git a/destrum/include/destrum/Graphics/RenderContext.h b/destrum/include/destrum/Graphics/RenderContext.h index eb800dd..bb54f16 100644 --- a/destrum/include/destrum/Graphics/RenderContext.h +++ b/destrum/include/destrum/Graphics/RenderContext.h @@ -1,12 +1,17 @@ #ifndef RENDERCONTEXT_H #define RENDERCONTEXT_H +#include + +#include #include struct RenderContext { GameRenderer& renderer; const Camera& camera; const GameRenderer::SceneData& sceneData; + // Unset: canvases fill the draw image + std::optional canvasFrame{}; }; #endif //RENDERCONTEXT_H diff --git a/destrum/src/App.cpp b/destrum/src/App.cpp index 01125ba..231aefa 100644 --- a/destrum/src/App.cpp +++ b/destrum/src/App.cpp @@ -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); diff --git a/destrum/src/Components/UI/Canvas.cpp b/destrum/src/Components/UI/Canvas.cpp index 39e2d1c..f5f32b6 100644 --- a/destrum/src/Components/UI/Canvas.cpp +++ b/destrum/src/Components/UI/Canvas.cpp @@ -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(extent.width), static_cast(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->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 diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index a1517ca..cd4c14d 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -11,6 +11,7 @@ set(EDITOR_SRC src/AssetBrowser.cpp src/TransformGizmo.cpp src/RectGizmo.cpp + src/CanvasView.cpp src/ResourcesInspector.cpp ) diff --git a/editor/include/editor/CanvasView.h b/editor/include/editor/CanvasView.h new file mode 100644 index 0000000..e83876f --- /dev/null +++ b/editor/include/editor/CanvasView.h @@ -0,0 +1,45 @@ +#ifndef EDITOR_CANVASVIEW_H +#define EDITOR_CANVASVIEW_H + +#include + +#include + +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 diff --git a/editor/include/editor/Editor.h b/editor/include/editor/Editor.h index 2cb6b41..3254375 100644 --- a/editor/include/editor/Editor.h +++ b/editor/include/editor/Editor.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -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() 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; diff --git a/editor/src/CanvasView.cpp b/editor/src/CanvasView.cpp new file mode 100644 index 0000000..f711b11 --- /dev/null +++ b/editor/src/CanvasView.cpp @@ -0,0 +1,238 @@ +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace { + struct ResolutionPreset { + const char* label; + glm::ivec2 size; + }; + + constexpr std::array 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(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(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()) { + return; + } + if (const auto* rectTransform = object.GetComponent(); + 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(m_Preset)].label : "Custom"; + if (ImGui::BeginCombo("Resolution", preview)) { + for (int i = 0; i < CustomPreset; ++i) { + if (ImGui::Selectable(Presets[static_cast(i)].label, m_Preset == i)) { + m_Preset = i; + m_Resolution = Presets[static_cast(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() && object->IsActiveInHierarchy()) { + CollectHits(*object, pixel, true, hit); + } + } + return hit.object; +} diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index 466ba56..229ab3a 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -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 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 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())); diff --git a/editor/src/EditorMenus.cpp b/editor/src/EditorMenus.cpp index 52a658e..a2f98fe 100644 --- a/editor/src/EditorMenus.cpp +++ b/editor/src/EditorMenus.cpp @@ -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)) { diff --git a/editor/src/EditorViewport.cpp b/editor/src/EditorViewport.cpp index bb8452d..471bd91 100644 --- a/editor/src/EditorViewport.cpp +++ b/editor/src/EditorViewport.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,15 @@ void Editor::DrawViewport() m_Viewport.hoverPixel = glm::ivec2{uv * glm::vec2{static_cast(extent.width), static_cast(extent.height)}}; } + const glm::vec2 drawImageSize{static_cast(drawImage.extent.width), static_cast(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() != 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; } diff --git a/editor/src/RectGizmo.cpp b/editor/src/RectGizmo.cpp index 278f0f7..f311a4a 100644 --- a/editor/src/RectGizmo.cpp +++ b/editor/src/RectGizmo.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -60,7 +61,6 @@ bool RectGizmo::Manipulate(GameObject& object, const glm::vec2& drawImageSize, c { 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; @@ -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;