Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -883,3 +883,12 @@ For now, the inspector continues to edit the active selected object only. Multi-
### Revisit when

Tools are stable enough and we have irnoned out any issues with the multi-selection and the command history model. Then we can start to explore grouped inspector edits with a clear understanding of how the editor behaves and what the user expects.

## Explicit editor tool mode state

Prune now has an explicit generic editor tool mode rather than inferring editor behaviour entirely from whichever viewport handle was clicked.

Initial tool modes are deliberately limited to Select and Move. Select keeps selection and handle-based movement. Move adds direct object-body dragging for movable authored objects. Scale and rotate should be added only when their own interaction rules and undo labels are implemented.

The active tool is transient editor state. It lives in the generic world scene state so viewport interaction and UI can share it, but it is not saved into scene files.

11 changes: 11 additions & 0 deletions NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ Multi-select now supports grouped viewport move and grouped delete. Grouped insp

### 5. Tool mode state

Status: implemented.

Introduce an explicit editor tool mode before adding more viewport tools.

Suggested initial tools:
Expand All @@ -111,6 +113,15 @@ enum class EditorTool {

This avoids hiding editor behaviour inside whichever handle happened to be clicked.

Current implementation:

- [x] Active tool state lives in generic world scene state.
- [x] A fixed viewport palette exposes Select and Move as text tool buttons.
- [x] The viewport palette also exposes scene-owned creation actions such as Wall, Platform, Hazard, and Terrain Line.
- [x] Select keeps the existing selection and move-handle behaviour.
- [x] Move allows direct body dragging for movable authored objects.
- [x] Scale and rotate remain unimplemented until their interaction rules are explicit.

### 6. Scale tool

Implement scale before rotate.
Expand Down
Binary file modified assets/repo/artillery.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/repo/platformer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/repo/simple-shooter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions src/prune/editor/editor_tool.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

namespace prune {

enum class EditorTool {
Select = 0,
Move
};

[[nodiscard]] inline constexpr const char* editor_tool_label(EditorTool tool) noexcept
{
switch (tool) {
case EditorTool::Select:
return "Select";
case EditorTool::Move:
return "Move";
}

return "Unknown";
}

[[nodiscard]] inline constexpr bool editor_tool_allows_body_move(EditorTool tool) noexcept
{
return tool == EditorTool::Move;
}

}
2 changes: 1 addition & 1 deletion src/prune/scene/artillery/artillery_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ namespace prune {
m_grid_options = {};
m_state.scene_options = {};
m_state.drag_state = {};
m_state.editor_tool = EditorTool::Select;
m_state.editor_commands.clear();
}

Expand Down Expand Up @@ -172,7 +173,6 @@ namespace prune {
tooling::EditorLayout::scene_panel();

if (ImGui::Begin("Artillery", &open)) {
draw_creation_tools();
draw_debug_tools();

ImGui::Separator();
Expand Down
6 changes: 5 additions & 1 deletion src/prune/scene/platformer/platformer_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ namespace prune {
platformer_state.player_id = first_object_id_for_kind(state, platformer_concepts::ObjectKind::Player);
}

if (GameObject* player = state.objects.get_by_id(platformer_state.player_id)) {
player->editor.movable = true;
}

if (!object_has_kind(state, platformer_state.player_start_id, platformer_concepts::ObjectKind::PlayerStart)) {
platformer_state.player_start_id = first_object_id_for_kind(state, platformer_concepts::ObjectKind::PlayerStart);
}
Expand Down Expand Up @@ -110,6 +114,7 @@ namespace prune {
m_grid_options = {};
m_state.scene_options = {};
m_state.drag_state = {};
m_state.editor_tool = EditorTool::Select;
m_state.editor_commands.clear();
}

Expand Down Expand Up @@ -178,7 +183,6 @@ namespace prune {
tooling::EditorLayout::scene_panel();

if (ImGui::Begin("Platformer", &open)) {
draw_creation_tools();
draw_debug_tools();

ImGui::Separator();
Expand Down
9 changes: 9 additions & 0 deletions src/prune/scene/scene.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "prune/core/input.hpp"
#include "prune/editor/editor_command.hpp"
#include "prune/editor/editor_tool.hpp"
#include "prune/scene/game_object_manager.hpp"
#include "prune/scene/object_concept.hpp"
#include "prune/scene/scene_camera.hpp"
Expand Down Expand Up @@ -110,6 +111,11 @@ namespace prune {
return k_invalid_game_object_id;
}

virtual bool execute_scene_creation_action(std::string_view)
{
return false;
}

[[nodiscard]] virtual std::string_view scene_tools_label() const noexcept = 0;
virtual void draw_scene_tools(bool& open) = 0;
virtual void draw_viewport_overlays() = 0;
Expand All @@ -122,6 +128,9 @@ namespace prune {

virtual SceneOptions& get_scene_options() = 0;

[[nodiscard]] virtual EditorTool current_editor_tool() const noexcept { return EditorTool::Select; }
virtual void set_current_editor_tool(EditorTool) noexcept {}

[[nodiscard]] virtual WorldSceneContext world_scene_context() noexcept { return {}; }
[[nodiscard]] virtual ConstWorldSceneContext world_scene_context() const noexcept { return {}; }

Expand Down
46 changes: 45 additions & 1 deletion src/prune/scene/scene_interaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ namespace prune {
return;
}

const std::vector<GameObjectId> movable_ids = movable_objects_from_handle_at_screen(scene, state, camera, input.mouse_x(), input.mouse_y());
const std::vector<GameObjectId> movable_ids = movable_objects_from_drag_start_at_screen(scene, state, camera, input.mouse_x(), input.mouse_y());
if (movable_ids.empty()) {
return;
}
Expand Down Expand Up @@ -433,6 +433,50 @@ namespace prune {
}



std::vector<GameObjectId> SceneInteraction::movable_objects_from_drag_start_at_screen(Scene& scene, SceneState& state, const SceneCamera& camera, int screen_x, int screen_y)
{
if (editor_tool_allows_body_move(state.editor_tool)) {
std::vector<GameObjectId> body_move_ids = movable_objects_from_move_tool_at_screen(scene, state, camera, screen_x, screen_y);
if (!body_move_ids.empty()) {
return body_move_ids;
}
}

return movable_objects_from_handle_at_screen(scene, state, camera, screen_x, screen_y);
}

std::vector<GameObjectId> SceneInteraction::movable_objects_from_move_tool_at_screen(Scene& scene, SceneState& state, const SceneCamera& camera, int screen_x, int screen_y)
{
GameObject* picked = pick_object_at_screen(scene, state, camera, screen_x, screen_y);
if (!picked || !scene.object_is_movable(*picked)) {
return {};
}

if (!state.objects.is_selected(picked->identity.id)) {
state.objects.select(picked->identity.id);
return { picked->identity.id };
}

if (state.objects.selected_count() <= 1) {
return { picked->identity.id };
}

std::vector<GameObjectId> movable_ids;
movable_ids.reserve(state.objects.selected_count());

for (const GameObjectId id : state.objects.selected_ids()) {
const GameObject* object = state.objects.get_by_id(id);
if (!object || !scene.object_is_movable(*object)) {
return {};
}

movable_ids.push_back(id);
}

return movable_ids;
}

std::vector<GameObjectId> SceneInteraction::movable_objects_from_handle_at_screen(Scene& scene, SceneState& state, const SceneCamera& camera, int screen_x, int screen_y)
{
std::vector<GameObjectId> movable_ids;
Expand Down
2 changes: 2 additions & 0 deletions src/prune/scene/scene_interaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ namespace prune {
void handle_delete_duplicate_shortcuts(Scene& scene, SceneState& state, const GridOptions& grid_options, const Input& input);

[[nodiscard]] GameObject* pick_object_at_screen(Scene& scene, SceneState& state, const SceneCamera& camera, int screen_x, int screen_y) noexcept;
[[nodiscard]] std::vector<GameObjectId> movable_objects_from_drag_start_at_screen(Scene& scene, SceneState& state, const SceneCamera& camera, int screen_x, int screen_y);
[[nodiscard]] std::vector<GameObjectId> movable_objects_from_move_tool_at_screen(Scene& scene, SceneState& state, const SceneCamera& camera, int screen_x, int screen_y);
[[nodiscard]] std::vector<GameObjectId> movable_objects_from_handle_at_screen(Scene& scene, SceneState& state, const SceneCamera& camera, int screen_x, int screen_y);
[[nodiscard]] static bool selected_screen_bounds(const SceneState& state, const SceneCamera& camera, SDL_Rect& bounds) noexcept;
[[nodiscard]] static bool rect_visible(const SceneViewport& viewport, const SDL_Rect& rect) noexcept;
Expand Down
2 changes: 2 additions & 0 deletions src/prune/scene/scene_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "prune/core/defaults.hpp"
#include "prune/editor/editor_command.hpp"
#include "prune/editor/editor_tool.hpp"
#include "prune/scene/game_object.hpp"
#include "prune/scene/game_object_manager.hpp"

Expand Down Expand Up @@ -71,6 +72,7 @@ namespace prune {
SceneViewport viewport{};
DragState drag_state{};
SceneOptions scene_options{};
EditorTool editor_tool = EditorTool::Select;

GameObjectManager objects;
EditorCommandHistory editor_commands;
Expand Down
4 changes: 3 additions & 1 deletion src/prune/scene/simple_shooter/simple_shooter_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ namespace prune {
if (player->runtime.behaviour.empty()) {
player->runtime.behaviour = simple_shooter_ids::player_behaviour;
}

player->editor.movable = true;
}

restore_legacy_wall_concepts(state);
Expand Down Expand Up @@ -140,7 +142,6 @@ namespace prune {
tooling::EditorLayout::scene_panel();

if (ImGui::Begin("Simple Shooter", &open)) {
draw_creation_tools();
draw_debug_tools();

ImGui::Separator();
Expand Down Expand Up @@ -219,6 +220,7 @@ namespace prune {
m_grid_options = {};
m_state.scene_options = {};
m_state.drag_state = {};
m_state.editor_tool = EditorTool::Select;
m_state.editor_commands.clear();

m_simple_shooter_state = {};
Expand Down
49 changes: 24 additions & 25 deletions src/prune/scene/world_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,39 +73,28 @@ namespace prune {
}
}

void WorldScene::draw_creation_tools()
bool WorldScene::execute_scene_creation_action(std::string_view action_id)
{
if (!tooling::imgui::layout::collapsing_header("Game Tools", true)) {
return;
}

tooling::imgui::layout::spacing(3);

const std::span<const SceneCreationAction> actions = scene_creation_actions();
if (actions.empty()) {
ImGui::TextUnformatted("No creation actions for this scene.");
return;
}
for (const SceneCreationAction& action : scene_creation_actions()) {
if (action.id != action_id) {
continue;
}

bool first = true;
for (const SceneCreationAction& action : actions) {
if (!first) {
ImGui::SameLine();
const GameObjectId created_id = create_scene_object(action.id);
if (created_id == k_invalid_game_object_id) {
return false;
}

ImGui::PushID(action.id.data());
if (ImGui::Button(action.label.data())) {
const GameObjectId created_id = create_scene_object(action.id);
if (const GameObject* created = m_state.objects.get_by_id(created_id)) {
record_editor_command(make_create_object_command(*created, action.label));
}
const GameObject* created = m_state.objects.get_by_id(created_id);
if (!created) {
return false;
}
ImGui::PopID();

first = false;
record_editor_command(make_create_object_command(*created, action.label));
return true;
}

tooling::imgui::layout::spacing(3);
return false;
}

void WorldScene::draw_debug_tools()
Expand Down Expand Up @@ -271,6 +260,16 @@ namespace prune {
return m_state.scene_options;
}

EditorTool WorldScene::current_editor_tool() const noexcept
{
return m_state.editor_tool;
}

void WorldScene::set_current_editor_tool(EditorTool tool) noexcept
{
m_state.editor_tool = tool;
}

WorldSceneContext WorldScene::world_scene_context() noexcept
{
return WorldSceneContext{ &m_grid_options, &m_camera };
Expand Down
4 changes: 3 additions & 1 deletion src/prune/scene/world_scene.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ namespace prune {
bool undo_editor_command() final;
bool redo_editor_command() final;
SceneOptions& get_scene_options() final;
[[nodiscard]] EditorTool current_editor_tool() const noexcept final;
void set_current_editor_tool(EditorTool tool) noexcept final;
bool execute_scene_creation_action(std::string_view action_id) final;

[[nodiscard]] WorldSceneContext world_scene_context() noexcept final;
[[nodiscard]] ConstWorldSceneContext world_scene_context() const noexcept final;
Expand All @@ -53,7 +56,6 @@ namespace prune {
[[nodiscard]] Transform first_free_view_center_spawn_position(const GameObject& object) const;
[[nodiscard]] bool is_space_free(const GameObject& candidate) const noexcept;

void draw_creation_tools();
void draw_debug_tools();

virtual void update_runtime(float dt, const Input& input, bool keyboard_input_enabled) = 0;
Expand Down
4 changes: 3 additions & 1 deletion src/prune/tooling/controls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ namespace prune {
tooling::imgui::layout::text_wrapped("Left click selects one object");
tooling::imgui::layout::text_wrapped("Shift+Left click toggles objects in the selection set");
tooling::imgui::layout::text_wrapped("Empty viewport click clears the selection set");
tooling::imgui::layout::text_wrapped("Mouse button left on a selection handle moves the selected object or selected objects");
tooling::imgui::layout::text_wrapped("Use the Select/Move tool buttons in the main menu bar to change viewport tool mode");

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 | ⚡ Quick win

Update the tool location text to match the actual UI.

The tools are rendered in the viewport palette, not in the main menu bar, so this instruction is currently misleading.

tooling::imgui::layout::text_wrapped("Select tool: left click selects, Shift+Left click toggles selection, and selection handles move selected objects");
tooling::imgui::layout::text_wrapped("Move tool: left drag an object body to move it; dragging a selected object moves the selection set");

tooling::imgui::layout::spacing(2);

Expand Down
2 changes: 1 addition & 1 deletion src/prune/tooling/editor_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace prune::tooling {
constexpr float k_options_height = 308.0f;

constexpr float k_controls_width = 377.0f;
constexpr float k_controls_height = 165.0f;
constexpr float k_controls_height = 215.0f;

constexpr float k_scene_min_width = 320.0f;
constexpr float k_scene_min_height = 240.0f;
Expand Down
Loading
Loading