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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_executable(Prune
src/prune/core/input.cpp

src/prune/editor/editor_command.cpp
src/prune/editor/editor_actions.cpp

src/prune/scene/game_object.cpp
src/prune/scene/game_object_manager.cpp
Expand Down
2 changes: 0 additions & 2 deletions NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,8 @@ Initial scope:
## Medium term, in no particular order

- Rotate tool
- Delete action and command
- Background image support
- Simplified asset management
- Copy / Paste command
- Text rendering
- Behaviour toggles for authored objects
- Polished sample scenes
Expand Down
115 changes: 115 additions & 0 deletions src/prune/editor/editor_actions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "prune/editor/editor_actions.hpp"

#include <algorithm>
#include <string>
#include <vector>

#include "prune/editor/editor_command.hpp"

namespace prune {

namespace {

[[nodiscard]] float clone_offset(const GridOptions& grid_options) noexcept
{
return static_cast<float>(
grid_options.snap_to_grid
? std::max(1, grid_options.grid_size)
: std::max(1, grid_options.nudge_step)
);
}

}

SelectedObjectActionAvailability selected_object_action_availability(Scene& scene)
{
GameObjectManager& objects = scene.get_object_manager();

SelectedObjectActionAvailability availability{};
if (GameObject* active = objects.selected_object()) {
availability.can_clone_active =
scene.object_is_editable(*active) &&
active->editor.cloneable;
}

for (const GameObjectId id : objects.selected_ids()) {
const GameObject* object = objects.get_by_id(id);
if (!object || !scene.object_is_editable(*object) || !object->editor.deletable) {
continue;
}

++availability.deletable_count;
}

availability.can_delete_selection = availability.deletable_count > 0;
return availability;
}

bool clone_active_selected_object(Scene& scene, const GridOptions& grid_options)
{
GameObjectManager& objects = scene.get_object_manager();
const GameObject* selected = objects.selected_object();

if (!selected || !scene.object_is_editable(*selected) || !selected->editor.cloneable) {
return false;
}

GameObject clone = *selected;
clone.identity.name = objects.make_unique_name(clone.identity.name + " Copy", k_invalid_game_object_id);

const float offset = clone_offset(grid_options);
clone.transform.x += offset;
clone.transform.y += offset;

const GameObjectId clone_id = objects.create_object(clone);
GameObject* created = objects.get_by_id(clone_id);
if (!created) {
return false;
}

objects.select(clone_id);
scene.record_editor_command(make_create_object_command(*created, created->identity.name));
return true;
}

bool delete_selected_objects(Scene& scene)
{
GameObjectManager& objects = scene.get_object_manager();
std::vector<GameObject> deleted_objects;
deleted_objects.reserve(objects.selected_count());

for (const GameObjectId id : objects.selected_ids()) {
const GameObject* object = objects.get_by_id(id);
if (!object || !scene.object_is_editable(*object) || !object->editor.deletable) {
continue;
}

deleted_objects.push_back(*object);
}

if (deleted_objects.empty()) {
return false;
}

for (const GameObject& object : deleted_objects) {
objects.remove_object(object.identity.id);
}

if (deleted_objects.size() > 1) {
objects.clear_selection();
}

if (deleted_objects.size() == 1) {
scene.record_editor_command(make_delete_object_command(deleted_objects.front(), deleted_objects.front().identity.name));
}
else {
scene.record_editor_command(make_multi_delete_object_command(
deleted_objects,
std::to_string(deleted_objects.size()) + " objects"
));
}

return true;
}

}
19 changes: 19 additions & 0 deletions src/prune/editor/editor_actions.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <cstddef>

#include "prune/scene/scene.hpp"

namespace prune {

struct SelectedObjectActionAvailability {
bool can_clone_active = false;
bool can_delete_selection = false;
std::size_t deletable_count = 0;
};

[[nodiscard]] SelectedObjectActionAvailability selected_object_action_availability(Scene& scene);
bool clone_active_selected_object(Scene& scene, const GridOptions& grid_options);
bool delete_selected_objects(Scene& scene);

}
62 changes: 4 additions & 58 deletions src/prune/scene/scene_interaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <SDL2/SDL.h>

#include "prune/editor/editor_actions.hpp"
#include "prune/editor/tools/transform_gizmo.hpp"
#include "prune/scene/scene.hpp"
#include "prune/scene/scene_interaction.hpp"
Expand Down Expand Up @@ -407,67 +408,12 @@ namespace prune {
input.is_key_down(SDL_SCANCODE_RCTRL);

if (input.was_key_pressed(SDL_SCANCODE_DELETE) || input.was_key_pressed(SDL_SCANCODE_BACKSPACE)) {
std::vector<GameObject> deleted_objects;
deleted_objects.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_editable(*object) || !object->editor.deletable) {
continue;
}

deleted_objects.push_back(*object);
}

if (deleted_objects.empty()) {
return;
}

for (const GameObject& object : deleted_objects) {
state.objects.remove_object(object.identity.id);
}

if (deleted_objects.size() > 1) {
state.objects.clear_selection();
}

if (deleted_objects.size() == 1) {
scene.record_editor_command(make_delete_object_command(deleted_objects.front(), deleted_objects.front().identity.name));
}
else {
scene.record_editor_command(make_multi_delete_object_command(
deleted_objects,
std::to_string(deleted_objects.size()) + " objects"
));
}
delete_selected_objects(scene);
return;
}

