diff --git a/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp b/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp index 4b5b9499..3a67adc2 100644 --- a/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp +++ b/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp @@ -414,6 +414,10 @@ void setPixel(TileSheet::SubSheet &ss, ox::Point const&pt, uint8_t palIdx) noexc ox::Error setPixelCount(TileSheet::SubSheet &ss, std::size_t cnt) noexcept; +void flipX(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept; + +void flipY(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept; + /** * Gets a count of the pixels in this sheet, and not that of its children. * @return a count of the pixels in this sheet diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/CMakeLists.txt b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/CMakeLists.txt index 7096243e..df125ef6 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/CMakeLists.txt +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources( cutpastecommand.cpp deletetilescommand.cpp drawcommand.cpp + flipcommand.cpp inserttilescommand.cpp palettechangecommand.cpp rmsubsheetcommand.cpp diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/commands.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/commands.hpp index 68b15e3c..5d8302eb 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/commands.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/commands.hpp @@ -12,14 +12,16 @@ namespace nostalgia::gfx { // Command IDs to use with QUndoCommand::id() enum class CommandId { Draw = 1, - AddSubSheet = 2, - RmSubSheet = 3, - DeleteTile = 4, - InsertTile = 4, - UpdateSubSheet = 5, - Cut = 6, - Paste = 7, - PaletteChange = 8, + AddSubSheet, + RmSubSheet, + DeleteTile, + FlipXCommand, + FlipYCommand, + InsertTile, + UpdateSubSheet, + Cut, + Paste, + PaletteChange, }; constexpr bool operator==(CommandId c, int i) noexcept { diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.cpp new file mode 100644 index 00000000..6acc1274 --- /dev/null +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "flipcommand.hpp" + +#include + +namespace nostalgia::gfx { + +FlipXCommand::FlipXCommand( + TileSheet &img, + TileSheet::SubSheetIdx subSheetIdx, + ox::Point const &a, + ox::Point const &b) noexcept: + m_img{img}, + m_subSheetIdx{std::move(subSheetIdx)}, + m_ptA{a}, + m_ptB{b} {} + +ox::Error FlipXCommand::redo() noexcept { + auto &ss = getSubSheet(m_img, m_subSheetIdx); + flipX(ss, m_ptA, m_ptB); + return {}; +} + +ox::Error FlipXCommand::undo() noexcept { + auto &ss = getSubSheet(m_img, m_subSheetIdx); + flipX(ss, m_ptA, m_ptB); + return {}; +} + +int FlipXCommand::commandId() const noexcept { + return static_cast(CommandId::FlipXCommand); +} + +TileSheet::SubSheetIdx const &FlipXCommand::subsheetIdx() const noexcept { + return m_subSheetIdx; +} + + +FlipYCommand::FlipYCommand( + TileSheet &img, + TileSheet::SubSheetIdx subSheetIdx, + ox::Point const &a, + ox::Point const &b) noexcept: + m_img{img}, + m_subSheetIdx{std::move(subSheetIdx)}, + m_ptA{a}, + m_ptB{b} {} + +ox::Error FlipYCommand::redo() noexcept { + auto &ss = getSubSheet(m_img, m_subSheetIdx); + flipY(ss, m_ptA, m_ptB); + return {}; +} + +ox::Error FlipYCommand::undo() noexcept { + auto &ss = getSubSheet(m_img, m_subSheetIdx); + flipY(ss, m_ptA, m_ptB); + return {}; +} + +int FlipYCommand::commandId() const noexcept { + return static_cast(CommandId::FlipYCommand); +} + +TileSheet::SubSheetIdx const &FlipYCommand::subsheetIdx() const noexcept { + return m_subSheetIdx; +} + +} diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.hpp new file mode 100644 index 00000000..8d1d7b34 --- /dev/null +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.hpp @@ -0,0 +1,63 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include "commands.hpp" + +namespace nostalgia::gfx { + +class FlipXCommand: public TileSheetCommand { + private: + TileSheet &m_img; + TileSheet::SubSheetIdx m_subSheetIdx; + ox::Point const m_ptA; + ox::Point const m_ptB; + + public: + FlipXCommand( + TileSheet &img, + TileSheet::SubSheetIdx subSheetIdx, + ox::Point const &a, + ox::Point const &b) noexcept; + + ox::Error redo() noexcept final; + + ox::Error undo() noexcept final; + + [[nodiscard]] + int commandId() const noexcept final; + + [[nodiscard]] + TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override; + +}; + +class FlipYCommand: public TileSheetCommand { + private: + TileSheet &m_img; + TileSheet::SubSheetIdx m_subSheetIdx; + ox::Point const m_ptA; + ox::Point const m_ptB; + + public: + FlipYCommand( + TileSheet &img, + TileSheet::SubSheetIdx subSheetIdx, + ox::Point const &a, + ox::Point const &b) noexcept; + + ox::Error redo() noexcept final; + + ox::Error undo() noexcept final; + + [[nodiscard]] + int commandId() const noexcept final; + + [[nodiscard]] + TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override; + +}; + +} diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp index 288ab3b5..c45a6692 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -222,7 +222,19 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept { } } ImGui::EndChild(); - auto const ySize = controlsSize.y - 38; + ImGui::BeginChild("OperationsBox", {s_palViewWidth - 24, ig::BtnSz.y + 17}, true); + { + auto constexpr btnSz = ImVec2{55, ig::BtnSz.y}; + if (ig::PushButton("Flip X", btnSz)) { + oxLogError(m_model.flipX()); + } + ImGui::SameLine(); + if (ig::PushButton("Flip Y", btnSz)) { + oxLogError(m_model.flipY()); + } + } + ImGui::EndChild(); + auto const ySize = controlsSize.y - (38 + ig::BtnSz.y + 21); // draw palette/color picker ImGui::BeginChild("Palette", {s_palViewWidth - 24, ySize / 2.f}, true); { diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp index b1c7fda7..99ab1b70 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp @@ -22,6 +22,8 @@ #include "commands/updatesubsheetcommand.hpp" #include "tilesheeteditormodel.hpp" +#include "commands/flipcommand.hpp" + namespace nostalgia::gfx { // delete pixels of all non-leaf nodes @@ -75,7 +77,7 @@ void TileSheetEditorModel::cut() { auto const pt1 = m_selection->a; auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight}; turbine::setClipboardObject(m_tctx, std::move(cb)); - pushCommand(ox::make( + std::ignore = pushCommand(ox::make( CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb)); } @@ -108,7 +110,7 @@ void TileSheetEditorModel::paste() { auto const&s = activeSubSheet(); auto const pt1 = m_selection->a; auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight}; - pushCommand(ox::make( + std::ignore = pushCommand(ox::make( CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb)); } @@ -123,7 +125,7 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept { ox::Error TileSheetEditorModel::setPalette(ox::StringViewCR path) noexcept { OX_REQUIRE(uuid, keelCtx(m_tctx).pathToUuid.at(path)); - pushCommand(ox::make( + std::ignore = pushCommand(ox::make( activeSubSheetIdx(), m_img, uuid->toString())); return {}; } @@ -146,7 +148,7 @@ void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t const pal if (m_ongoingDrawCommand) { m_updated = m_updated || m_ongoingDrawCommand->append(idx); } else if (getPixel(activeSubSheet, idx) != palIdx) { - pushCommand(ox::make( + std::ignore = pushCommand(ox::make( m_img, m_activeSubsSheetIdx, idx, static_cast(palIdx))); } } @@ -156,27 +158,27 @@ void TileSheetEditorModel::endDrawCommand() noexcept { } void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept { - pushCommand(ox::make(m_img, parentIdx)); + std::ignore = pushCommand(ox::make(m_img, parentIdx)); } void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept { - pushCommand(ox::make(m_img, idx)); + std::ignore = pushCommand(ox::make(m_img, idx)); } void TileSheetEditorModel::insertTiles( TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept { - pushCommand(ox::make(m_img, idx, tileIdx, tileCnt)); + std::ignore = pushCommand(ox::make(m_img, idx, tileIdx, tileCnt)); } void TileSheetEditorModel::deleteTiles( TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept { - pushCommand(ox::make(m_img, idx, tileIdx, tileCnt)); + std::ignore = pushCommand(ox::make(m_img, idx, tileIdx, tileCnt)); } ox::Error TileSheetEditorModel::updateSubsheet( TileSheet::SubSheetIdx const&idx, ox::StringViewCR name, int const cols, int const rows) noexcept { OX_REQUIRE(cmd, ox::makeCatch(m_img, idx, name, cols, rows)); - pushCommand(cmd); + std::ignore = pushCommand(cmd); return {}; } @@ -206,7 +208,7 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int const palIdx) noexcept { if (m_ongoingDrawCommand) { m_updated = m_updated || m_ongoingDrawCommand->append(idxList); } else if (getPixel(activeSubSheet, pt) != palIdx) { - pushCommand(ox::make(m_img, m_activeSubsSheetIdx, idxList, palIdx)); + std::ignore = pushCommand(ox::make(m_img, m_activeSubsSheetIdx, idxList, palIdx)); } } @@ -279,6 +281,28 @@ bool TileSheetEditorModel::pixelSelected(std::size_t const idx) const noexcept { return m_selection && m_selection->contains(pt); } +ox::Error TileSheetEditorModel::flipX() noexcept { + auto const &ss = activeSubSheet(); + ox::Point a; + ox::Point b{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1}; + if (m_selection) { + a = m_selection->a; + b = m_selection->b; + } + return pushCommand(ox::make(m_img, m_activeSubsSheetIdx, a, b)); +} + +ox::Error TileSheetEditorModel::flipY() noexcept { + auto const &ss = activeSubSheet(); + ox::Point a; + ox::Point b{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1}; + if (m_selection) { + a = m_selection->a; + b = m_selection->b; + } + return pushCommand(ox::make(m_img, m_activeSubsSheetIdx, a, b)); +} + void TileSheetEditorModel::getFillPixels( TileSheet::SubSheet const&activeSubSheet, ox::Span pixels, @@ -323,10 +347,11 @@ void TileSheetEditorModel::setPalPath() noexcept { } } -void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept { +ox::Error TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept { std::ignore = m_undoStack.push(ox::UPtr{cmd}); m_ongoingDrawCommand = dynamic_cast(cmd); m_updated = true; + return {}; } ox::Error TileSheetEditorModel::handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const&id) noexcept { diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp index 6b00e8f1..9af2ec86 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp @@ -126,6 +126,10 @@ class TileSheetEditorModel: public ox::SignalHandler { bool pixelSelected(std::size_t idx) const noexcept; + ox::Error flipX() noexcept; + + ox::Error flipY() noexcept; + private: void getFillPixels( TileSheet::SubSheet const&activeSubSheet, @@ -135,7 +139,7 @@ class TileSheetEditorModel: public ox::SignalHandler { void setPalPath() noexcept; - void pushCommand(studio::UndoCommand *cmd) noexcept; + ox::Error pushCommand(studio::UndoCommand *cmd) noexcept; ox::Error handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const&id) noexcept; diff --git a/src/nostalgia/modules/gfx/src/tilesheet.cpp b/src/nostalgia/modules/gfx/src/tilesheet.cpp index a3aa903c..078da34a 100644 --- a/src/nostalgia/modules/gfx/src/tilesheet.cpp +++ b/src/nostalgia/modules/gfx/src/tilesheet.cpp @@ -103,6 +103,30 @@ ox::Error setPixelCount(TileSheet::SubSheet &ss, std::size_t const cnt) noexcept return setPixelCount(ss.pixels, cnt); } +void flipX(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept { + auto const w = (b.x - a.x) / 2 + 1; + auto const pixCols = ss.columns * TileWidth; + for (int32_t yi = a.y; yi <= b.y; ++yi) { + for (int32_t xi = 0; xi < w; ++xi) { + auto const aIdx = ptToIdx(a.x + xi, yi, pixCols); + auto const bIdx = ptToIdx(b.x - xi, yi, pixCols); + std::swap(ss.pixels[aIdx], ss.pixels[bIdx]); + } + } +} + +void flipY(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept { + auto const h = (b.y - a.y) / 2 + 1; + auto const pixCols = ss.columns * TileWidth; + for (int32_t yi = 0; yi < h; ++yi) { + for (int32_t xi = a.x; xi <= b.x; ++xi) { + auto const aIdx = ptToIdx(xi, a.y + yi, pixCols); + auto const bIdx = ptToIdx(xi, b.y - yi, pixCols); + std::swap(ss.pixels[aIdx], ss.pixels[bIdx]); + } + } +} + unsigned pixelCnt(TileSheet::SubSheet const&ss) noexcept { return static_cast(ss.pixels.size()); }