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 f8f42c87..ee4fd9bc 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/CMakeLists.txt +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/CMakeLists.txt @@ -9,5 +9,6 @@ target_sources( inserttilescommand.cpp palettechangecommand.cpp rmsubsheetcommand.cpp + rotatecommand.cpp updatesubsheetcommand.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 4cdebe6b..17b98990 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/commands.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/commands.hpp @@ -17,6 +17,7 @@ enum class CommandId { DeleteTile, FlipX, FlipY, + Rotate, InsertTile, MoveSubSheet, UpdateSubSheet, diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/rmsubsheetcommand.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/rmsubsheetcommand.cpp index bb6011bb..bb11bb66 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/rmsubsheetcommand.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/rmsubsheetcommand.cpp @@ -6,7 +6,7 @@ namespace nostalgia::gfx { -gfx::RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept: +RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept: m_img(img), m_idx(std::move(idx)), m_parentIdx(m_idx) { diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/rotatecommand.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/rotatecommand.cpp new file mode 100644 index 00000000..bad82472 --- /dev/null +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/rotatecommand.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "rotatecommand.hpp" + +namespace nostalgia::gfx { + +static void rotateLeft(TileSheet::SubSheet &ss, ox::Point const &pt, int const depth = 0) noexcept { + if (depth >= 4) { + return; + } + auto const h = ss.rows * TileHeight; + auto const dstPt = ox::Point{pt.y, h - 1 - pt.x}; + auto const srcIdx = ptToIdx(pt, ss.columns); + auto const dstIdx = ptToIdx(dstPt, ss.columns); + auto const src = ss.pixels[srcIdx]; + auto &dst = ss.pixels[dstIdx]; + rotateLeft(ss, dstPt, depth + 1); + dst = src; +} + +static void rotateLeft(TileSheet::SubSheet &ss) noexcept { + auto const w = ss.columns * TileWidth; + auto const h = ss.rows * TileHeight; + for (int x = 0; x < w / 2; ++x) { + for (int y = 0; y < h / 2; ++y) { + rotateLeft(ss, {w - 1 - y, x}); + } + } +} + +static void rotateRight(TileSheet::SubSheet &ss, ox::Point const &pt, int const depth = 0) noexcept { + if (depth >= 4) { + return; + } + auto const w = ss.columns * TileWidth; + auto const dstPt = ox::Point{w - 1 - pt.y, pt.x}; + auto const srcIdx = ptToIdx(pt, ss.columns); + auto const dstIdx = ptToIdx(dstPt, ss.columns); + auto const src = ss.pixels[srcIdx]; + auto &dst = ss.pixels[dstIdx]; + rotateRight(ss, dstPt, depth + 1); + dst = src; +} + +static void rotateRight(TileSheet::SubSheet &ss) noexcept { + auto const w = ss.columns * TileWidth; + auto const h = ss.rows * TileHeight; + for (int x = 0; x < w / 2; ++x) { + for (int y = 0; y < h / 2; ++y) { + rotateRight(ss, {w - 1 - y, x}); + } + } +} + + +RotateCommand::RotateCommand( + TileSheet &img, + TileSheet::SubSheetIdx idx, + Direction const dir) noexcept: + m_img(img), + m_idx(std::move(idx)), + m_dir{dir} { +} + +ox::Error RotateCommand::redo() noexcept { + switch (m_dir) { + case Direction::Left: + rotateLeft(getSubSheet(m_img, m_idx)); + break; + case Direction::Right: + rotateRight(getSubSheet(m_img, m_idx)); + break; + } + return {}; +} + +ox::Error RotateCommand::undo() noexcept { + switch (m_dir) { + case Direction::Left: + rotateRight(getSubSheet(m_img, m_idx)); + break; + case Direction::Right: + rotateLeft(getSubSheet(m_img, m_idx)); + break; + } + return {}; +} + +int RotateCommand::commandId() const noexcept { + return static_cast(CommandId::Rotate); +} + +TileSheet::SubSheetIdx const&RotateCommand::subsheetIdx() const noexcept { + return m_idx; +} + +} diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/rotatecommand.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/rotatecommand.hpp new file mode 100644 index 00000000..e7a89772 --- /dev/null +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/rotatecommand.hpp @@ -0,0 +1,38 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include "commands.hpp" + +namespace nostalgia::gfx { + +class RotateCommand: public TileSheetCommand { + public: + enum class Direction { + Right, + Left, + }; + + private: + TileSheet &m_img; + TileSheet::SubSheetIdx m_idx; + Direction const m_dir; + + public: + RotateCommand(TileSheet &img, TileSheet::SubSheetIdx idx, Direction dir) 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 fe4bece9..6b8fc250 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -248,14 +248,22 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept { ImGui::EndChild(); ImGui::BeginChild("OperationsBox", {0, 32}, true); { - auto constexpr btnSz = ImVec2{55, 16}; - if (ig::PushButton("Flip X", btnSz)) { + auto constexpr btnSz = ImVec2{75, 16}; + if (ig::PushButton("Flip X", {55, 16})) { oxLogError(m_model.flipX()); } ImGui::SameLine(); - if (ig::PushButton("Flip Y", btnSz)) { + if (ig::PushButton("Flip Y", {55, 16})) { oxLogError(m_model.flipY()); } + ImGui::SameLine(); + if (ig::PushButton("Rotate Left", {80, 16})) { + oxLogError(m_model.rotateLeft()); + } + ImGui::SameLine(); + if (ig::PushButton("Rotate Right", {80, 16})) { + oxLogError(m_model.rotateRight()); + } } ImGui::EndChild(); auto const ySize = controlsSize.y - (38 + ig::BtnSz.y + 21); diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp index 954ef891..82f72b6f 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp @@ -24,6 +24,7 @@ #include "tilesheeteditormodel.hpp" #include "commands/movesubsheetcommand.hpp" +#include "commands/rotatecommand.hpp" namespace nostalgia::gfx { @@ -237,6 +238,16 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int const palIdx) noexcept { } } +ox::Error TileSheetEditorModel::rotateLeft() noexcept { + return pushCommand(ox::make( + m_img, m_activeSubsSheetIdx, RotateCommand::Direction::Left)); +} + +ox::Error TileSheetEditorModel::rotateRight() noexcept { + return pushCommand(ox::make( + m_img, m_activeSubsSheetIdx, RotateCommand::Direction::Right)); +} + void TileSheetEditorModel::setSelection(studio::Selection const&sel) noexcept { m_selection.emplace(sel); m_updated = true; diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp index 95cab4f4..390c9aef 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp @@ -106,6 +106,10 @@ class TileSheetEditorModel: public ox::SignalHandler { void fill(ox::Point const&pt, int palIdx) noexcept; + ox::Error rotateLeft() noexcept; + + ox::Error rotateRight() noexcept; + void setSelection(studio::Selection const&sel) noexcept; void select(ox::Point const&pt) noexcept;