GameObject* selected = state.objects.selected_object();
if (!selected || !scene.object_is_editable(*selected)) {
return;
}

if (!selected->editor.cloneable || !ctrl_down || !input.was_key_pressed(SDL_SCANCODE_D)) {
return;
}

GameObject duplicate = *selected;
duplicate.identity.name = state.objects.make_unique_name(duplicate.identity.name + " Copy", k_invalid_game_object_id);

const float offset = static_cast<float>(
grid_options.snap_to_grid
? std::max(1, grid_options.grid_size)
: std::max(1, grid_options.nudge_step)
);

duplicate.transform.x += offset;
duplicate.transform.y += offset;

const GameObjectId duplicate_id = state.objects.create_object(duplicate);
if (GameObject* created = state.objects.get_by_id(duplicate_id)) {
state.objects.select(duplicate_id);
scene.record_editor_command(make_create_object_command(*created, "Duplicate object"));
if (ctrl_down && input.was_key_pressed(SDL_SCANCODE_D)) {
clone_active_selected_object(scene, grid_options);
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/prune/tooling/controls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,23 @@ namespace prune {

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

tooling::imgui::layout::text_wrapped("Artillery");
tooling::imgui::layout::text_wrapped("A/D keys control the angle left and right");
tooling::imgui::layout::text_wrapped("W/S keys control the power up and down");
tooling::imgui::layout::text_wrapped("Space fires a projectile");

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

tooling::imgui::layout::text_wrapped("Objects");
tooling::imgui::layout::text_wrapped("CTRL+Arrow keys move the selected object or selected objects");
tooling::imgui::layout::text_wrapped("Hold Shift for larger movements");
tooling::imgui::layout::text_wrapped("The Selected tool palette actions clone or delete the selected editable object");
tooling::imgui::layout::text_wrapped("Delete or Backspace deletes selected deletable objects");
tooling::imgui::layout::text_wrapped("CTRL+D duplicates the active selected object");
tooling::imgui::layout::text_wrapped("CTRL+D clones the active selected object");
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("Use the Select/Move tool buttons in the top right of the viewport to change tool state");
tooling::imgui::layout::text_wrapped("Use the Select/Move/Scale tool buttons in the top right of the viewport to change tool mode");
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 on an object handle to move the object");

Expand Down
59 changes: 3 additions & 56 deletions src/prune/tooling/inspector.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#include <algorithm>
#include <string_view>

#include "imgui.h"
Expand All @@ -11,22 +10,19 @@ namespace prune {

void Inspector::draw(
Scene& scene,
GridOptions& grid_options,
const Camera& camera
) {
GameObjectManager& objects = scene.get_object_manager();

draw_selected(scene, grid_options);
draw_selected(scene);
draw_scene_meaning(scene);
draw_properties(scene, objects);
draw_computed(objects, camera);
draw_flags(scene, objects);
}

void Inspector::draw_selected(
Scene& scene,
GridOptions& grid_options
) {
void Inspector::draw_selected(Scene& scene)
{
GameObjectManager& objects = scene.get_object_manager();
GameObject* selected = objects.selected_object();

Expand All @@ -36,8 +32,6 @@ namespace prune {

const bool can_edit = scene.object_is_editable(*selected);
const bool can_rename = selected->editor.renameable && can_edit;
const bool can_delete = selected->editor.deletable && can_edit;
const bool can_clone = selected->editor.cloneable && can_edit;

if (tooling::imgui::layout::collapsing_header("Selected")) {
if (tooling::imgui::property_table::begin("Selected")) {
Expand Down Expand Up @@ -86,53 +80,6 @@ namespace prune {
}
}

if (can_delete || can_clone) {

tooling::imgui::layout::separator();

tooling::imgui::property_table::begin_row("Actions");

if (can_delete && tooling::imgui::property_table::button_raw("Delete")) {
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, deleted.identity.name));
}

tooling::imgui::property_table::end();
return;
}

if (can_delete && can_clone) {
ImGui::SameLine();
}

if (can_clone && tooling::imgui::property_table::button_raw("Clone")) {
const std::string source_name = selected->identity.name;

GameObject clone = *selected;

const float step = grid_options.snap_to_grid
? static_cast<float>(std::max(1, grid_options.grid_size))
: static_cast<float>(k_default_object_size);

clone.transform.x += step;
clone.transform.y += step;

const GameObjectId clone_id = objects.create_object(clone);

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, created->identity.name));
}

objects.select(clone_id);

tooling::imgui::property_table::end();
return;
}
}

tooling::imgui::property_table::end();
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/prune/tooling/inspector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ namespace prune {
public:
void draw(
Scene& scene,
GridOptions& grid_options,
const Camera& camera
);

private:
void draw_selected(Scene& scene, GridOptions& grid_options);
void draw_selected(Scene& scene);
void draw_scene_meaning(Scene& scene);
void draw_properties(Scene& scene, GameObjectManager& objects);
void draw_computed(GameObjectManager& objects, const Camera& camera);
Expand Down
Loading
Loading