Skip to content

Commit

Permalink
Add layer creation command
Browse files Browse the repository at this point in the history
  • Loading branch information
albin-johansson committed Aug 18, 2024
1 parent ef685ac commit a337144
Show file tree
Hide file tree
Showing 8 changed files with 400 additions and 0 deletions.
2 changes: 2 additions & 0 deletions source/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ add_library(tactile::core ALIAS tactile-core)

target_sources(tactile-core
PRIVATE
"src/tactile/core/cmd/layer/create_layer_command.cpp"
"src/tactile/core/cmd/meta/create_property_command.cpp"
"src/tactile/core/cmd/meta/remove_property_command.cpp"
"src/tactile/core/cmd/meta/rename_property_command.cpp"
Expand Down Expand Up @@ -124,6 +125,7 @@ target_sources(tactile-core
"src/tactile/core/tactile_app.cpp"

PUBLIC FILE_SET "HEADERS" BASE_DIRS "inc" FILES
"inc/tactile/core/cmd/layer/create_layer_command.hpp"
"inc/tactile/core/cmd/meta/create_property_command.hpp"
"inc/tactile/core/cmd/meta/remove_property_command.hpp"
"inc/tactile/core/cmd/meta/rename_property_command.hpp"
Expand Down
45 changes: 45 additions & 0 deletions source/core/inc/tactile/core/cmd/layer/create_layer_command.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0)

#pragma once

#include "tactile/base/layer/layer_type.hpp"
#include "tactile/base/prelude.hpp"
#include "tactile/core/cmd/command.hpp"
#include "tactile/core/entity/entity.hpp"

namespace tactile {

class MapDocument;

/**
* A command for creating new map layers.
*/
class CreateLayerCommand final : public ICommand
{
public:
TACTILE_DELETE_COPY(CreateLayerCommand);
TACTILE_DEFAULT_MOVE(CreateLayerCommand);

/**
* Creates a command.
*
* \param document The parent map document.
* \param type The type of the new layer.
*/
CreateLayerCommand(MapDocument* document, LayerType type);

~CreateLayerCommand() noexcept override;

void undo() override;

void redo() override;

private:
MapDocument* mDocument;
LayerType mType;
EntityID mParentLayerId;
EntityID mLayerId;
bool mWasLayerAdded;
};

} // namespace tactile
53 changes: 53 additions & 0 deletions source/core/inc/tactile/core/map/map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "tactile/base/id.hpp"
#include "tactile/base/int.hpp"
#include "tactile/base/io/compress/compression_format.hpp"
#include "tactile/base/layer/layer_type.hpp"
#include "tactile/base/layer/tile_encoding.hpp"
#include "tactile/base/layer/tile_orientation.hpp"
#include "tactile/base/numeric/vec.hpp"
Expand Down Expand Up @@ -195,5 +196,57 @@ auto add_tileset_to_map(Registry& registry,
*/
void remove_tileset_from_map(Registry& registry, EntityID map_id, EntityID tileset_id);

/**
* Creates a layer and adds it to a map.
*
* \pre The map identifier must be valid.
*
* \details
* This function automatically makes the new layer the active layer in the map.
*
* \param registry The associated registry.
* \param map_id The target map identifier.
* \param type The type of the new layer.
*
* \return
* A layer entity identifier if successful; an error code otherwise.
*/
[[nodiscard]]
auto add_layer_to_map(Registry& registry, EntityID map_id, LayerType type) -> Result<EntityID>;

/**
* Appends an existing layer to a map.
*
* \details
* If the currently active layer is a group layer, then the layer is appended to that group.
* Otherwise, the layer is appended to the root layer. The new layer is subsequently made
* active.
*
* \pre The map identifier must be valid.
* \pre The layer identifier must be valid.
*
* \param registry The associated registry.
* \param map_id The host map identifier.
* \param layer_id The identifier of the layer that will be added.
*/
void append_layer_to_map(Registry& registry, EntityID map_id, EntityID layer_id);

/**
* Removes a layer from a map, but does not destroy it.
*
* \pre The map identifier must be valid.
* \pre The layer identifier must be valid.
*
* \param registry The associated registry.
* \param map_id The host map identifier.
* \param layer_id The target layer identifier.
*
* \return
* Nothing if the layer was removed; an error code otherwise.
*/
[[nodiscard]]
auto remove_layer_from_map(Registry& registry,
EntityID map_id,
EntityID layer_id) -> Result<void>;

} // namespace tactile
84 changes: 84 additions & 0 deletions source/core/src/tactile/core/cmd/layer/create_layer_command.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0)

#include "tactile/core/cmd/layer/create_layer_command.hpp"

#include "tactile/core/debug/validation.hpp"
#include "tactile/core/document/document_info.hpp"
#include "tactile/core/document/map_document.hpp"
#include "tactile/core/entity/registry.hpp"
#include "tactile/core/layer/group_layer.hpp"
#include "tactile/core/layer/layer.hpp"
#include "tactile/core/layer/object_layer.hpp"
#include "tactile/core/layer/tile_layer.hpp"
#include "tactile/core/log/logger.hpp"
#include "tactile/core/map/map.hpp"

