Skip to content
Merged
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
25 changes: 25 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -858,3 +858,28 @@ Rotation cuts across rendering, picking, collision, bounds, serialization, inspe
- The next viewport transform tool after move should be scale.
- Rotation remains planned but deliberately later.
- Any future rotation work should start by documenting what rotates visually only and what rotates physically.

---

## Defer grouped inspector edit actions

### Decision

Implement basic group support for move and delete, but defer grouped inspector/property edits.

### Why

Grouped editor commands are now supported for multi-selection move and delete, because those actions need to undo and redo atomically as a single user action.

Grouped inspector/property edits are intentionally deferred. Changing size, colour, sprite, flags, labels, or scene-specific properties across multiple selected objects introduces additional UI and command-history complexity:

mixed values need clear inspector behaviour
partial edits need explicit rules
command history must describe the grouped edit accurately
undo/redo must restore all affected objects as one atomic command

For now, the inspector continues to edit the active selected object only. Multi-object mutation is limited to viewport movement and deletion.

### 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.
26 changes: 16 additions & 10 deletions NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ Add normal editor actions once command history exists.

Initial scope:

- Delete selected authored object
- Duplicate selected authored object
- Select the duplicated object
- Offset duplicates slightly so the result is visible
- [x] Delete selected authored object
- [x] Duplicate selected authored object
- [x] Select the duplicated object
- [x] Offset duplicates slightly so the result is visible

This gives undo/redo useful behaviour to prove.

Expand All @@ -80,12 +80,18 @@ Add multi-select after the single-object command path is stable.

Initial scope:

- Selection set instead of one selected id
- Shift-click to add/remove from the selection set
- Clear selection on empty viewport click
- Outliner multi-select support - only if it stays small

The first implementation should not include grouping.
- [x] Selection set instead of one selected id
- [x] Shift-click to add/remove from the selection set
- [x] Clear selection on empty viewport click
- [x] Outliner multi-select support
- [x] Visible outline for every selected object
- [x] Combined selection bounds in the viewport
- [x] Multi-selection drag handle on the selection bounds
- [x] Selection count in editor UI
- [x] Move selected objects as one editor command
- [x] Delete selected deletable objects as one editor command

Multi-select now supports grouped viewport move and grouped delete. Grouped inspector/property edits remain deferred because they need more specific UX and command labels.

### 5. Tool mode state

Expand Down
47 changes: 47 additions & 0 deletions src/prune/editor/editor_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ namespace prune {
return "Change sprite";
case EditorCommandType::MoveViewport:
return "Move viewport";
case EditorCommandType::MoveObjects:
return "Move objects";
case EditorCommandType::DeleteObjects:
return "Delete objects";
}

return "Unknown editor command";
Expand All @@ -120,6 +124,7 @@ namespace prune {
command.label = std::string(label);
command.detail = std::string(detail);
command.object_id = after.identity.id;
command.object_ids.push_back(after.identity.id);
command.before_object = before;
command.after_object = after;

Expand All @@ -135,6 +140,7 @@ namespace prune {
command.label = editor_command_type_label(EditorCommandType::CreateObject);
command.detail = std::string(detail);
command.object_id = created.identity.id;
command.object_ids.push_back(created.identity.id);
command.after_object = created;

return command;
Expand All @@ -149,11 +155,52 @@ namespace prune {
command.label = editor_command_type_label(EditorCommandType::DeleteObject);
command.detail = std::string(detail);
command.object_id = deleted.identity.id;
command.object_ids.push_back(deleted.identity.id);
command.before_object = deleted;

return command;
}

EditorCommand make_multi_object_command(
EditorCommandType type,
std::string_view label,
std::span<const GameObject> before,
std::span<const GameObject> after,
std::string_view detail
) {
EditorCommand command{};
command.type = type;
command.label = std::string(label);
command.detail = std::string(detail);
command.before_objects.assign(before.begin(), before.end());
command.after_objects.assign(after.begin(), after.end());

command.object_ids.reserve(command.after_objects.size());
for (const GameObject& object : command.after_objects) {
command.object_ids.push_back(object.identity.id);
}

return command;
}

EditorCommand make_multi_delete_object_command(
std::span<const GameObject> deleted,
std::string_view detail
) {
EditorCommand command{};
command.type = EditorCommandType::DeleteObjects;
command.label = editor_command_type_label(EditorCommandType::DeleteObjects);
command.detail = std::string(detail);
command.before_objects.assign(deleted.begin(), deleted.end());

command.object_ids.reserve(command.before_objects.size());
for (const GameObject& object : command.before_objects) {
command.object_ids.push_back(object.identity.id);
}

return command;
}

EditorCommand make_viewport_command(
std::string_view label,
const Camera& before,
Expand Down
21 changes: 20 additions & 1 deletion src/prune/editor/editor_command.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <cstddef>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
Expand All @@ -22,7 +23,9 @@ namespace prune {
ChangeObjectColour,
ChangeObjectFlag,
ChangeSprite,
MoveViewport
MoveViewport,
MoveObjects,
DeleteObjects
};

struct EditorCommand {
Expand All @@ -33,6 +36,9 @@ namespace prune {

std::optional<GameObject> before_object;
std::optional<GameObject> after_object;
std::vector<GameObject> before_objects;
std::vector<GameObject> after_objects;
std::vector<GameObjectId> object_ids;

std::optional<Camera> before_camera;
std::optional<Camera> after_camera;
Expand Down Expand Up @@ -80,6 +86,19 @@ namespace prune {
std::string_view detail = {}
);

[[nodiscard]] EditorCommand make_multi_object_command(
EditorCommandType type,
std::string_view label,
std::span<const GameObject> before,
std::span<const GameObject> after,
std::string_view detail = {}
);

[[nodiscard]] EditorCommand make_multi_delete_object_command(
std::span<const GameObject> deleted,
std::string_view detail = {}
);

[[nodiscard]] EditorCommand make_viewport_command(
std::string_view label,
const Camera& before,
Expand Down
Loading
Loading