[jasper/world/studio] Add copy/cut/paste to WorldEditor
All checks were successful
Build / build (push) Successful in 3m18s

This commit is contained in:
Gary Talent 2024-05-28 01:45:59 -05:00
parent 8ae716e09c
commit db5cff5e8c
7 changed files with 156 additions and 14 deletions

View File

@ -114,7 +114,7 @@ auto &tile(
ox::CommonRefWith<WorldStatic> 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<size_t>(row) * static_cast<size_t>(ws.columns) + static_cast<size_t>(col);
return ws.map[lyr].tiles[idx];
}

View File

@ -4,6 +4,7 @@ target_sources(
commands/modifytiles.cpp
commands/editsize.cpp
objectexplorer.cpp
tileclipboard.cpp
worldeditor-imgui.cpp
worldeditorview.cpp
)

View File

@ -37,4 +37,6 @@ class ModifyTilesCommand: public studio::UndoCommand {
void swap() noexcept;
};
using TileUpdate = ModifyTilesCommand::Mod;
}

View File

@ -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<ModifyTilesCommand::Mod> TileClipboard::tiles() const noexcept {
return m_tileMods;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <turbine/turbine.hpp>
#include "commands/modifytiles.hpp"
namespace jasper::world {
class TileClipboard : public turbine::ClipboardObject<TileClipboard> {
public:
static constexpr auto TypeName = "net.drinkingtea.jasper.world.studio.TileClipboard";
static constexpr auto TypeVersion = 1;
protected:
ox::Size m_selSz;
ox::Vector<ModifyTilesCommand::Mod> 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<ModifyTilesCommand::Mod> tiles() const noexcept;
};
}

View File

@ -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<TileClipboard>(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<ModifyTilesCommand::Mod> 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<ModifyTilesCommand>(
m_doc,
m_worldStatic,
m_objCache,
std::move(mods));
}
}
void WorldEditorImGui::paste() {
if (m_selection) {
auto const cb = getClipboardObject<TileClipboard>(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<ModifyTilesCommand::Mod> 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<ModifyTilesCommand>(
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::Size>(ox::Vec2(paneSize)), fbPaneScale);
handleMouseSelection(static_cast<ox::Size>(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<ox::UUID, bool>::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();
}

View File

@ -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;