Skip to content

Scene viewport as a panel#25

Merged
deanblackborough merged 4 commits intomainfrom
scene-viewport-panel
Apr 20, 2026
Merged

Scene viewport as a panel#25
deanblackborough merged 4 commits intomainfrom
scene-viewport-panel

Conversation

@deanblackborough
Copy link
Copy Markdown
Owner

@deanblackborough deanblackborough commented Apr 20, 2026

  • Added a viewport for the scene
  • Not the entire SDL window with floating panels
  • Notes for next steps

Summary by CodeRabbit

  • New Features

    • Scene viewport now renders into a dedicated editor panel with offscreen rendering and improved selection/interaction gating.
    • Viewport stats updated to show position, size, hovered/focused state.
  • Bug Fixes

    • Improved mouse input handling so button events use current cursor coordinates for more accurate selection.
  • Documentation

    • Added a short-term roadmap outlining sprite support, movement gizmos, inspector/outliner scene-awareness and other editor enhancements.

- Added a viewport for the scene
- Not the entire SDL window with floating panels
@deanblackborough deanblackborough self-assigned this Apr 20, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR adds a new Notes.md roadmap and introduces a viewport abstraction (prune::SceneViewport) carrying position, size and hover/focus state. SandboxScene replaces width/height APIs with set_viewport/get_viewport and uses the new viewport for camera, input gating and coordinate conversion. App renames its UI member to m_ui and forwards SDL events directly to input. Input now updates mouse coordinates on button events. Ui now renders the scene into an SDL texture render target and exposes Ui::render(SandboxScene&, SDL_Renderer*). Tooling signatures (Stats::draw, property_table) were adjusted to use SceneViewport. No behavioural game-logic changes beyond input gating.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Scene viewport as a panel' directly captures the core architectural change—moving scene rendering from the full SDL window into a dedicated ImGui panel viewport.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch scene-viewport-panel

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Apr 20, 2026
coderabbitai[bot]
coderabbitai Bot previously requested changes Apr 20, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/prune/app/app.cpp (2)

107-114: ⚠️ Potential issue | 🟡 Minor

Remove the duplicate resize refresh.

m_window->refresh_size() is called twice for the same SDL_WINDOWEVENT_SIZE_CHANGED. Harmless, but it does duplicate work on every resize and looks like a copy/paste slip.

🧹 Proposed fix
                 case SDL_WINDOWEVENT_SIZE_CHANGED:
                     if (m_window) {
                         m_window->refresh_size();
                     }
-
-                    if (m_window) {
-                        m_window->refresh_size();
-                    }
                     break;

132-137: ⚠️ Potential issue | 🟠 Major

Gate keyboard events to prevent app shortcuts firing whilst ImGui is capturing input.

The app closes unexpectedly when users press Escape whilst typing in ImGui widgets. The scene properly gates its own input handling, but App::run() checks the Escape shortcut unconditionally after calling process_events(). Because process_events() forwards all keyboard events to m_input without checking ImGui::GetIO().WantCaptureKeyboard, the Input system records the key press regardless of ImGui's state.

Refactor to check ImGui's keyboard capture flag before forwarding keyboard events:

Proposed fix
         while (SDL_PollEvent(&event)) {
             ImGui_ImplSDL2_ProcessEvent(&event);
             handle_event(event);
 
-            m_input->process_event(event);
+            const bool is_keyboard_event =
+                event.type == SDL_KEYDOWN || event.type == SDL_KEYUP;
+            if (!is_keyboard_event || !ImGui::GetIO().WantCaptureKeyboard) {
+                m_input->process_event(event);
+            }
         }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 65ce4bd0-7217-4a7e-870c-48e60ab450d7

📥 Commits

Reviewing files that changed from the base of the PR and between 8b85c8e and aa96f49.

📒 Files selected for processing (12)
  • Notes.md
  • src/prune/app/app.cpp
  • src/prune/app/app.hpp
  • src/prune/core/input.cpp
  • src/prune/scene/sandbox_scene.cpp
  • src/prune/scene/sandbox_scene.hpp
  • src/prune/scene/sandbox_scene_editor.cpp
  • src/prune/tooling/imgui/property_table.cpp
  • src/prune/tooling/stats.cpp
  • src/prune/tooling/stats.hpp
  • src/prune/tooling/ui.cpp
  • src/prune/tooling/ui.hpp