namespace tactile {

CreateLayerCommand::CreateLayerCommand(MapDocument* document, const LayerType type)
: mDocument {require_not_null(document, "null document")},
mType {type},
mParentLayerId {kInvalidEntity},
mLayerId {kInvalidEntity},
mWasLayerAdded {false}
{}

CreateLayerCommand::~CreateLayerCommand() noexcept
{
try {
if (!mWasLayerAdded && mLayerId != kInvalidEntity) {
auto& registry = mDocument->get_registry();
switch (mType) {
case LayerType::kTileLayer: destroy_tile_layer(registry, mLayerId); break;
case LayerType::kObjectLayer: destroy_object_layer(registry, mLayerId); break;
case LayerType::kGroupLayer: destroy_group_layer(registry, mLayerId); break;
}
}
}
catch (const std::exception& error) {
TACTILE_LOG_ERROR("CreateLayerCommand threw unexpected exception: {}", error.what());
}
}

void CreateLayerCommand::undo()
{
TACTILE_LOG_DEBUG("Removing layer {}", entity_to_string(mLayerId));

auto& registry = mDocument->get_registry();

const auto& document_info = registry.get<CDocumentInfo>();
const auto map_id = document_info.root;

remove_layer_from_map(registry, map_id, mLayerId).value();

mWasLayerAdded = false;
}

void CreateLayerCommand::redo()
{
TACTILE_LOG_DEBUG("Creating layer...");

auto& registry = mDocument->get_registry();

const auto& document_info = registry.get<CDocumentInfo>();
const auto map_id = document_info.root;

auto& map = registry.get<CMap>(map_id);

if (mLayerId == kInvalidEntity) {
mParentLayerId = map.active_layer;
mLayerId = add_layer_to_map(registry, map_id, mType).value();
}
else {
const auto old_active_layer = map.active_layer;
map.active_layer = mParentLayerId;

append_layer_to_map(registry, map_id, mLayerId);

map.active_layer = old_active_layer;
}

mWasLayerAdded = true;
}

} // namespace tactile
68 changes: 68 additions & 0 deletions source/core/src/tactile/core/map/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

#include "tactile/core/map/map.hpp"

#include <system_error> // make_error_code, errc

#include "tactile/base/io/save/ir.hpp"
#include "tactile/base/numeric/saturate_cast.hpp"
#include "tactile/core/debug/assert.hpp"
#include "tactile/core/debug/generic_error.hpp"
#include "tactile/core/entity/registry.hpp"
#include "tactile/core/layer/group_layer.hpp"
#include "tactile/core/layer/layer.hpp"
#include "tactile/core/layer/layer_common.hpp"
#include "tactile/core/layer/object_layer.hpp"
#include "tactile/core/layer/tile_layer.hpp"
Expand Down Expand Up @@ -209,4 +212,69 @@ void remove_tileset_from_map(Registry& registry,
}
}

auto add_layer_to_map(Registry& registry,
const EntityID map_id,
const LayerType type) -> Result<EntityID>
{
TACTILE_ASSERT(is_map(registry, map_id));
const auto& map = registry.get<CMap>(map_id);

EntityID layer_entity {};
switch (type) {
case LayerType::kTileLayer: layer_entity = make_tile_layer(registry, map.extent); break;
case LayerType::kObjectLayer: layer_entity = make_object_layer(registry); break;
case LayerType::kGroupLayer: layer_entity = make_group_layer(registry); break;
default: return unexpected(std::make_error_code(std::errc::invalid_argument));
}

append_layer_to_map(registry, map_id, layer_entity);

return layer_entity;
}

void append_layer_to_map(Registry& registry, const EntityID map_id, const EntityID layer_id)
{
TACTILE_ASSERT(is_map(registry, map_id));
TACTILE_ASSERT(is_layer(registry, layer_id));

auto& map = registry.get<CMap>(map_id);

if (is_group_layer(registry, map.active_layer)) {
auto& active_group = registry.get<CGroupLayer>(map.active_layer);
active_group.layers.push_back(layer_id);
}
else {
auto& root = registry.get<CGroupLayer>(map.root_layer);
root.layers.push_back(layer_id);
}

if (layer_id != kInvalidEntity) {
map.active_layer = layer_id;
}
}

auto remove_layer_from_map(Registry& registry,
const EntityID map_id,
const EntityID layer_id) -> Result<void>
{
TACTILE_ASSERT(is_map(registry, map_id));
TACTILE_ASSERT(is_layer(registry, layer_id));

auto& map = registry.get<CMap>(map_id);

const auto parent_layer_id = find_parent_layer(registry, map.root_layer, layer_id);
if (parent_layer_id == kInvalidEntity) {
return unexpected(std::make_error_code(std::errc::invalid_argument));
}

auto& parent_layer = registry.get<CGroupLayer>(parent_layer_id);
std::erase(parent_layer.layers, layer_id);

if (map.active_layer == layer_id) {
map.active_layer = kInvalidEntity;
}

return kOK;
}

} // namespace tactile
1 change: 1 addition & 0 deletions source/core/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ add_executable(tactile-core-test)

target_sources(tactile-core-test
PRIVATE
"src/cmd/layer/create_layer_command_test.cpp"
"src/cmd/meta/create_property_command_test.cpp"
"src/cmd/meta/remove_property_command_test.cpp"
"src/cmd/meta/rename_property_command_test.cpp"
Expand Down
Loading

0 comments on commit a337144

Please sign in to comment.