[nostalgia/gfx/studio/tilesheet] Add flip x and flip y functionality
All checks were successful
Build / build (push) Successful in 3m33s

This commit is contained in:
Gary Talent 2025-01-26 15:41:13 -06:00
parent 1207dadee8
commit be51838775
9 changed files with 228 additions and 21 deletions

View File

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

View File

@ -4,6 +4,7 @@ target_sources(
cutpastecommand.cpp
deletetilescommand.cpp
drawcommand.cpp
flipcommand.cpp
inserttilescommand.cpp
palettechangecommand.cpp
rmsubsheetcommand.cpp

View File

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

View File

@ -0,0 +1,72 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "flipcommand.hpp"
#include <utility>
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<int>(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<int>(CommandId::FlipYCommand);
}
TileSheet::SubSheetIdx const &FlipYCommand::subsheetIdx() const noexcept {
return m_subSheetIdx;
}
}

View File

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

View File

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

View File

@ -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<CutPasteCommand>(
std::ignore = pushCommand(ox::make<CutPasteCommand>(
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<CutPasteCommand>(
std::ignore = pushCommand(ox::make<CutPasteCommand>(
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<PaletteChangeCommand>(
std::ignore = pushCommand(ox::make<PaletteChangeCommand>(
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<DrawCommand>(
std::ignore = pushCommand(ox::make<DrawCommand>(
m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
}
}
@ -156,27 +158,27 @@ void TileSheetEditorModel::endDrawCommand() noexcept {
}
void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept {
pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
std::ignore = pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
}
void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
std::ignore = pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
}
void TileSheetEditorModel::insertTiles(
TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept {
pushCommand(ox::make<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
std::ignore = pushCommand(ox::make<InsertTilesCommand>(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<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
std::ignore = pushCommand(ox::make<DeleteTilesCommand>(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<UpdateSubSheetCommand>(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<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
std::ignore = pushCommand(ox::make<DrawCommand>(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<FlipXCommand>(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<FlipYCommand>(m_img, m_activeSubsSheetIdx, a, b));
}
void TileSheetEditorModel::getFillPixels(
TileSheet::SubSheet const&activeSubSheet,
ox::Span<bool> 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<studio::UndoCommand>{cmd});
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
m_updated = true;
return {};
}
ox::Error TileSheetEditorModel::handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const&id) noexcept {

View File

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

View File

@ -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<unsigned>(ss.pixels.size());
}