Comment thread Notes.md

Don’t over-engineer this
Don’t build a full framework yet
Just stop SandboxScene becoming the permanent dumping ground No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a trailing newline at the end of the file.

Standard Markdown formatting expects files to end with a single newline character. This keeps tooling happy and follows best practices.

📝 Proposed fix

Add a single newline after the last line of content.

 Just stop SandboxScene becoming the permanent dumping ground
+

As per static analysis: MD047 (single-trailing-newline).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Just stop SandboxScene becoming the permanent dumping ground
Just stop SandboxScene becoming the permanent dumping ground
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 144-144: Files should end with a single newline character

(MD047, single-trailing-newline)

Comment on lines +95 to +105
if (!m_viewport.hovered) {
return;
}

if (!input.was_mouse_button_pressed(SDL_BUTTON_LEFT)) {
return;
}

if (!m_viewport.contains(input.mouse_x(), input.mouse_y())) {
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t gate picking on cached hovered state.

m_viewport.hovered is filled during UI render, but handle_scene_click() consumes it in the next update. That makes the first click after moving into the scene panel a no-op, because the hover flag is still from the previous frame. The coordinate check is the reliable guard here; keep contains(input.mouse_x(), input.mouse_y()) and drop the stale hovered early-return.

🛠️ Proposed fix
-        if (!m_viewport.hovered) {
-            return;
-        }
-
         if (!input.was_mouse_button_pressed(SDL_BUTTON_LEFT)) {
             return;
         }
 
         if (!m_viewport.contains(input.mouse_x(), input.mouse_y())) {
             return;
         }

Comment on lines +107 to +108
begin_row(label);
return ImGui::DragFloat(id, &value, speed, min, max, format);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect drag_float call sites and ID argument style.
rg -nP -C2 --iglob '*.{cpp,cc,cxx,h,hpp,hxx}' '\b(?:property_table::)?drag_float\s*\('

echo "---- callsites with string literal label + id ----"
rg -nP --iglob '*.{cpp,cc,cxx,h,hpp,hxx}' \
  '\b(?:property_table::)?drag_float\s*\(\s*"[^"]+"\s*,\s*"[^"]+"'

echo "---- callsites where id is already hidden (starts with ##) ----"
rg -nP --iglob '*.{cpp,cc,cxx,h,hpp,hxx}' \
  '\b(?:property_table::)?drag_float\s*\(\s*"[^"]+"\s*,\s*"##[^"]*"'

Repository: deanblackborough/Prune

Length of output: 4142


🏁 Script executed:

sed -n '97,110p' src/prune/tooling/imgui/property_table.cpp

Repository: deanblackborough/Prune

Length of output: 360


🏁 Script executed:

sed -n '70,110p' src/prune/tooling/imgui/property_table.cpp

Repository: deanblackborough/Prune

Length of output: 1074


🏁 Script executed:

sed -n '1,70p' src/prune/tooling/imgui/property_table.cpp

Repository: deanblackborough/Prune

Length of output: 1663


Restore hidden-ID semantics in drag_float to match combo pattern.

Right catch here. Look at the combo function a few lines up—it uses PushID(id) paired with a hidden label ("##value"). That's the established pattern in this file for keeping internal IDs out of the UI. Your drag_float refactor at line 108 passes id directly to ImGui::DragFloat, which exposes it as a visible label.

All your call sites use nice descriptive IDs ("editor_x", "game_x", "transform_x", etc.), so they'll now show up in the value column—changing the UI contract quietly. The begin_row(label) refactor is solid, but let's keep the ID-hiding semantics consistent with how combo does it.

Apply the suggested fix: wrap the ImGui call with PushID/PopID and use "##value" as the label. That keeps your layout cleanup while maintaining the established pattern.

Comment thread src/prune/tooling/ui.cpp
Comment on lines +218 to +220
if (!m_show_scene_viewport) {
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reset scene viewport when the Scene panel is hidden.

Line 218 returns early without clearing the scene viewport. That leaves stale focused/width/height in SandboxScene, so scene input can remain enabled even after hiding the panel.

Suggested fix
         if (!m_show_scene_viewport) {
+            scene.set_viewport({});
             return;
         }

Comment thread src/prune/tooling/ui.hpp
Comment on lines +19 to +29
~Ui();
void render(SandboxScene& scene, SDL_Renderer* renderer);

private:
void draw_scene_viewport(SandboxScene& scene, SDL_Renderer* renderer);
void ensure_scene_render_target(SDL_Renderer* renderer, int width, int height);
void destroy_scene_render_target();

SDL_Texture* m_scene_render_target = nullptr;
int m_scene_render_target_width = 0;
int m_scene_render_target_height = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, locate and read the file to verify the exact code at those lines
fd -t f "ui.hpp" | grep -E "prune/tooling"

Repository: deanblackborough/Prune

Length of output: 90


🏁 Script executed:

# Read the file to see the class declaration context
cat -n src/prune/tooling/ui.hpp | head -40

Repository: deanblackborough/Prune

Length of output: 1373


🏁 Script executed:

# Search for any copy/move operation definitions or deletions in the Ui class
rg -A 20 "class Ui" src/prune/tooling/ui.hpp

Repository: deanblackborough/Prune

Length of output: 754


🏁 Script executed:

# Get the complete class declaration
cat -n src/prune/tooling/ui.hpp

Repository: deanblackborough/Prune

Length of output: 1775


🏁 Script executed:

# Search for any copy/move operation declarations (deleted, defined, etc.)
rg "operator=|Ui\(const Ui|Ui\(Ui" src/prune/tooling/ui.hpp

Repository: deanblackborough/Prune

Length of output: 48


🏁 Script executed:

# Check the destructor implementation to see what it does
rg -A 10 "Ui::~Ui" src/prune/tooling/ui.cpp

Repository: deanblackborough/Prune

Length of output: 335


Mark the Ui class as non-copyable to prevent accidental double-destruction of the SDL texture.

This class now owns an SDL_Texture* and your destructor cleans it up by calling destroy_scene_render_target(). Without explicitly deleting the copy and move operations, the compiler generates defaults that shallow-copy the pointer—and that's a recipe for a crash when both copies get destroyed. In game engine code, this kind of silent corruption is exactly the bug that haunts you in the wild. Either delete the copy/move operations (simplest fix for now) or wrap the texture in an RAII type if you need to share it later.

Suggested fix
 class Ui {
 public:
     ~Ui();
+    Ui(const Ui&) = delete;
+    Ui& operator=(const Ui&) = delete;
+    Ui(Ui&&) = delete;
+    Ui& operator=(Ui&&) = delete;
     void render(SandboxScene& scene, SDL_Renderer* renderer);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
~Ui();
void render(SandboxScene& scene, SDL_Renderer* renderer);
private:
void draw_scene_viewport(SandboxScene& scene, SDL_Renderer* renderer);
void ensure_scene_render_target(SDL_Renderer* renderer, int width, int height);
void destroy_scene_render_target();
SDL_Texture* m_scene_render_target = nullptr;
int m_scene_render_target_width = 0;
int m_scene_render_target_height = 0;
~Ui();
Ui(const Ui&) = delete;
Ui& operator=(const Ui&) = delete;
Ui(Ui&&) = delete;
Ui& operator=(Ui&&) = delete;
void render(SandboxScene& scene, SDL_Renderer* renderer);
private:
void draw_scene_viewport(SandboxScene& scene, SDL_Renderer* renderer);
void ensure_scene_render_target(SDL_Renderer* renderer, int width, int height);
void destroy_scene_render_target();
SDL_Texture* m_scene_render_target = nullptr;
int m_scene_render_target_width = 0;
int m_scene_render_target_height = 0;

@deanblackborough deanblackborough dismissed coderabbitai[bot]’s stale review April 20, 2026 21:35

Still in earlier development

@deanblackborough deanblackborough merged commit 8b32425 into main Apr 20, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant