diff --git a/src/jasper/modules/world/include/jasper/world/worldstatic.hpp b/src/jasper/modules/world/include/jasper/world/worldstatic.hpp index 53b30f6..8ac6e66 100644 --- a/src/jasper/modules/world/include/jasper/world/worldstatic.hpp +++ b/src/jasper/modules/world/include/jasper/world/worldstatic.hpp @@ -114,7 +114,7 @@ auto &tile( ox::CommonRefWith auto&ws, size_t lyr, ox::IntegerRange_c<500> auto col, - ox::Integer_c auto row) noexcept { + ox::IntegerRange_c<500> auto row) noexcept { auto const idx = static_cast(row) * static_cast(ws.columns) + static_cast(col); return ws.map[lyr].tiles[idx]; } diff --git a/src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt b/src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt index 8367489..79cf5ef 100644 --- a/src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt +++ b/src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources( commands/modifytiles.cpp commands/editsize.cpp objectexplorer.cpp + tileclipboard.cpp worldeditor-imgui.cpp worldeditorview.cpp ) diff --git a/src/jasper/modules/world/src/studio/worldeditor/commands/modifytiles.hpp b/src/jasper/modules/world/src/studio/worldeditor/commands/modifytiles.hpp index d48db8b..2cba944 100644 --- a/src/jasper/modules/world/src/studio/worldeditor/commands/modifytiles.hpp +++ b/src/jasper/modules/world/src/studio/worldeditor/commands/modifytiles.hpp @@ -37,4 +37,6 @@ class ModifyTilesCommand: public studio::UndoCommand { void swap() noexcept; }; +using TileUpdate = ModifyTilesCommand::Mod; + } diff --git a/src/jasper/modules/world/src/studio/worldeditor/tileclipboard.cpp b/src/jasper/modules/world/src/studio/worldeditor/tileclipboard.cpp new file mode 100644 index 0000000..8517f80 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/tileclipboard.cpp @@ -0,0 +1,23 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "tileclipboard.hpp" + +namespace jasper::world { + +TileClipboard::TileClipboard(ox::Size selSz) noexcept: m_selSz(selSz) {} + +ox::Size TileClipboard::selectionSize() const noexcept { + return m_selSz; +} + +void TileClipboard::addTile(const ModifyTilesCommand::Mod&mod) noexcept { + m_tileMods.emplace_back(mod); +} + +ox::SpanView TileClipboard::tiles() const noexcept { + return m_tileMods; +} + +} \ No newline at end of file diff --git a/src/jasper/modules/world/src/studio/worldeditor/tileclipboard.hpp b/src/jasper/modules/world/src/studio/worldeditor/tileclipboard.hpp new file mode 100644 index 0000000..6133e93 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/tileclipboard.hpp @@ -0,0 +1,35 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include "commands/modifytiles.hpp" + +namespace jasper::world { + +class TileClipboard : public turbine::ClipboardObject { + public: + static constexpr auto TypeName = "net.drinkingtea.jasper.world.studio.TileClipboard"; + static constexpr auto TypeVersion = 1; + + + protected: + ox::Size m_selSz; + ox::Vector m_tileMods; + + public: + explicit TileClipboard(ox::Size selSz) noexcept; + + [[nodiscard]] + ox::Size selectionSize() const noexcept; + + void addTile(ModifyTilesCommand::Mod const&mod) noexcept; + + [[nodiscard]] + ox::SpanView tiles() const noexcept; +}; + +} \ No newline at end of file diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp index bce1302..65a7bc1 100644 --- a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp @@ -14,6 +14,7 @@ #include "commands/addrmobjectset.hpp" #include "commands/editsize.hpp" #include "commands/modifytiles.hpp" +#include "tileclipboard.hpp" #include "worldeditor-imgui.hpp" @@ -113,6 +114,69 @@ void WorldEditorImGui::onActivated() noexcept { oxLogError(m_view.setupWorld()); } +void WorldEditorImGui::copy() noexcept { + if (m_selection) { + auto mods = ox::make_unique(m_selection->size()); + studio::iterateSelection(*m_selection, [this, &mods](int32_t x, int32_t y) { + auto &t = tile(m_doc, m_activeLayer, x, y); + x -= m_selection->a.x; + y -= m_selection->a.y; + mods->addTile({ + .layer = m_activeLayer, + .tileAddr = {x, y}, + .objId = t.obj.worldObjectId, + .setId = t.obj.worldObjectSetId, + }); + }); + setClipboardObject(m_sctx.tctx, std::move(mods)); + } +} + +void WorldEditorImGui::cut() noexcept { + if (m_selection) { + copy(); + ox::Vector mods; + studio::iterateSelection(*m_selection, [this, &mods](int32_t x, int32_t y) { + mods.emplace_back(ModifyTilesCommand::Mod{ + .layer = m_activeLayer, + .tileAddr = {x, y}, + .objId = 0, + .setId = 0, + }); + }); + std::ignore = pushCommand( + m_doc, + m_worldStatic, + m_objCache, + std::move(mods)); + } +} + +void WorldEditorImGui::paste() { + if (m_selection) { + auto const cb = getClipboardObject(m_sctx.tctx).unwrapThrow(); + auto const selSz = cb->selectionSize(); + auto &sel = *m_selection; + sel.b.x = ox::min(sel.a.x + selSz.width, m_doc.columns - 1); + sel.b.y = ox::min(sel.a.y + selSz.height, m_doc.rows - 1); + m_view.setSelection(sel); + ox::Vector mods; + for (auto const&m : cb->tiles()) { + mods.emplace_back(ModifyTilesCommand::Mod{ + .layer = m_activeLayer, + .tileAddr = m.tileAddr + sel.a, + .objId = m.objId, + .setId = m.setId, + }); + } + std::ignore = pushCommand( + m_doc, + m_worldStatic, + m_objCache, + std::move(mods)); + } +} + ox::Error WorldEditorImGui::saveItem() noexcept { return m_sctx.project->writeObj(itemPath(), m_doc, ox::ClawFormat::Organic); } @@ -228,10 +292,22 @@ void WorldEditorImGui::drawWorldView() noexcept { std::ignore = ig::dragDropTarget([this, fbPaneScale] { return handleDrop(fbPaneScale); }); - handleSelection(static_cast(ox::Vec2(paneSize)), fbPaneScale); + handleMouseSelection(static_cast(ox::Vec2(paneSize)), fbPaneScale); + auto const&io = ImGui::GetIO(); + if (io.KeyCtrl) { + if (io.KeysDown[ImGuiKey_G]) { + clearSelection(); + } else if (io.KeysDown[ImGuiKey_A]) { + m_selection.emplace(ox::Point{0, 0}, ox::Point{m_doc.columns - 1, m_doc.rows - 1}); + m_view.setSelection(*m_selection); + setCopyEnabled(true); + setCutEnabled(true); + setPasteEnabled(true); + } + } } -void WorldEditorImGui::handleSelection(ox::Size const&paneSz, float fbPaneScale) noexcept { +void WorldEditorImGui::handleMouseSelection(ox::Size const&paneSz, float fbPaneScale) noexcept { auto const&io = ImGui::GetIO(); if (io.MouseDown[0]) { auto const fbPos = world::fbPos(ox::Vec2{io.MousePos}); @@ -246,14 +322,9 @@ void WorldEditorImGui::handleSelection(ox::Size const&paneSz, float fbPaneScale) } else if (io.MouseReleased[0] && m_selTracker.selectionOngoing()) { m_selTracker.finishSelection(); m_selection.emplace(m_selTracker.selection()); - } - if (io.KeyCtrl) { - if (io.KeysDown[ImGuiKey_G]) { - clearSelection(); - } else if (io.KeysDown[ImGuiKey_A]) { - m_selection.emplace(ox::Point{0, 0}, ox::Point{m_doc.columns - 1, m_doc.rows - 1}); - m_view.setSelection(*m_selection); - } + setCopyEnabled(true); + setCutEnabled(true); + setPasteEnabled(true); } } @@ -309,8 +380,9 @@ void WorldEditorImGui::rmObjSet() noexcept { } ox::Error WorldEditorImGui::handleDepUpdate(ox::StringView, ox::UUID const&uuid) noexcept { - auto const objSetMatches = [&uuid](ObjectSetEntry const&set) { - auto const [setUuid, err] = keel::uuidUrlToUuid(set.path); + auto &kctx = keelCtx(m_sctx.tctx); + auto const objSetMatches = [&uuid, &kctx](ObjectSetEntry const&set) { + auto const [setUuid, err] = keel::getUuid(kctx, set.path); return !err && setUuid == uuid; }; auto const depMatches = [&uuid](ox::SmallMap::Pair const&set) { @@ -355,6 +427,9 @@ bool WorldEditorImGui::tileSelected(ox::Point const&pt) const noexcept { } void WorldEditorImGui::clearSelection() noexcept { + setCopyEnabled(false); + setCutEnabled(false); + setPasteEnabled(false); m_selection.reset(); m_view.clearSelection(); } diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp index 0710034..7d5b8f0 100644 --- a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp @@ -58,6 +58,12 @@ class WorldEditorImGui: public studio::Editor { void onActivated() noexcept override; protected: + void copy() noexcept override; + + void cut() noexcept override; + + void paste() override; + ox::Error saveItem() noexcept final; private: @@ -73,7 +79,7 @@ class WorldEditorImGui: public studio::Editor { void drawWorldView() noexcept; - void handleSelection(ox::Size const&paneSz, float fbPaneScale) noexcept; + void handleMouseSelection(ox::Size const&paneSz, float fbPaneScale) noexcept; ox::Error handleDrop(float fbPaneScale) noexcept;