Compare commits

..

6 Commits

Author SHA1 Message Date
1b32bdfcad [nostalgia/studio] Bump version to d2024.12.3
All checks were successful
Build / build (push) Successful in 3m14s
2025-01-17 21:45:37 -06:00
3544392fa8 [nostalgia/core/studio] Cleanup, fix possible TileSheet fill tool failure
All checks were successful
Build / build (push) Successful in 3m21s
2025-01-17 21:43:46 -06:00
144d234d09 [nostalgia/studio] Change version to release-d2024.12
All checks were successful
Build / build (push) Successful in 3m20s
2025-01-12 21:03:34 -06:00
465fb06f76 [nostalgia/core] Fix TileSheet validation/repair to ensure pixels gets cleared if there are subsheets
Some checks failed
Build / build (push) Has been cancelled
2025-01-12 20:58:19 -06:00
db953dd0d1 [studio] Fix new project menu to return an appropriately sized string for name 2025-01-12 20:58:19 -06:00
daab4dc4f5 [nostalgia/core/studio] Fix SubSheet editor to return an appropriately sized string 2025-01-12 20:58:19 -06:00
8 changed files with 97 additions and 86 deletions

View File

@ -233,7 +233,9 @@ struct TileSheetV4 {
[[nodiscard]]
constexpr bool valid(TileSheetV4::SubSheet const&ss, int bpp) noexcept {
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
return ox::all_of(ss.subsheets.begin(), ss.subsheets.end(),
return
(ss.pixels.empty() || ss.subsheets.empty()) &&
ox::all_of(ss.subsheets.begin(), ss.subsheets.end(),
[bpp, bytes](TileSheetV4::SubSheet const&s) {
return bytes == s.pixels.size() && valid(s, bpp);
});
@ -245,8 +247,14 @@ constexpr bool valid(TileSheetV4 const&ts) noexcept {
}
constexpr void repair(TileSheetV4::SubSheet &ss, int bpp) noexcept {
if (ss.subsheets.empty()) {
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
ss.pixels.resize(bytes);
} else {
ss.pixels.clear();
ss.columns = -1;
ss.rows = -1;
}
for (auto &s : ss.subsheets) {
repair(s, bpp);
}

View File

@ -9,27 +9,24 @@ namespace nostalgia::core {
core::UpdateSubSheetCommand::UpdateSubSheetCommand(
TileSheet &img,
TileSheet::SubSheetIdx idx,
ox::String name,
int cols,
int rows) noexcept:
m_img(img),
m_idx(std::move(idx)),
m_sheet(getSubSheet(m_img, m_idx)),
m_newName(std::move(name)),
m_newCols(cols),
m_newRows(rows) {
ox::StringParam name,
int const cols,
int const rows):
m_img{img},
m_idx{std::move(idx)},
m_sheet{getSubSheet(m_img, m_idx)} {
m_sheet = getSubSheet(m_img, m_idx);
m_sheet.name = std::move(name);
OX_THROW_ERROR(resizeSubsheet(m_sheet, m_img.bpp, {cols, rows}));
}
ox::Error UpdateSubSheetCommand::redo() noexcept {
auto &sheet = getSubSheet(m_img, m_idx);
sheet.name = m_newName;
oxLogError(resizeSubsheet(sheet, m_img.bpp, {m_newCols, m_newRows}));
std::swap(m_sheet, getSubSheet(m_img, m_idx));
return {};
}
ox::Error UpdateSubSheetCommand::undo() noexcept {
auto &sheet = getSubSheet(m_img, m_idx);
sheet = m_sheet;
std::swap(m_sheet, getSubSheet(m_img, m_idx));
return {};
}

View File

@ -13,17 +13,14 @@ class UpdateSubSheetCommand: public TileSheetCommand {
TileSheet &m_img;
TileSheet::SubSheetIdx m_idx;
TileSheet::SubSheet m_sheet;
ox::String m_newName;
int m_newCols = 0;
int m_newRows = 0;
public:
UpdateSubSheetCommand(
TileSheet &img,
TileSheet::SubSheetIdx idx,
ox::String name,
ox::StringParam name,
int cols,
int rows) noexcept;
int rows);
ox::Error redo() noexcept final;

View File

@ -544,7 +544,7 @@ void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &tctx) noexcept
auto const popupHeight = modSize ? 130.f : 85.f;
auto const popupSz = ImVec2{popupWidth, popupHeight};
if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) {
ImGui::InputText("Name", m_name.data(), m_name.cap());
ig::InputText("Name", m_name);
if (modSize) {
ImGui::InputInt("Columns", &m_cols);
ImGui::InputInt("Rows", &m_rows);

View File

@ -26,11 +26,6 @@
namespace nostalgia::core {
Palette const TileSheetEditorModel::s_defaultPalette = {
.colorNames = {ox::Vector<ox::String>{{}}},
.pages = {{"Page 1", ox::Vector<Color16>(128)}},
};
// delete pixels of all non-leaf nodes
static void normalizeSubsheets(TileSheet::SubSheet &ss) noexcept {
if (ss.subsheets.empty()) {
@ -42,7 +37,14 @@ static void normalizeSubsheets(TileSheet::SubSheet &ss) noexcept {
}
}
TileSheetEditorModel::TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack):
Palette const TileSheetEditorModel::s_defaultPalette = {
.colorNames = {ox::Vector<ox::String>{{}}},
.pages = {{"Page 1", ox::Vector<Color16>(128)}},
};
TileSheetEditorModel::TileSheetEditorModel(
studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack):
m_sctx(sctx),
m_tctx(m_sctx.tctx),
m_path(path),
@ -62,7 +64,7 @@ void TileSheetEditorModel::cut() {
TileSheetClipboard blankCb;
auto cb = ox::make_unique<TileSheetClipboard>();
auto const&s = activeSubSheet();
iterateSelectionRows(*m_selection, [&](int x, int y) {
iterateSelectionRows(*m_selection, [&](int const x, int const y) {
auto pt = ox::Point{x, y};
auto const idx = core::idx(s, pt);
auto const c = getPixel(s, m_img.bpp, idx);
@ -73,7 +75,8 @@ 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>(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
pushCommand(ox::make<CutPasteCommand>(
CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
}
void TileSheetEditorModel::copy() {
@ -81,7 +84,7 @@ void TileSheetEditorModel::copy() {
return;
}
auto cb = ox::make_unique<TileSheetClipboard>();
iterateSelectionRows(*m_selection, [&](int x, int y) {
iterateSelectionRows(*m_selection, [&](int const x, int const y) {
auto pt = ox::Point{x, y};
const auto&s = activeSubSheet();
const auto idx = core::idx(s, pt);
@ -105,7 +108,8 @@ 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>(CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
pushCommand(ox::make<CutPasteCommand>(
CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
}
bool TileSheetEditorModel::acceptsClipboardPayload() const noexcept {
@ -120,8 +124,8 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept {
}
constexpr ox::StringView uuidPrefix = "uuid://";
if (ox::beginsWith(path, uuidPrefix)) {
auto uuid = ox::StringView(&path[uuidPrefix.bytes()], path.bytes() - uuidPrefix.bytes());
auto out = keelCtx(m_tctx).uuidToPath.at(uuid);
auto const uuid = ox::StringView(&path[uuidPrefix.bytes()], path.bytes() - uuidPrefix.bytes());
auto const out = keelCtx(m_tctx).uuidToPath.at(uuid);
if (out.error) {
return {};
}
@ -131,13 +135,14 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept {
}
}
ox::Error TileSheetEditorModel::setPalette(ox::StringView path) noexcept {
ox::Error TileSheetEditorModel::setPalette(ox::StringViewCR path) noexcept {
OX_REQUIRE(uuid, keelCtx(m_tctx).pathToUuid.at(path));
pushCommand(ox::make<PaletteChangeCommand>(activeSubSheetIdx(), m_img, uuid->toString()));
pushCommand(ox::make<PaletteChangeCommand>(
activeSubSheetIdx(), m_img, uuid->toString()));
return {};
}
void TileSheetEditorModel::setPalettePage(size_t pg) noexcept {
void TileSheetEditorModel::setPalettePage(size_t const pg) noexcept {
m_palettePage = ox::clamp<size_t>(pg, 0, m_pal->pages.size() - 1);
m_updated = true;
}
@ -146,7 +151,7 @@ size_t TileSheetEditorModel::palettePage() const noexcept {
return m_palettePage;
}
void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept {
void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t const palIdx) noexcept {
const auto &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
return;
@ -155,7 +160,8 @@ void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t palIdx) n
if (m_ongoingDrawCommand) {
m_updated = m_updated || m_ongoingDrawCommand->append(idx);
} else if (getPixel(activeSubSheet, m_img.bpp, idx) != palIdx) {
pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
pushCommand(ox::make<DrawCommand>(
m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
}
}
@ -171,16 +177,20 @@ void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept
pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
}
void TileSheetEditorModel::insertTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
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));
}
void TileSheetEditorModel::deleteTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
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));
}
ox::Error TileSheetEditorModel::updateSubsheet(TileSheet::SubSheetIdx const&idx, ox::StringView const&name, int cols, int rows) noexcept {
pushCommand(ox::make<UpdateSubSheetCommand>(m_img, idx, ox::String(name), cols, rows));
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);
return {};
}
@ -189,7 +199,7 @@ void TileSheetEditorModel::setActiveSubsheet(TileSheet::SubSheetIdx const&idx) n
this->activeSubsheetChanged.emit(m_activeSubsSheetIdx);
}
void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept {
void TileSheetEditorModel::fill(ox::Point const&pt, int const palIdx) noexcept {
auto const&activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
// build idx list
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
@ -197,10 +207,10 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept {
}
ox::Array<bool, PixelsPerTile> updateMap = {};
auto const oldColor = getPixel(activeSubSheet, m_img.bpp, pt);
getFillPixels(updateMap, pt, oldColor);
getFillPixels(activeSubSheet, updateMap, pt, oldColor);
ox::Vector<std::size_t> idxList;
auto i = core::idx(activeSubSheet, pt) / PixelsPerTile * PixelsPerTile;
for (auto u : updateMap) {
for (auto const u : updateMap) {
if (u) {
idxList.emplace_back(i);
}
@ -230,7 +240,7 @@ void TileSheetEditorModel::completeSelection() noexcept {
m_selTracker.finishSelection();
m_selection.emplace(m_selTracker.selection());
auto&pt = m_selection->b;
auto&s = activeSubSheet();
auto const&s = activeSubSheet();
pt.x = ox::min(s.columns * TileWidth - 1, pt.x);
pt.y = ox::min(s.rows * TileHeight - 1, pt.y);
}
@ -275,47 +285,44 @@ ox::Error TileSheetEditorModel::saveFile() noexcept {
return m_sctx.project->writeObj(m_path, m_img, ox::ClawFormat::Metal);
}
bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept {
bool TileSheetEditorModel::pixelSelected(std::size_t const idx) const noexcept {
auto const&s = activeSubSheet();
auto const pt = idxToPt(static_cast<int>(idx), s.columns);
return m_selection && m_selection->contains(pt);
}
void TileSheetEditorModel::getFillPixels(ox::Span<bool> pixels, ox::Point const&pt, int oldColor) const noexcept {
const auto &activeSubSheet = this->activeSubSheet();
const auto tileIdx = [activeSubSheet](const ox::Point &pt) noexcept {
return ptToIdx(pt, activeSubSheet.columns) / PixelsPerTile;
};
// get points
const auto leftPt = pt + ox::Point(-1, 0);
const auto rightPt = pt + ox::Point(1, 0);
const auto topPt = pt + ox::Point(0, -1);
const auto bottomPt = pt + ox::Point(0, 1);
// calculate indices
const auto idx = ptToIdx(pt, activeSubSheet.columns);
const auto leftIdx = ptToIdx(leftPt, activeSubSheet.columns);
const auto rightIdx = ptToIdx(rightPt, activeSubSheet.columns);
const auto topIdx = ptToIdx(topPt, activeSubSheet.columns);
const auto bottomIdx = ptToIdx(bottomPt, activeSubSheet.columns);
const auto tile = tileIdx(pt);
void TileSheetEditorModel::getFillPixels(
TileSheet::SubSheet const&activeSubSheet,
ox::Span<bool> pixels,
ox::Point const&pt,
int const oldColor) const noexcept {
auto const idx = ptToIdx(pt, activeSubSheet.columns);
auto const relIdx = idx % PixelsPerTile;
if (pixels[relIdx] || getPixel(activeSubSheet, m_img.bpp, idx) != oldColor) {
return;
}
// mark pixels to update
pixels[idx % PixelsPerTile] = true;
if (!pixels[leftIdx % PixelsPerTile] && tile == tileIdx(leftPt) && getPixel(activeSubSheet, m_img.bpp, leftIdx) == oldColor) {
getFillPixels(pixels, leftPt, oldColor);
pixels[relIdx] = true;
if (pt.x % TileWidth != 0) {
auto const leftPt = pt + ox::Point{-1, 0};
getFillPixels(activeSubSheet, pixels, leftPt, oldColor);
}
if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && getPixel(activeSubSheet, m_img.bpp, rightIdx) == oldColor) {
getFillPixels(pixels, rightPt, oldColor);
if (pt.x % TileWidth != TileWidth - 1) {
auto const rightPt = pt + ox::Point{1, 0};
getFillPixels(activeSubSheet, pixels, rightPt, oldColor);
}
if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && getPixel(activeSubSheet, m_img.bpp, topIdx) == oldColor) {
getFillPixels(pixels, topPt, oldColor);
if (pt.y % TileHeight != 0) {
auto const topPt = pt + ox::Point{0, -1};
getFillPixels(activeSubSheet, pixels, topPt, oldColor);
}
if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && getPixel(activeSubSheet, m_img.bpp, bottomIdx) == oldColor) {
getFillPixels(pixels, bottomPt, oldColor);
if (pt.y % TileHeight != TileHeight - 1) {
auto const bottomPt = pt + ox::Point{0, 1};
getFillPixels(activeSubSheet, pixels, bottomPt, oldColor);
}
}
void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
std::ignore = m_undoStack.push(ox::UPtr<studio::UndoCommand>(cmd));
std::ignore = m_undoStack.push(ox::UPtr<studio::UndoCommand>{cmd});
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
m_updated = true;
}

View File

@ -4,9 +4,7 @@
#pragma once
#include <ox/std/bounds.hpp>
#include <ox/std/point.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/string.hpp>
#include <studio/studio.hpp>
@ -38,7 +36,7 @@ class TileSheetEditorModel: public ox::SignalHandler {
bool m_updated = false;
public:
TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack);
TileSheetEditorModel(studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack);
~TileSheetEditorModel() override = default;
@ -63,7 +61,7 @@ class TileSheetEditorModel: public ox::SignalHandler {
[[nodiscard]]
ox::StringView palPath() const noexcept;
ox::Error setPalette(ox::StringView path) noexcept;
ox::Error setPalette(ox::StringViewCR path) noexcept;
void setPalettePage(size_t pg) noexcept;
@ -128,7 +126,11 @@ class TileSheetEditorModel: public ox::SignalHandler {
bool pixelSelected(std::size_t idx) const noexcept;
private:
void getFillPixels(ox::Span<bool> pixels, ox::Point const&pt, int oldColor) const noexcept;
void getFillPixels(
TileSheet::SubSheet const&activeSubSheet,
ox::Span<bool> pixels,
ox::Point const&pt,
int oldColor) const noexcept;
void pushCommand(studio::UndoCommand *cmd) noexcept;

View File

@ -11,7 +11,7 @@ target_link_libraries(
target_compile_definitions(
NostalgiaStudio PUBLIC
OLYMPIC_APP_VERSION="d2024.12.1"
OLYMPIC_APP_VERSION="d2024.12.3"
)
install(

View File

@ -50,7 +50,7 @@ void NewProject::draw(studio::StudioContext &ctx) noexcept {
void NewProject::drawNewProjectName(studio::StudioContext &sctx) noexcept {
drawWindow(sctx.tctx, &m_open, [this, &sctx] {
ImGui::InputText("Name", m_projectName.data(), m_projectName.cap());
ig::InputText("Name", m_projectName);
ImGui::Text("Path: %s", m_projectPath.c_str());
if (ImGui::Button("Browse")) {
oxLogError(studio::chooseDirectory().moveTo(m_projectPath));