From f1e68e0a043c79bf19e37cea3790cbd7b5d87e9f Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Wed, 21 May 2025 22:14:32 -0500 Subject: [PATCH] [nostalgia/gfx/studio/tilesheet] Fix overrun errors when switching subsheets, clear selection on switch --- .../commands/cutpastecommand.cpp | 5 +- .../commands/cutpastecommand.hpp | 2 +- .../tilesheeteditor/tilesheeteditormodel.cpp | 55 ++++++++++++------- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/cutpastecommand.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/cutpastecommand.cpp index c5aa5b51..a09bdd5f 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/cutpastecommand.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/cutpastecommand.cpp @@ -27,11 +27,14 @@ CutPasteCommand::CutPasteCommand( TileSheet::SubSheetIdx subSheetIdx, ox::Point const&dstStart, ox::Point dstEnd, - TileSheetClipboard const&cb) noexcept: + TileSheetClipboard const&cb): m_commandId(commandId), m_img(img), m_subSheetIdx(std::move(subSheetIdx)) { auto const&ss = getSubSheet(m_img, m_subSheetIdx); + if (dstStart.x >= ss.columns * TileWidth || dstStart.y >= ss.rows * TileHeight) { + throw ox::Exception{1, "paste starts beyond the bounds of target"}; + } dstEnd.x = std::min(ss.columns * TileWidth - 1, dstEnd.x); dstEnd.y = std::min(ss.rows * TileHeight - 1, dstEnd.y); for (auto const&p : cb.pixels()) { diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/cutpastecommand.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/cutpastecommand.hpp index dc54f27c..0ff7966b 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/cutpastecommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/cutpastecommand.hpp @@ -69,7 +69,7 @@ class CutPasteCommand: public TileSheetCommand { TileSheet::SubSheetIdx subSheetIdx, ox::Point const&dstStart, ox::Point dstEnd, - TileSheetClipboard const&cb) noexcept; + TileSheetClipboard const&cb); ox::Error redo() noexcept final; diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp index 9efc5002..18b29553 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp @@ -46,13 +46,13 @@ Palette const TileSheetEditorModel::s_defaultPalette = { TileSheetEditorModel::TileSheetEditorModel( studio::Context &sctx, ox::StringParam path, studio::UndoStack &undoStack): - m_sctx(sctx), - m_tctx(m_sctx.tctx), - m_path(std::move(path)), - m_img(*readObj(keelCtx(m_tctx), m_path).unwrapThrow()), + m_sctx{sctx}, + m_tctx{m_sctx.tctx}, + m_path{std::move(path)}, + m_img{*readObj(keelCtx(m_tctx), m_path).unwrapThrow()}, // ignore failure to load palette - m_pal(readObj(keelCtx(m_tctx), m_img.defaultPalette).value), - m_undoStack(undoStack) { + m_pal{readObj(keelCtx(m_tctx), m_img.defaultPalette).value}, + m_undoStack{undoStack} { normalizeSubsheets(m_img.subsheet); m_pal.updated.connect(this, &TileSheetEditorModel::markUpdated); m_undoStack.changeTriggered.connect(this, &TileSheetEditorModel::markUpdatedCmdId); @@ -67,19 +67,27 @@ void TileSheetEditorModel::cut() { TileSheetClipboard blankCb; auto cb = ox::make_unique(); auto const&s = activeSubSheet(); - iterateSelectionRows(*m_selection, [&](int const x, int const y) { + if (iterateSelectionRows(*m_selection, [&](int const x, int const y) { auto pt = ox::Point{x, y}; auto const idx = gfx::idx(s, pt); - auto const c = getPixel(s, idx); + if (idx >= s.pixels.size()) { + return ox::Error{1, "invalid idx"}; + } + auto const c = s.pixels[idx]; pt -= m_selection->a; cb->addPixel(pt, c); blankCb.addPixel(pt, 0); - }); + return ox::Error{}; + })) { + return; + } auto const pt1 = m_selection->a; auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight}; turbine::setClipboardObject(m_tctx, std::move(cb)); - std::ignore = pushCommand(ox::make( - CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb)); + if (auto const cmd = ox::makeCatch( + CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb); cmd.ok()) { + std::ignore = pushCommand(cmd.value); + } } void TileSheetEditorModel::copy() { @@ -87,14 +95,20 @@ void TileSheetEditorModel::copy() { return; } auto cb = ox::make_unique(); - iterateSelectionRows(*m_selection, [&](int const x, int const y) { + if (iterateSelectionRows(*m_selection, [&](int const x, int const y) { auto pt = ox::Point{x, y}; auto const&s = activeSubSheet(); auto const idx = gfx::idx(s, pt); - auto const c = getPixel(s, idx); + if (idx >= s.pixels.size()) { + return ox::Error{1, "invalid idx"}; + } + auto const c = s.pixels[idx]; pt -= m_selection->a; cb->addPixel(pt, c); - }); + return ox::Error{}; + })) { + return; + } turbine::setClipboardObject(m_tctx, std::move(cb)); } @@ -111,8 +125,10 @@ void TileSheetEditorModel::paste() { auto const&s = activeSubSheet(); auto const pt1 = m_selection->a; auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight}; - std::ignore = pushCommand(ox::make( - CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb)); + if (auto const cmd = ox::makeCatch( + CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb); cmd.ok()) { + std::ignore = pushCommand(cmd.value); + } } bool TileSheetEditorModel::acceptsClipboardPayload() const noexcept { @@ -152,7 +168,7 @@ void TileSheetEditorModel::drawCommand(ox::Point const &pt, std::size_t const pa auto const idx = gfx::idx(activeSubSheet, pt); if (m_ongoingDrawCommand) { m_updated = m_updated || m_ongoingDrawCommand->append(idx); - } else if (getPixel(activeSubSheet, idx) != palIdx) { + } else if (activeSubSheet.pixels[idx] != palIdx) { std::ignore = pushCommand(ox::make( m_img, m_activeSubsSheetIdx, idx, static_cast(palIdx))); } @@ -213,6 +229,7 @@ ox::Error TileSheetEditorModel::updateSubsheet( void TileSheetEditorModel::setActiveSubsheet(TileSheet::SubSheetIdx const&idx) noexcept { m_activeSubsSheetIdx = idx; this->activeSubsheetChanged.emit(m_activeSubsSheetIdx); + clearSelection(); } void TileSheetEditorModel::fill(ox::Point const&pt, int const palIdx) noexcept { @@ -381,7 +398,7 @@ void TileSheetEditorModel::getFillPixels( int const oldColor) const noexcept { auto const idx = ptToIdx(pt, activeSubSheet.columns); auto const relIdx = idx % PixelsPerTile; - if (pixels[relIdx] || getPixel(activeSubSheet, idx) != oldColor) { + if (pixels[relIdx] || activeSubSheet.pixels[idx] != oldColor) { return; } // mark pixels to update @@ -419,7 +436,7 @@ void TileSheetEditorModel::setPalPath() noexcept { } ox::Error TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept { - std::ignore = m_undoStack.push(ox::UPtr{cmd}); + std::ignore = m_undoStack.push(ox::UPtr{cmd}); m_ongoingDrawCommand = dynamic_cast(cmd); m_updated = true; return {};