From 74effd361135b5ad8192081896596fa718b69aa7 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 22 May 2022 16:15:58 -0500 Subject: [PATCH] [nostalgia/core/studio] Add insert/delete tile actions and make double click on subsheet open SubsheetEditor --- .../core/studio/tilesheeteditor-imgui.cpp | 63 +++++--- .../core/studio/tilesheeteditor-imgui.hpp | 5 + .../core/studio/tilesheeteditormodel.cpp | 153 ++++++++++++++++-- .../core/studio/tilesheeteditormodel.hpp | 4 + .../core/studio/tilesheeteditorview.cpp | 14 ++ .../core/studio/tilesheeteditorview.hpp | 4 + 6 files changed, 210 insertions(+), 33 deletions(-) diff --git a/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp b/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp index 20d8e2bb..17a96819 100644 --- a/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp @@ -61,10 +61,10 @@ void TileSheetEditorImGui::keyStateChanged(core::Key key, bool down) { } const auto colorCnt = model()->pal().colors.size(); if (key >= core::Key::Num_1 && key <= core::Key::Num_0 + colorCnt) { - auto idx = ox::min(key - core::Key::Num_1, colorCnt - 1); + auto idx = ox::min(static_cast(key - core::Key::Num_1), colorCnt - 1); m_tileSheetEditor.setPalIdx(idx); } else if (key == core::Key::Num_0 && colorCnt >= 10) { - auto idx = ox::min(key - core::Key::Num_1 + 9, colorCnt - 1); + auto idx = ox::min(static_cast(key - core::Key::Num_1 + 9), colorCnt - 1); m_tileSheetEditor.setPalIdx(idx); } } @@ -126,12 +126,7 @@ void TileSheetEditorImGui::draw(core::Context*) noexcept { } ImGui::SameLine(); if (ImGui::Button("Edit", ImVec2(36, btnHeight))) { - const auto sheet = model()->activeSubSheet(); - if (sheet->subsheets.size()) { - m_subsheetEditor.show(sheet->name, -1, -1); - } else { - m_subsheetEditor.show(sheet->name, sheet->columns, sheet->rows); - } + showSubsheetEditor(); } TileSheet::SubSheetIdx path; static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; @@ -168,6 +163,9 @@ void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, T if (ImGui::IsItemClicked()) { model()->setActiveSubsheet(*path); } + if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) { + showSubsheetEditor(); + } if (subsheet->subsheets.size()) { ImGui::TableNextColumn(); ImGui::Text("--"); @@ -196,11 +194,28 @@ studio::UndoStack *TileSheetEditorImGui::undoStack() noexcept { return model()->undoStack(); } +[[nodiscard]] +geo::Vec2 TileSheetEditorImGui::clickPos(const ImVec2 &winPos, geo::Vec2 clickPos) noexcept { + clickPos.x -= winPos.x + 10; + clickPos.y -= winPos.y + 10; + return clickPos; +} + ox::Error TileSheetEditorImGui::saveItem() noexcept { return model()->saveFile(); } +void TileSheetEditorImGui::showSubsheetEditor() noexcept { + const auto sheet = model()->activeSubSheet(); + if (sheet->subsheets.size()) { + m_subsheetEditor.show(sheet->name, -1, -1); + } else { + m_subsheetEditor.show(sheet->name, sheet->columns, sheet->rows); + } +} + void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept { + const auto winPos = ImGui::GetWindowPos(); const auto fbSizei = geo::Size(static_cast(fbSize.x), static_cast(fbSize.y)); if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) { m_framebuffer = glutils::generateFrameBuffer(fbSizei.width, fbSizei.height); @@ -214,7 +229,11 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept { glViewport(0, 0, fbSizei.width, fbSizei.height); m_tileSheetEditor.draw(); glBindFramebuffer(GL_FRAMEBUFFER, 0); - ImGui::Image(reinterpret_cast(m_framebuffer.color.id), static_cast(fbSize), ImVec2(0, 1), ImVec2(1, 0)); + ImGui::Image( + reinterpret_cast(m_framebuffer.color.id), + static_cast(fbSize), + ImVec2(0, 1), + ImVec2(1, 0)); // handle input, this must come after drawing const auto &io = ImGui::GetIO(); const auto mousePos = geo::Vec2(io.MousePos); @@ -222,7 +241,8 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept { const auto wheel = io.MouseWheel; const auto wheelh = io.MouseWheelH; if (wheel != 0) { - const auto zoomMod = ox::defines::OS == ox::OS::Darwin ? io.KeySuper : core::buttonDown(m_ctx, core::Key::Mod_Ctrl); + const auto zoomMod = ox::defines::OS == ox::OS::Darwin ? + io.KeySuper : core::buttonDown(m_ctx, core::Key::Mod_Ctrl); m_tileSheetEditor.scrollV(fbSize, wheel, zoomMod); } if (wheelh != 0) { @@ -230,25 +250,31 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept { } if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) { m_prevMouseDownPos = mousePos; - auto clickPos = mousePos; - const auto &winPos = ImGui::GetWindowPos(); - clickPos.x -= winPos.x + 10; - clickPos.y -= winPos.y + 10; switch (m_tool) { case Tool::Draw: - m_tileSheetEditor.clickDraw(fbSize, clickPos); + m_tileSheetEditor.clickDraw(fbSize, clickPos(winPos, mousePos)); break; case Tool::Fill: - m_tileSheetEditor.clickFill(fbSize, clickPos); + m_tileSheetEditor.clickFill(fbSize, clickPos(winPos, mousePos)); break; case Tool::Select: - m_tileSheetEditor.clickSelect(fbSize, clickPos); + m_tileSheetEditor.clickSelect(fbSize, clickPos(winPos, mousePos)); break; case Tool::None: break; } } } + if (ImGui::BeginPopupContextItem("TileMenu", ImGuiPopupFlags_MouseButtonRight)) { + const auto popupPos = geo::Vec2(ImGui::GetWindowPos()); + if (ImGui::MenuItem("Insert Tile")) { + m_tileSheetEditor.insertTile(fbSize, clickPos(winPos, popupPos)); + } + if (ImGui::MenuItem("Delete Tile")) { + m_tileSheetEditor.deleteTile(fbSize, clickPos(winPos, popupPos)); + } + ImGui::EndPopup(); + } if (io.MouseReleased[0]) { m_prevMouseDownPos = {-1, -1}; m_tileSheetEditor.releaseMouseButton(); @@ -258,7 +284,8 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept { void TileSheetEditorImGui::drawPaletteSelector() noexcept { auto sctx = applicationData(m_ctx); const auto &files = sctx->project->fileList(core::FileExt_npal + 1); - const auto first = m_selectedPaletteIdx < files.size() ? files[m_selectedPaletteIdx].c_str() : ""; + const auto first = m_selectedPaletteIdx < files.size() ? + files[m_selectedPaletteIdx].c_str() : ""; if (ImGui::BeginCombo("Palette", first, 0)) { for (auto n = 0u; n < files.size(); n++) { const auto selected = (m_selectedPaletteIdx == n); diff --git a/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp b/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp index 96571421..8a2378b5 100644 --- a/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp +++ b/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp @@ -78,10 +78,15 @@ class TileSheetEditorImGui: public studio::BaseEditor { studio::UndoStack *undoStack() noexcept final; + [[nodiscard]] + static geo::Vec2 clickPos(const ImVec2 &winPos, geo::Vec2 clickPos) noexcept; + protected: ox::Error saveItem() noexcept override; private: + void showSubsheetEditor() noexcept; + [[nodiscard]] constexpr auto model() const noexcept { return m_tileSheetEditor.model(); diff --git a/src/nostalgia/core/studio/tilesheeteditormodel.cpp b/src/nostalgia/core/studio/tilesheeteditormodel.cpp index 56a89121..8cc172ee 100644 --- a/src/nostalgia/core/studio/tilesheeteditormodel.cpp +++ b/src/nostalgia/core/studio/tilesheeteditormodel.cpp @@ -59,10 +59,12 @@ enum class CommandId { Draw = 1, AddSubSheet = 2, RmSubSheet = 3, - UpdateSubSheet = 4, - Cut = 5, - Paste = 6, - PaletteChange = 7, + DeleteTile = 4, + InsertTile = 4, + UpdateSubSheet = 5, + Cut = 6, + Paste = 7, + PaletteChange = 8, }; constexpr bool operator==(CommandId c, int i) noexcept { @@ -324,6 +326,128 @@ class RmSubSheetCommand: public TileSheetCommand { }; +class InsertTilesCommand: public TileSheetCommand { + private: + TileSheet *m_img = nullptr; + TileSheet::SubSheetIdx m_idx; + std::size_t m_insertPos = 0; + std::size_t m_insertCnt = 0; + ox::Vector m_deletedPixels = {}; + + public: + constexpr InsertTilesCommand(TileSheet *img, const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { + const unsigned bytesPerTile = img->bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile; + m_img = img; + m_idx = idx; + m_insertPos = tileIdx * bytesPerTile; + m_insertCnt = tileCnt * bytesPerTile; + m_deletedPixels.resize(m_insertCnt); + // copy pixels to be erased + { + auto &s = m_img->getSubSheet(m_idx); + auto &p = s.pixels; + auto dst = m_deletedPixels.data(); + auto src = p.data() + p.size() - m_insertCnt; + const auto sz = m_insertCnt * sizeof(decltype(p[0])); + ox_memcpy(dst, src, sz); + } + } + + void redo() noexcept final { + auto &s = m_img->getSubSheet(m_idx); + auto &p = s.pixels; + auto dstPos = m_insertPos + m_insertCnt; + const auto dst = p.data() + dstPos; + const auto src = p.data() + m_insertPos; + ox_memmove(dst, src, p.size() - dstPos); + ox_memset(src, 0, m_insertCnt * sizeof(decltype(p[0]))); + } + + void undo() noexcept final { + auto &s = m_img->getSubSheet(m_idx); + auto &p = s.pixels; + const auto srcIdx = m_insertPos + m_insertCnt; + const auto src = p.data() + srcIdx; + const auto dst1 = p.data() + m_insertPos; + const auto dst2 = p.data() + p.size() - m_insertCnt; + const auto sz = p.size() - srcIdx; + ox_memmove(dst1, src, sz); + ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size()); + } + + [[nodiscard]] + int commandId() const noexcept final { + return static_cast(CommandId::InsertTile); + } + + [[nodiscard]] + const TileSheet::SubSheetIdx &subsheetIdx() const noexcept override { + return m_idx; + } + +}; + +class DeleteTilesCommand: public TileSheetCommand { + private: + TileSheet *m_img = nullptr; + TileSheet::SubSheetIdx m_idx; + std::size_t m_deletePos = 0; + std::size_t m_deleteSz = 0; + ox::Vector m_deletedPixels = {}; + + public: + constexpr DeleteTilesCommand(TileSheet *img, const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { + const unsigned bytesPerTile = img->bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile; + m_img = img; + m_idx = idx; + m_deletePos = tileIdx * bytesPerTile; + m_deleteSz = tileCnt * bytesPerTile; + m_deletedPixels.resize(m_deleteSz); + // copy pixels to be erased + { + auto &s = m_img->getSubSheet(m_idx); + auto &p = s.pixels; + auto dst = m_deletedPixels.data(); + auto src = p.data() + m_deletePos; + const auto sz = m_deleteSz * sizeof(decltype(p[0])); + ox_memcpy(dst, src, sz); + } + } + + void redo() noexcept final { + auto &s = m_img->getSubSheet(m_idx); + auto &p = s.pixels; + auto srcPos = m_deletePos + m_deleteSz; + const auto src = p.data() + srcPos; + const auto dst1 = p.data() + m_deletePos; + const auto dst2 = p.data() + (p.size() - m_deleteSz); + ox_memmove(dst1, src, p.size() - srcPos); + ox_memset(dst2, 0, m_deleteSz * sizeof(decltype(p[0]))); + } + + void undo() noexcept final { + auto &s = m_img->getSubSheet(m_idx); + auto &p = s.pixels; + const auto src = p.data() + m_deletePos; + const auto dst1 = p.data() + m_deletePos + m_deleteSz; + const auto dst2 = src; + const auto sz = p.size() - m_deletePos - m_deleteSz; + ox_memmove(dst1, src, sz); + ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size()); + } + + [[nodiscard]] + int commandId() const noexcept final { + return static_cast(CommandId::DeleteTile); + } + + [[nodiscard]] + const TileSheet::SubSheetIdx &subsheetIdx() const noexcept override { + return m_idx; + } + +}; + class UpdateSubSheetCommand: public TileSheetCommand { private: TileSheet *m_img = nullptr; @@ -499,6 +623,14 @@ void TileSheetEditorModel::rmSubsheet(const TileSheet::SubSheetIdx &idx) noexcep pushCommand(new RmSubSheetCommand(&m_img, idx)); } +void TileSheetEditorModel::insertTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { + pushCommand(new InsertTilesCommand(&m_img, idx, tileIdx, tileCnt)); +} + +void TileSheetEditorModel::deleteTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { + pushCommand(new DeleteTilesCommand(&m_img, idx, tileIdx, tileCnt)); +} + ox::Error TileSheetEditorModel::updateSubsheet(const TileSheet::SubSheetIdx &idx, const ox::String &name, int cols, int rows) noexcept { pushCommand(new UpdateSubSheetCommand(&m_img, idx, name, cols, rows)); return OxError(0); @@ -568,17 +700,8 @@ bool TileSheetEditorModel::updated() const noexcept { ox::Error TileSheetEditorModel::markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept { m_updated = true; const auto cmdId = cmd->commandId(); - switch (static_cast(cmdId)) { - case CommandId::AddSubSheet: - case CommandId::RmSubSheet: - case CommandId::Cut: - case CommandId::Draw: - case CommandId::Paste: - case CommandId::UpdateSubSheet: - break; - case CommandId::PaletteChange: - oxReturnError(readObj(m_ctx, m_img.defaultPalette.getPath().value).moveTo(&m_pal)); - break; + if (static_cast(cmdId) == CommandId::PaletteChange) { + oxReturnError(readObj(m_ctx, m_img.defaultPalette.getPath().value).moveTo(&m_pal)); } auto tsCmd = dynamic_cast(cmd); auto idx = m_img.validateSubSheetIdx(tsCmd->subsheetIdx()); diff --git a/src/nostalgia/core/studio/tilesheeteditormodel.hpp b/src/nostalgia/core/studio/tilesheeteditormodel.hpp index 06fbb1dc..7e34576d 100644 --- a/src/nostalgia/core/studio/tilesheeteditormodel.hpp +++ b/src/nostalgia/core/studio/tilesheeteditormodel.hpp @@ -65,6 +65,10 @@ class TileSheetEditorModel: public ox::SignalHandler { void rmSubsheet(const TileSheet::SubSheetIdx &idx) noexcept; + void insertTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept; + + void deleteTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept; + ox::Error updateSubsheet(const TileSheet::SubSheetIdx &idx, const ox::String &name, int cols, int rows) noexcept; void setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept; diff --git a/src/nostalgia/core/studio/tilesheeteditorview.cpp b/src/nostalgia/core/studio/tilesheeteditorview.cpp index a0b281fa..6093f098 100644 --- a/src/nostalgia/core/studio/tilesheeteditorview.cpp +++ b/src/nostalgia/core/studio/tilesheeteditorview.cpp @@ -50,6 +50,20 @@ void TileSheetEditorView::scrollH(const geo::Vec2 &paneSz, float wheelh) noexcep m_scrollOffset.x = ox::clamp(m_scrollOffset.x, -(sheetSize.x / 2), 0.f); } +void TileSheetEditorView::insertTile(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept { + const auto pt = clickPoint(paneSize, clickPos); + const auto s = m_model.activeSubSheet(); + const auto tileIdx = ptToIdx(pt, s->columns) / PixelsPerTile; + m_model.insertTiles(m_model.activeSubSheetIdx(), tileIdx, 1); +} + +void TileSheetEditorView::deleteTile(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept { + const auto pt = clickPoint(paneSize, clickPos); + const auto s = m_model.activeSubSheet(); + const auto tileIdx = ptToIdx(pt, s->columns) / PixelsPerTile; + m_model.deleteTiles(m_model.activeSubSheetIdx(), tileIdx, 1); +} + void TileSheetEditorView::clickDraw(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept { const auto pt = clickPoint(paneSize, clickPos); m_model.drawCommand(pt, m_palIdx); diff --git a/src/nostalgia/core/studio/tilesheeteditorview.hpp b/src/nostalgia/core/studio/tilesheeteditorview.hpp index f99021b7..7a82ee9f 100644 --- a/src/nostalgia/core/studio/tilesheeteditorview.hpp +++ b/src/nostalgia/core/studio/tilesheeteditorview.hpp @@ -55,6 +55,10 @@ class TileSheetEditorView: public ox::SignalHandler { void draw() noexcept; + void insertTile(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept; + + void deleteTile(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept; + void clickDraw(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept; void clickSelect(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept;