diff --git a/CMakeLists.txt b/CMakeLists.txt index d73df28..67c6389 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(Prune src/prune/tooling/inspector.cpp src/prune/tooling/command_history.cpp src/prune/tooling/sprite_picker.cpp + src/prune/tooling/editor/tracked_property_table.cpp src/prune/tooling/controls.cpp src/prune/tooling/options.cpp src/prune/tooling/stats.cpp diff --git a/src/prune/editor/editor_command.cpp b/src/prune/editor/editor_command.cpp index c8605e1..8418b31 100644 --- a/src/prune/editor/editor_command.cpp +++ b/src/prune/editor/editor_command.cpp @@ -110,13 +110,15 @@ namespace prune { EditorCommand make_object_command( EditorCommandType type, - std::string label, + std::string_view label, const GameObject& before, - const GameObject& after + const GameObject& after, + std::string_view detail ) { EditorCommand command{}; command.type = type; - command.label = std::move(label); + command.label = std::string(label); + command.detail = std::string(detail); command.object_id = after.identity.id; command.before_object = before; command.after_object = after; @@ -124,22 +126,28 @@ namespace prune { return command; } - EditorCommand make_create_object_command(const GameObject& created) - { + EditorCommand make_create_object_command( + const GameObject& created, + std::string_view detail + ) { EditorCommand command{}; command.type = EditorCommandType::CreateObject; command.label = editor_command_type_label(EditorCommandType::CreateObject); + command.detail = std::string(detail); command.object_id = created.identity.id; command.after_object = created; return command; } - EditorCommand make_delete_object_command(const GameObject& deleted) - { + EditorCommand make_delete_object_command( + const GameObject& deleted, + std::string_view detail + ) { EditorCommand command{}; command.type = EditorCommandType::DeleteObject; command.label = editor_command_type_label(EditorCommandType::DeleteObject); + command.detail = std::string(detail); command.object_id = deleted.identity.id; command.before_object = deleted; @@ -147,13 +155,15 @@ namespace prune { } EditorCommand make_viewport_command( - std::string label, + std::string_view label, const Camera& before, - const Camera& after + const Camera& after, + std::string_view detail ) { EditorCommand command{}; command.type = EditorCommandType::MoveViewport; - command.label = std::move(label); + command.label = std::string(label); + command.detail = std::string(detail); command.before_camera = before; command.after_camera = after; diff --git a/src/prune/editor/editor_command.hpp b/src/prune/editor/editor_command.hpp index 902cb7f..92232af 100644 --- a/src/prune/editor/editor_command.hpp +++ b/src/prune/editor/editor_command.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "prune/scene/game_object.hpp" @@ -27,6 +28,7 @@ namespace prune { struct EditorCommand { EditorCommandType type = EditorCommandType::MoveObject; std::string label; + std::string detail; GameObjectId object_id = k_invalid_game_object_id; std::optional before_object; @@ -62,18 +64,27 @@ namespace prune { [[nodiscard]] EditorCommand make_object_command( EditorCommandType type, - std::string label, + std::string_view label, const GameObject& before, - const GameObject& after + const GameObject& after, + std::string_view detail = {} ); - [[nodiscard]] EditorCommand make_create_object_command(const GameObject& created); - [[nodiscard]] EditorCommand make_delete_object_command(const GameObject& deleted); + [[nodiscard]] EditorCommand make_create_object_command( + const GameObject& created, + std::string_view detail = {} + ); + + [[nodiscard]] EditorCommand make_delete_object_command( + const GameObject& deleted, + std::string_view detail = {} + ); [[nodiscard]] EditorCommand make_viewport_command( - std::string label, + std::string_view label, const Camera& before, - const Camera& after + const Camera& after, + std::string_view detail = {} ); } diff --git a/src/prune/scene/scene_interaction.cpp b/src/prune/scene/scene_interaction.cpp index 7b08934..d6a3a27 100644 --- a/src/prune/scene/scene_interaction.cpp +++ b/src/prune/scene/scene_interaction.cpp @@ -54,7 +54,8 @@ namespace prune { scene.record_editor_command(make_viewport_command( editor_command_type_label(EditorCommandType::MoveViewport), m_viewport_pan_start, - after + after, + "Mouse pan" )); } @@ -110,7 +111,8 @@ namespace prune { EditorCommandType::MoveObject, editor_command_type_label(EditorCommandType::MoveObject), before, - *object + *object, + "Mouse drag" )); } } @@ -233,7 +235,8 @@ namespace prune { EditorCommandType::MoveObject, editor_command_type_label(EditorCommandType::MoveObject), before, - *selected + *selected, + "Keyboard nudge" )); } } diff --git a/src/prune/scene/world_scene.cpp b/src/prune/scene/world_scene.cpp index 7ac969c..b4e6ca7 100644 --- a/src/prune/scene/world_scene.cpp +++ b/src/prune/scene/world_scene.cpp @@ -97,7 +97,7 @@ namespace prune { 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)); + record_editor_command(make_create_object_command(*created, action.label)); } } ImGui::PopID(); diff --git a/src/prune/tooling/command_history.cpp b/src/prune/tooling/command_history.cpp index 850ec25..152c561 100644 --- a/src/prune/tooling/command_history.cpp +++ b/src/prune/tooling/command_history.cpp @@ -48,14 +48,26 @@ namespace prune { ImGui::BeginDisabled(); } - ImGui::Text( - "%s%zu. %s", - applied ? "" : "redo: ", - command_index + 1, - command.label.empty() - ? editor_command_type_label(command.type) - : command.label.c_str() - ); + const char* label = command.label.empty() + ? editor_command_type_label(command.type) + : command.label.c_str(); + + if (command.detail.empty()) { + ImGui::Text( + "%s%zu. %s", + applied ? "" : "redo: ", + command_index + 1, + label + ); + } else { + ImGui::Text( + "%s%zu. %s: %s", + applied ? "" : "redo: ", + command_index + 1, + label, + command.detail.c_str() + ); + } if (command.object_id != k_invalid_game_object_id) { ImGui::SameLine(); diff --git a/src/prune/tooling/editor/tracked_property_table.cpp b/src/prune/tooling/editor/tracked_property_table.cpp new file mode 100644 index 0000000..fbbea19 --- /dev/null +++ b/src/prune/tooling/editor/tracked_property_table.cpp @@ -0,0 +1,210 @@ +#include "imgui.h" + +#include "prune/tooling/editor/tracked_property_table.hpp" +#include "prune/scene/scene.hpp" +#include "prune/tooling/imgui/property_table.hpp" +#include "prune/tooling/sprite_picker.hpp" + +namespace prune::tooling::editor::tracked_property_table { + + namespace { + + [[nodiscard]] bool command_object_changed( + EditorCommandType type, + const GameObject& before, + const GameObject& after + ) noexcept { + switch (type) { + case EditorCommandType::MoveObject: + case EditorCommandType::ChangeObjectPosition: + return before.transform.x != after.transform.x || before.transform.y != after.transform.y; + + case EditorCommandType::ChangeObjectSize: + return before.size.width != after.size.width || before.size.height != after.size.height; + + case EditorCommandType::ChangeObjectRenderType: + return before.render.type != after.render.type; + + case EditorCommandType::ChangeObjectColour: + return before.render.rectangle.color[0] != after.render.rectangle.color[0] || + before.render.rectangle.color[1] != after.render.rectangle.color[1] || + before.render.rectangle.color[2] != after.render.rectangle.color[2]; + + case EditorCommandType::ChangeObjectFlag: + return before.lifecycle.active != after.lifecycle.active || + before.render.visible != after.render.visible || + before.collision.solid != after.collision.solid; + + case EditorCommandType::ChangeSprite: + return before.render.sprite.sprite_key != after.render.sprite.sprite_key || + before.render.sprite.flip_x != after.render.sprite.flip_x; + + case EditorCommandType::RenameObject: + return before.identity.name != after.identity.name; + + case EditorCommandType::CreateObject: + case EditorCommandType::DeleteObject: + case EditorCommandType::MoveViewport: + return true; + } + + return true; + } + + } + + void ObjectEditTracker::capture_if_activated(const GameObject& before) + { + if (ImGui::IsItemActivated()) { + m_before = before; + } + } + + void ObjectEditTracker::commit_if_deactivated_after_edit( + Scene& scene, + EditorCommandType type, + const GameObject& after, + std::string_view detail + ) { + if (!ImGui::IsItemDeactivatedAfterEdit()) { + return; + } + + if (!m_before.has_value()) { + return; + } + + const GameObject before = m_before.value(); + m_before.reset(); + + commit_if_changed(scene, type, before, after, detail); + } + + void ObjectEditTracker::reset() noexcept + { + m_before.reset(); + } + + bool drag_float( + ObjectEditTracker& tracker, + Scene& scene, + EditorCommandType type, + GameObject& object, + const char* label, + const char* id, + float& value, + float speed, + float min, + float max, + const char* format, + std::string_view detail + ) { + const GameObject before = object; + const bool changed = ::prune::tooling::imgui::property_table::drag_float(label, id, value, speed, min, max, format); + + tracker.capture_if_activated(before); + tracker.commit_if_deactivated_after_edit(scene, type, object, detail); + + return changed; + } + + bool slider_int( + ObjectEditTracker& tracker, + Scene& scene, + EditorCommandType type, + GameObject& object, + const char* label, + const char* id, + int& value, + int min, + int max, + std::string_view detail + ) { + const GameObject before = object; + const bool changed = ::prune::tooling::imgui::property_table::slider_int(label, id, value, min, max); + + tracker.capture_if_activated(before); + tracker.commit_if_deactivated_after_edit(scene, type, object, detail); + + return changed; + } + + bool color3( + ObjectEditTracker& tracker, + Scene& scene, + EditorCommandType type, + GameObject& object, + const char* label, + const char* id, + float color[3], + std::string_view detail + ) { + const GameObject before = object; + const bool changed = ::prune::tooling::imgui::property_table::color3(label, id, color); + + tracker.capture_if_activated(before); + tracker.commit_if_deactivated_after_edit(scene, type, object, detail); + + return changed; + } + + bool checkbox( + Scene& scene, + EditorCommandType type, + GameObject& object, + const char* label, + const char* id, + bool& value, + std::string_view detail + ) { + const GameObject before = object; + + if (!::prune::tooling::imgui::property_table::checkbox(label, id, value)) { + return false; + } + + commit_if_changed(scene, type, before, object, detail); + return true; + } + + bool sprite_picker( + Scene& scene, + EditorCommandType type, + GameObject& object, + const char* label, + const char* id, + std::string& sprite_key, + std::string_view detail + ) { + const GameObject before = object; + + if (!::prune::tooling::draw_sprite_picker(label, id, sprite_key)) { + return false; + } + + commit_if_changed(scene, type, before, object, detail); + return true; + } + + bool commit_if_changed( + Scene& scene, + EditorCommandType type, + const GameObject& before, + const GameObject& after, + std::string_view detail + ) { + if (before.identity.id != after.identity.id || !command_object_changed(type, before, after)) { + return false; + } + + scene.record_editor_command(make_object_command( + type, + editor_command_type_label(type), + before, + after, + detail + )); + + return true; + } +} diff --git a/src/prune/tooling/editor/tracked_property_table.hpp b/src/prune/tooling/editor/tracked_property_table.hpp new file mode 100644 index 0000000..a395d29 --- /dev/null +++ b/src/prune/tooling/editor/tracked_property_table.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include + +#include "prune/editor/editor_command.hpp" +#include "prune/scene/game_object.hpp" + +namespace prune { + class Scene; +} + +namespace prune::tooling::editor::tracked_property_table { + + class ObjectEditTracker { + public: + void capture_if_activated(const GameObject& before); + void commit_if_deactivated_after_edit( + Scene& scene, + EditorCommandType type, + const GameObject& after, + std::string_view detail = {} + ); + void reset() noexcept; + + private: + std::optional m_before; + }; + + bool drag_float( + ObjectEditTracker& tracker, + Scene& scene, + EditorCommandType type, + GameObject& object, + const char* label, + const char* id, + float& value, + float speed = 1.0f, + float min = 0.0f, + float max = 0.0f, + const char* format = "%.3f", + std::string_view detail = {} + ); + + bool slider_int( + ObjectEditTracker& tracker, + Scene& scene, + EditorCommandType type, + GameObject& object, + const char* label, + const char* id, + int& value, + int min, + int max, + std::string_view detail = {} + ); + + bool color3( + ObjectEditTracker& tracker, + Scene& scene, + EditorCommandType type, + GameObject& object, + const char* label, + const char* id, + float color[3], + std::string_view detail = {} + ); + + bool checkbox( + Scene& scene, + EditorCommandType type, + GameObject& object, + const char* label, + const char* id, + bool& value, + std::string_view detail = {} + ); + + bool sprite_picker( + Scene& scene, + EditorCommandType type, + GameObject& object, + const char* label, + const char* id, + std::string& sprite_key, + std::string_view detail = {} + ); + + bool commit_if_changed( + Scene& scene, + EditorCommandType type, + const GameObject& before, + const GameObject& after, + std::string_view detail = {} + ); + +} diff --git a/src/prune/tooling/inspector.cpp b/src/prune/tooling/inspector.cpp index be1ab7c..1069eb1 100644 --- a/src/prune/tooling/inspector.cpp +++ b/src/prune/tooling/inspector.cpp @@ -6,7 +6,6 @@ #include "prune/tooling/inspector.hpp" #include "prune/tooling/imgui/layout.hpp" #include "prune/tooling/imgui/property_table.hpp" -#include "prune/tooling/sprite_picker.hpp" namespace prune { @@ -69,7 +68,8 @@ namespace prune { EditorCommandType::RenameObject, editor_command_type_label(EditorCommandType::RenameObject), before, - *selected + *selected, + selected->identity.name )); } @@ -92,7 +92,7 @@ namespace prune { const GameObject deleted = *selected; const GameObjectId id_to_remove = selected->identity.id; if (objects.remove_object(id_to_remove)) { - scene.record_editor_command(make_delete_object_command(deleted)); + scene.record_editor_command(make_delete_object_command(deleted, deleted.identity.name)); } tooling::imgui::property_table::end(); @@ -119,7 +119,7 @@ namespace prune { if (GameObject* created = objects.get_by_id(clone_id)) { created->identity.name = objects.make_unique_name(source_name, clone_id); - scene.record_editor_command(make_create_object_command(*created)); + scene.record_editor_command(make_create_object_command(*created, created->identity.name)); } objects.select(clone_id); @@ -202,15 +202,35 @@ namespace prune { if (tooling::imgui::property_table::begin("##transform")) { ImGui::BeginDisabled(!can_move); - GameObject before = *selected; - tooling::imgui::property_table::drag_float("X", "transform_x", selected->transform.x, 1.0f); - capture_object_edit_start(before); - commit_object_edit(scene, EditorCommandType::ChangeObjectPosition, *selected); + tooling::editor::tracked_property_table::drag_float( + m_object_edit_tracker, + scene, + EditorCommandType::ChangeObjectPosition, + *selected, + "X", + "transform_x", + selected->transform.x, + 1.0f, + 0.0f, + 0.0f, + "%.3f", + "X" + ); - before = *selected; - tooling::imgui::property_table::drag_float("Y", "transform_y", selected->transform.y, 1.0f); - capture_object_edit_start(before); - commit_object_edit(scene, EditorCommandType::ChangeObjectPosition, *selected); + tooling::editor::tracked_property_table::drag_float( + m_object_edit_tracker, + scene, + EditorCommandType::ChangeObjectPosition, + *selected, + "Y", + "transform_y", + selected->transform.y, + 1.0f, + 0.0f, + 0.0f, + "%.3f", + "Y" + ); ImGui::EndDisabled(); tooling::imgui::property_table::end(); @@ -221,15 +241,31 @@ namespace prune { if (tooling::imgui::property_table::begin("##size")) { ImGui::BeginDisabled(!can_edit); - GameObject before = *selected; - tooling::imgui::property_table::slider_int("Width", "##width", selected->size.width, k_min_object_size, k_max_object_size); - capture_object_edit_start(before); - commit_object_edit(scene, EditorCommandType::ChangeObjectSize, *selected); + tooling::editor::tracked_property_table::slider_int( + m_object_edit_tracker, + scene, + EditorCommandType::ChangeObjectSize, + *selected, + "Width", + "##width", + selected->size.width, + k_min_object_size, + k_max_object_size, + "Width" + ); - before = *selected; - tooling::imgui::property_table::slider_int("Height", "##height", selected->size.height, k_min_object_size, k_max_object_size); - capture_object_edit_start(before); - commit_object_edit(scene, EditorCommandType::ChangeObjectSize, *selected); + tooling::editor::tracked_property_table::slider_int( + m_object_edit_tracker, + scene, + EditorCommandType::ChangeObjectSize, + *selected, + "Height", + "##height", + selected->size.height, + k_min_object_size, + k_max_object_size, + "Height" + ); ImGui::EndDisabled(); tooling::imgui::property_table::end(); @@ -254,58 +290,53 @@ namespace prune { ? RenderType::Rectangle : RenderType::Sprite; - scene.record_editor_command(make_object_command( + tooling::editor::tracked_property_table::commit_if_changed( + scene, EditorCommandType::ChangeObjectRenderType, - editor_command_type_label(EditorCommandType::ChangeObjectRenderType), before, - *selected - )); + *selected, + (selected->render.type == RenderType::Rectangle) ? "Rectangle" : "Sprite" + ); } switch (selected->render.type) { case RenderType::Rectangle: { - const GameObject before = *selected; - tooling::imgui::property_table::color3( + tooling::editor::tracked_property_table::color3( + m_object_edit_tracker, + scene, + EditorCommandType::ChangeObjectColour, + *selected, "Colour", "##colour", - selected->render.rectangle.color + selected->render.rectangle.color, + "Rectangle colour" ); - capture_object_edit_start(before); - commit_object_edit(scene, EditorCommandType::ChangeObjectColour, *selected); } break; case RenderType::Sprite: { - const GameObject before = *selected; - if (tooling::draw_sprite_picker( + tooling::editor::tracked_property_table::sprite_picker( + scene, + EditorCommandType::ChangeSprite, + *selected, "Sprite", "##sprite_key", - selected->render.sprite.sprite_key - )) { - scene.record_editor_command(make_object_command( - EditorCommandType::ChangeSprite, - editor_command_type_label(EditorCommandType::ChangeSprite), - before, - *selected - )); - } + selected->render.sprite.sprite_key, + "Sprite key" + ); } { - const GameObject before = *selected; - if (tooling::imgui::property_table::checkbox( + tooling::editor::tracked_property_table::checkbox( + scene, + EditorCommandType::ChangeSprite, + *selected, "Flip X", "##sprite_flip_x", - selected->render.sprite.flip_x - )) { - scene.record_editor_command(make_object_command( - EditorCommandType::ChangeSprite, - editor_command_type_label(EditorCommandType::ChangeSprite), - before, - *selected - )); - } + selected->render.sprite.flip_x, + "Flip X" + ); } break; } @@ -360,52 +391,39 @@ namespace prune { ImGui::BeginDisabled(!can_edit); { - const GameObject before = *selected; - if (tooling::imgui::property_table::checkbox( + tooling::editor::tracked_property_table::checkbox( + scene, + EditorCommandType::ChangeObjectFlag, + *selected, "Lifecycle Active", "##active", - selected->lifecycle.active - )) - { - scene.record_editor_command(make_object_command( - EditorCommandType::ChangeObjectFlag, - editor_command_type_label(EditorCommandType::ChangeObjectFlag), - before, - *selected - )); - } + selected->lifecycle.active, + "Lifecycle Active" + ); } { - const GameObject before = *selected; - if (tooling::imgui::property_table::checkbox( + tooling::editor::tracked_property_table::checkbox( + scene, + EditorCommandType::ChangeObjectFlag, + *selected, "Render Visible", "##visible", - selected->render.visible - )) { - scene.record_editor_command(make_object_command( - EditorCommandType::ChangeObjectFlag, - editor_command_type_label(EditorCommandType::ChangeObjectFlag), - before, - *selected - )); - } + selected->render.visible, + "Render Visible" + ); } { - const GameObject before = *selected; - if (tooling::imgui::property_table::checkbox( + tooling::editor::tracked_property_table::checkbox( + scene, + EditorCommandType::ChangeObjectFlag, + *selected, "Collision Solid", "##solid", - selected->collision.solid - )) { - scene.record_editor_command(make_object_command( - EditorCommandType::ChangeObjectFlag, - editor_command_type_label(EditorCommandType::ChangeObjectFlag), - before, - *selected - )); - } + selected->collision.solid, + "Collision Solid" + ); } ImGui::EndDisabled(); @@ -440,77 +458,4 @@ namespace prune { ); } - void Inspector::capture_object_edit_start(const GameObject& before) - { - if (ImGui::IsItemActivated()) { - m_pending_object_edit = before; - } - } - - void Inspector::commit_object_edit(Scene& scene, EditorCommandType type, const GameObject& after) - { - if (!ImGui::IsItemDeactivatedAfterEdit()) { - return; - } - - if (!m_pending_object_edit.has_value()) { - return; - } - - const GameObject before = m_pending_object_edit.value(); - m_pending_object_edit.reset(); - - if (before.identity.id != after.identity.id || !command_object_changed(type, before, after)) { - return; - } - - scene.record_editor_command(make_object_command( - type, - editor_command_type_label(type), - before, - after - )); - } - - bool Inspector::command_object_changed( - EditorCommandType type, - const GameObject& before, - const GameObject& after - ) noexcept { - switch (type) { - case EditorCommandType::MoveObject: - case EditorCommandType::ChangeObjectPosition: - return before.transform.x != after.transform.x || before.transform.y != after.transform.y; - - case EditorCommandType::ChangeObjectSize: - return before.size.width != after.size.width || before.size.height != after.size.height; - - case EditorCommandType::ChangeObjectRenderType: - return before.render.type != after.render.type; - - case EditorCommandType::ChangeObjectColour: - return before.render.rectangle.color[0] != after.render.rectangle.color[0] || - before.render.rectangle.color[1] != after.render.rectangle.color[1] || - before.render.rectangle.color[2] != after.render.rectangle.color[2]; - - case EditorCommandType::ChangeObjectFlag: - return before.lifecycle.active != after.lifecycle.active || - before.render.visible != after.render.visible; - - case EditorCommandType::ChangeSprite: - return before.render.sprite.sprite_key != after.render.sprite.sprite_key || - before.render.sprite.flip_x != after.render.sprite.flip_x; - - case EditorCommandType::RenameObject: - return before.identity.name != after.identity.name; - - case EditorCommandType::CreateObject: - case EditorCommandType::DeleteObject: - case EditorCommandType::MoveViewport: - return true; - } - - return true; - } - } diff --git a/src/prune/tooling/inspector.hpp b/src/prune/tooling/inspector.hpp index fbd3004..f09b9ad 100644 --- a/src/prune/tooling/inspector.hpp +++ b/src/prune/tooling/inspector.hpp @@ -7,6 +7,7 @@ #include "prune/scene/game_object.hpp" #include "prune/scene/game_object_manager.hpp" #include "prune/scene/scene.hpp" +#include "prune/tooling/editor/tracked_property_table.hpp" namespace prune { @@ -26,17 +27,8 @@ namespace prune { void draw_flags(Scene& scene, GameObjectManager& objects); void sync_rename_buffer(const GameObject* selected); - void capture_object_edit_start(const GameObject& before); - void commit_object_edit(Scene& scene, EditorCommandType type, const GameObject& after); - - [[nodiscard]] static bool command_object_changed( - EditorCommandType type, - const GameObject& before, - const GameObject& after - ) noexcept; - std::optional m_rename_target_id; - std::optional m_pending_object_edit; + tooling::editor::tracked_property_table::ObjectEditTracker m_object_edit_tracker; std::array m_rename_buffer{}; }; } \ No newline at end of file