Squashed 'deps/nostalgia/' changes from eed115b2..7d9f363b
7d9f363b [nostalgia/core/studio] Show color names in TileSheetEditor ababc2a7 [nostalgia/core/studio] Add Name to colors table in PaletteEditor bfd4bc3c [nostalgia/core] Revise Palette system, give TileSheetEditor a config file 8826d51e [studio] Add configio.hpp to studio.hpp c021e5e7 [ox/oc] Fix OC not dealing with certain int types properly 7d8a8e0e [keel] Cleanup 95a69b72 [ox/std] Fix String::c_str to always retrun a valid C str e4c38660 [nostalgia/core] Fix subsheet resize not to read garbage 67cf3ae8 [ox/std] Ensure ox::String always has at least a null terminator 2761f23d [nostalgia/developer-handbook] Update serialization notes 6c170d31 [nostalgia/developer-handbook] Update serialization notes d20bfc77 [nostalgia/developer-handbook] Update serialization notes 07ecbde1 [nostalgia/developer-handbook] Update notes on error handling fbe2fcd3 [nostalgia/developer-handbook] Update notes on error handling a8bb99b6 [turbine] Remove ClipboardObject::typeMatches 667dd21a [turbine] Make ClipboardObject::typId return a StringView 5d89370c [turbine] Rework getClipboardObject to use ox::ModelTypeId_v b968ec8a [keel] Remove redundant move ec45ffb7 [studio] Fix build error 97dc0274 [nostalgia/core/studio] Add acceptsClipboardPayload to TileSheetEditor a138f60f [studio] Add acceptsClipboardPayload to Editor 791d1950 [turbine] Make ClipboardObject use ox::ModelTypeId_v for typeId 78eb8fca [keel] Cleanup pack logging 0b8051b6 [ox/preloader] Fix alignment issue 5a426829 [nostalgia/core/studio] Cleanup TileSheet selection, fix copy/paste bug 9d2fe0e8 [studio] Add size function to Selection f1894699 [keel] Remove setAsset 27b38ed2 [keel,studio] Fix hotloading for files that get loaded as multiple types 2bb7c514 [studio/modlib] Fix type desc writing logic inversion 5177cfb0 [studio/modlib] Make Project::mkdir only mkdir if dir does not exist f9a14433 [studio/modlib] Add variant of ComboBox that takes callback e62426b0 [keel] Ensure consistent asset IDs in AssetManager af634bd4 [ox/fs] Add FileSystem::exists 49b859ec [studio/modlib] Give Selection constructors 19a41201 [studio/modlib] Make iterateSelection return errors properly f69b8afa [nostalgia] Remove use of deleted function 9c98b5e2 [studio/modlib] Remove color.hpp 1f87216d [nostalgia/core] Add applySelectionColor 94c59604 [nostalgia/core/opengl] Fix for Ox changes 8ee016c1 [studio/modlib] Add SelectionTracker dc20c667 [ox/std] Add conversion functions for geo types, cleanup 407e5424 [ox/std] Remove SmallMap dtor, replace timing code with steady_clock 3b188696 [ox/claw] Remove enum type from ClawFormat 0fab6c7c [ox/preloader] Remove debug code a72b865d [studio/modlib] Add function for exporting selection color c0479604 [studio,nostalgia/studio] Make executing UndoCommands report errors a1c89906 [nostalgia/studio] Make UndoCommand undo/redo return ox::Error 7fb0549c [nostalgia/core] Revert some auto formatting done by CLion... 37e65ab0 [nostalgia/core/studio] Fix Subsheet width to update properly 9105b1ec [ox/std] Fix Linux build fbeb0815 [ox/model] Fix type params in buildTypeId b882a47e [ox/std] Fix resize to set null terminator 660f2f56 [ox/std] Rework FileReader into StreamReader aa83c2a6 [nostalgia/core/studio] Remove some unnecessary copying 4a2b1fd7 [studio,keel] Make fileChanged emit UUID as well as path, add uuidUrlToUuid 08f958fb [ox/std] Add IntegerRange_c a651d45a [ox/std] Fix Vector insert functions 9e9f317c [studio] Make UndoCommand::mergeWith take a reference f5a02ce9 [nostalgia/core/gba] Fix build 6971c310 [studio] Add NoChangeException c47f48eb [keel] Add/cleanup UUID/path lookup functions 76771e7b [nostalgia/core] Add tileColumns and tileRows functions f6a0ae20 [ox/std] Fix some Windows warnings 752c8c1d [glutils] Fix type conversion that happened on Windows af3bff1a [glutils] Add FrameBuffer::sizef 87416e13 [ox/std] Make MallocaPtr call destructor 047b4396 [ox/std] Make Point and Size members int32_t 40b8da4d [studio/modlib] Cleanup 123c4125 [ox/std] Add SmallMap::pairs(), SmallMap model 963ec5d3 [ox/std] Add operator-> to SpanIterator 6df77a23 [glutils] Add size function to FrameBuffer df412cf8 [ox/std] Add missing typenames ae30ef36 Merge commit 'b66cef7127e97269fc6072a6f66ccc08990f6d2e' 095a1135 Merge commit 'f48824793cfce315971fe2e699ece198c7a79407' ce1836ab Merge commit '1e041bd2ebfe5ace7bed3906faf60345aa98a8bc' 7d1641fa Merge commit '420fa96463f59c4a4a7cd66b16b0ad01ab0d55e6' 423212b2 [studio] Add missing include 60da1063 Merge commit 'bd416f82e25f1b710ab2b7890274571dd3fcd53d' 60d1996f [keel] Minor optimization git-subtree-dir: deps/nostalgia git-subtree-split: 7d9f363bfa7a2c64f5c4bcfd0b6686f3f5678119
This commit is contained in:
		@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/math.hpp>
 | 
			
		||||
#include <ox/std/types.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
@@ -135,7 +136,7 @@ constexpr Color16 color16(int r, int g, int b, int a = 0) noexcept {
 | 
			
		||||
	return static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(r), 31))
 | 
			
		||||
	     | static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(g), 31) << 5)
 | 
			
		||||
	     | static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(b), 31) << 10)
 | 
			
		||||
        | static_cast<Color16>(a << 15);
 | 
			
		||||
         | static_cast<Color16>(a << 15);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
@@ -151,4 +152,13 @@ static_assert(color16(16, 31, 0) == 1008);
 | 
			
		||||
static_assert(color16(16, 31, 8) == 9200);
 | 
			
		||||
static_assert(color16(16, 32, 8) == 9200);
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr Color16 applySelectionColor(Color16 const color) noexcept {
 | 
			
		||||
	namespace core = nostalgia::core;
 | 
			
		||||
	auto const r = core::red16(color) / 2;
 | 
			
		||||
	auto const g = (core::green16(color) + 20) / 2;
 | 
			
		||||
	auto const b = (core::blue16(color) + 31) / 2;
 | 
			
		||||
	return core::color16(r, g, b);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -103,15 +103,21 @@ oxModelEnd()
 | 
			
		||||
 | 
			
		||||
void addEntry(TileSheetSet &set, ox::FileAddress path, int32_t begin = 0, int32_t size = -1) noexcept;
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
int tileColumns(Context&) noexcept;
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
int tileRows(Context&) noexcept;
 | 
			
		||||
 | 
			
		||||
ox::Error loadBgPalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		size_t palBank,
 | 
			
		||||
		Palette const&palette,
 | 
			
		||||
		CompactPalette const&palette,
 | 
			
		||||
		size_t page = 0) noexcept;
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpritePalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		Palette const&palette,
 | 
			
		||||
		CompactPalette const&palette,
 | 
			
		||||
		size_t page = 0) noexcept;
 | 
			
		||||
 | 
			
		||||
ox::Error loadBgPalette(
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,43 @@ struct PaletteV2 {
 | 
			
		||||
	ox::Vector<ox::Vector<Color16>> pages;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using Palette = PaletteV2;
 | 
			
		||||
struct PaletteV3 {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
 | 
			
		||||
	static constexpr auto TypeVersion = 3;
 | 
			
		||||
	static constexpr auto Preloadable = true;
 | 
			
		||||
	struct ColorInfo {
 | 
			
		||||
		static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette.ColorInfo";
 | 
			
		||||
		static constexpr auto TypeVersion = 3;
 | 
			
		||||
		ox::String name;
 | 
			
		||||
		constexpr ColorInfo() noexcept = default;
 | 
			
		||||
		constexpr explicit ColorInfo(ox::StringView pName) noexcept:
 | 
			
		||||
			name(pName) {}
 | 
			
		||||
		constexpr explicit ColorInfo(ox::String &&pName) noexcept:
 | 
			
		||||
			name(std::move(pName)) {}
 | 
			
		||||
	};
 | 
			
		||||
	ox::Vector<ColorInfo> colorInfo;
 | 
			
		||||
	ox::Vector<ox::Vector<Color16>> pages;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using Palette = PaletteV3;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
struct CompactPaletteV1 {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
 | 
			
		||||
	static constexpr auto TypeVersion = 2;
 | 
			
		||||
	static constexpr auto Preloadable = true;
 | 
			
		||||
	ox::Vector<ox::Vector<Color16>> pages{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using CompactPalette = CompactPaletteV1;
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr bool valid(Palette const&p) noexcept {
 | 
			
		||||
	auto const colors = p.colorInfo.size();
 | 
			
		||||
	return ox::all_of(p.pages.begin(), p.pages.end(), [colors](auto const&page) {
 | 
			
		||||
		return page.size() == colors;
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr Color16 color(Palette const&pal, size_t page, size_t idx) noexcept {
 | 
			
		||||
@@ -47,13 +83,53 @@ constexpr Color16 color(Palette const&pal, size_t page, size_t idx) noexcept {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr Color16 color(Palette const&pal, size_t idx) noexcept {
 | 
			
		||||
	auto constexpr page = 0;
 | 
			
		||||
	return color(pal, page, idx);
 | 
			
		||||
constexpr Color16 color(CompactPalette const&pal, size_t page, size_t idx) noexcept {
 | 
			
		||||
	if (page < pal.pages.size() && idx < pal.pages[page].size()) [[likely]] {
 | 
			
		||||
		return pal.pages[page][idx];
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr size_t colors(Palette const&pal, size_t page = 0) noexcept {
 | 
			
		||||
constexpr Color16 color(Palette const&pal, size_t idx) noexcept {
 | 
			
		||||
	return color(pal, 0, idx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr Color16 color(CompactPalette const&pal, size_t idx) noexcept {
 | 
			
		||||
	return color(pal, 0, idx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr auto &colors(Palette &pal, size_t page = 0) noexcept {
 | 
			
		||||
	return pal.pages[page];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr auto &colors(CompactPalette &pal, size_t page = 0) noexcept {
 | 
			
		||||
	return pal.pages[page];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr auto &colors(Palette const&pal, size_t page = 0) noexcept {
 | 
			
		||||
	return pal.pages[page];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr auto &colors(CompactPalette const&pal, size_t page = 0) noexcept {
 | 
			
		||||
	return pal.pages[page];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr size_t colorCnt(Palette const&pal, size_t page = 0) noexcept {
 | 
			
		||||
	if (page < pal.pages.size()) [[likely]] {
 | 
			
		||||
		return pal.pages[page].size();
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr size_t colorCnt(CompactPalette const&pal, size_t page = 0) noexcept {
 | 
			
		||||
	if (page < pal.pages.size()) [[likely]] {
 | 
			
		||||
		return pal.pages[page].size();
 | 
			
		||||
	}
 | 
			
		||||
@@ -69,6 +145,15 @@ constexpr size_t largestPage(Palette const&pal) noexcept {
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr size_t largestPage(CompactPalette const&pal) noexcept {
 | 
			
		||||
	size_t out{};
 | 
			
		||||
	for (auto const&page : pal.pages) {
 | 
			
		||||
		out = ox::max(out, page.size());
 | 
			
		||||
	}
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
oxModelBegin(NostalgiaPalette)
 | 
			
		||||
	oxModelField(colors)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
@@ -81,4 +166,17 @@ oxModelBegin(PaletteV2)
 | 
			
		||||
	oxModelField(pages)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
oxModelBegin(PaletteV3::ColorInfo)
 | 
			
		||||
	oxModelField(name)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
oxModelBegin(PaletteV3)
 | 
			
		||||
	oxModelField(colorInfo)
 | 
			
		||||
	oxModelField(pages)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
oxModelBegin(CompactPaletteV1)
 | 
			
		||||
	oxModelField(pages)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -222,6 +222,14 @@ ox::Error setPixelCount(TileSheet::SubSheet &ss, int8_t pBpp, std::size_t cnt) n
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
unsigned pixelCnt(TileSheet::SubSheet const&ss, int8_t pBpp) noexcept;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @param ss
 | 
			
		||||
 * @param pBpp
 | 
			
		||||
 * @param sz size of Subsheet in tiles (not pixels)
 | 
			
		||||
 */
 | 
			
		||||
ox::Error resizeSubsheet(TileSheet::SubSheet &ss, int8_t pBpp, ox::Size const&sz) noexcept;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * validateSubSheetIdx takes a SubSheetIdx and moves the index to the
 | 
			
		||||
 * preceding or parent sheet if the current corresponding sheet does
 | 
			
		||||
 
 | 
			
		||||
@@ -22,8 +22,6 @@
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
constexpr auto GbaTileColumns = 32;
 | 
			
		||||
constexpr auto GbaTileRows = 32;
 | 
			
		||||
constexpr auto SpriteCount = 128;
 | 
			
		||||
 | 
			
		||||
struct GbaTileMapTarget {
 | 
			
		||||
@@ -138,13 +136,13 @@ ox::Error initGfx(Context&, InitParams const&) noexcept {
 | 
			
		||||
ox::Error loadBgPalette(
 | 
			
		||||
		Context&,
 | 
			
		||||
		size_t palBank,
 | 
			
		||||
		Palette const&palette,
 | 
			
		||||
		CompactPalette const&palette,
 | 
			
		||||
		size_t page) noexcept {
 | 
			
		||||
	if (palette.pages.empty()) {
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
	auto const paletteMem = MEM_BG_PALETTE + palBank * 16;
 | 
			
		||||
	for (auto i = 0u; i < colors(palette, page); ++i) {
 | 
			
		||||
	for (auto i = 0u; i < colorCnt(palette, page); ++i) {
 | 
			
		||||
		paletteMem[i] = color(palette, page, i);
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
@@ -152,13 +150,13 @@ ox::Error loadBgPalette(
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpritePalette(
 | 
			
		||||
		Context&,
 | 
			
		||||
		Palette const&palette,
 | 
			
		||||
		CompactPalette const&palette,
 | 
			
		||||
		size_t page) noexcept {
 | 
			
		||||
	if (palette.pages.empty()) {
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
	auto const paletteMem = MEM_SPRITE_PALETTE;
 | 
			
		||||
	for (auto i = 0u; i < colors(palette, page); ++i) {
 | 
			
		||||
	for (auto i = 0u; i < colorCnt(palette, page); ++i) {
 | 
			
		||||
		paletteMem[i] = color(palette, page, i);
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
@@ -168,14 +166,14 @@ ox::Error loadBgPalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		size_t palBank,
 | 
			
		||||
		ox::FileAddress const&paletteAddr) noexcept {
 | 
			
		||||
	oxRequire(pal, keel::readObj<Palette>(keelCtx(ctx), paletteAddr));
 | 
			
		||||
	oxRequire(pal, keel::readObj<CompactPalette>(keelCtx(ctx), paletteAddr));
 | 
			
		||||
	return loadBgPalette(ctx, palBank, *pal, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpritePalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::FileAddress const&paletteAddr) noexcept {
 | 
			
		||||
	oxRequire(pal, keel::readObj<Palette>(keelCtx(ctx), paletteAddr));
 | 
			
		||||
	oxRequire(pal, keel::readObj<CompactPalette>(keelCtx(ctx), paletteAddr));
 | 
			
		||||
	return loadSpritePalette(ctx, *pal, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -284,8 +282,8 @@ ox::Error loadSpriteTileSheet(
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setBgTile(Context&, uint_t bgIdx, int column, int row, BgTile const&tile) noexcept {
 | 
			
		||||
	auto const tileIdx = static_cast<std::size_t>(row * GbaTileColumns + column);
 | 
			
		||||
void setBgTile(Context &ctx, uint_t bgIdx, int column, int row, BgTile const&tile) noexcept {
 | 
			
		||||
	auto const tileIdx = static_cast<std::size_t>(row * tileColumns(ctx) + column);
 | 
			
		||||
	// see Tonc 9.3
 | 
			
		||||
	MEM_BG_MAP[bgIdx][tileIdx] =
 | 
			
		||||
			static_cast<uint16_t>(tile.tileIdx & 0b1'1111'1111) |
 | 
			
		||||
@@ -294,8 +292,8 @@ void setBgTile(Context&, uint_t bgIdx, int column, int row, BgTile const&tile) n
 | 
			
		||||
			static_cast<uint16_t>(tile.palBank << 0xc);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void clearBg(Context&, uint_t bgIdx) noexcept {
 | 
			
		||||
	memset(MEM_BG_MAP[bgIdx].data(), 0, GbaTileRows * GbaTileColumns);
 | 
			
		||||
void clearBg(Context &ctx, uint_t bgIdx) noexcept {
 | 
			
		||||
	memset(MEM_BG_MAP[bgIdx].data(), 0, static_cast<size_t>(tileRows(ctx) * tileColumns(ctx)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t bgStatus(Context&) noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,17 @@
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
constexpr auto GbaTileColumns = 32;
 | 
			
		||||
constexpr auto GbaTileRows = 32;
 | 
			
		||||
 | 
			
		||||
int tileColumns(Context&) noexcept {
 | 
			
		||||
	return GbaTileColumns;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int tileRows(Context&) noexcept {
 | 
			
		||||
	return GbaTileRows;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// map ASCII values to the nostalgia charset
 | 
			
		||||
constexpr ox::Array<char, 128> charMap = {
 | 
			
		||||
	0,
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,8 @@ static class: public keel::Module {
 | 
			
		||||
	private:
 | 
			
		||||
		NostalgiaPaletteToPaletteV1Converter m_nostalgiaPaletteToPaletteV1Converter;
 | 
			
		||||
		PaletteV1ToPaletteV2Converter m_paletteV1ToPaletteV2Converter;
 | 
			
		||||
		PaletteV2ToPaletteV3Converter m_paletteV2ToPaletteV3Converter;
 | 
			
		||||
		PaletteToCompactPaletteConverter m_paletteToCompactPaletteConverter;
 | 
			
		||||
		TileSheetV1ToTileSheetV2Converter m_tileSheetV1ToTileSheetV2Converter;
 | 
			
		||||
		TileSheetV2ToTileSheetV3Converter m_tileSheetV2ToTileSheetV3Converter;
 | 
			
		||||
		TileSheetV3ToTileSheetV4Converter m_tileSheetV3ToTileSheetV4Converter;
 | 
			
		||||
@@ -47,6 +49,8 @@ static class: public keel::Module {
 | 
			
		||||
			return {
 | 
			
		||||
				&m_nostalgiaPaletteToPaletteV1Converter,
 | 
			
		||||
				&m_paletteV1ToPaletteV2Converter,
 | 
			
		||||
				&m_paletteV2ToPaletteV3Converter,
 | 
			
		||||
				&m_paletteToCompactPaletteConverter,
 | 
			
		||||
				&m_tileSheetV1ToTileSheetV2Converter,
 | 
			
		||||
				&m_tileSheetV2ToTileSheetV3Converter,
 | 
			
		||||
				&m_tileSheetV3ToTileSheetV4Converter,
 | 
			
		||||
@@ -71,8 +75,10 @@ static class: public keel::Module {
 | 
			
		||||
				},
 | 
			
		||||
				[](keel::Context &ctx, ox::Buffer &buff, ox::StringView typeId) -> ox::Result<bool> {
 | 
			
		||||
					if (typeId == ox::ModelTypeId_v<NostalgiaPalette> ||
 | 
			
		||||
					    typeId == ox::ModelTypeId_v<PaletteV1>) {
 | 
			
		||||
						oxReturnError(keel::convertBuffToBuff<Palette>(
 | 
			
		||||
					    typeId == ox::ModelTypeId_v<PaletteV1> ||
 | 
			
		||||
						typeId == ox::ModelTypeId_v<PaletteV2> ||
 | 
			
		||||
						typeId == ox::ModelTypeId_v<PaletteV3>) {
 | 
			
		||||
						oxReturnError(keel::convertBuffToBuff<CompactPalette>(
 | 
			
		||||
								ctx, buff, ox::ClawFormat::Metal).moveTo(buff));
 | 
			
		||||
						return true;
 | 
			
		||||
					}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,27 @@ ox::Error PaletteV1ToPaletteV2Converter::convert(
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error PaletteV2ToPaletteV3Converter::convert(
 | 
			
		||||
		keel::Context&,
 | 
			
		||||
		PaletteV2 &src,
 | 
			
		||||
		PaletteV3 &dst) const noexcept {
 | 
			
		||||
	dst.pages = std::move(src.pages);
 | 
			
		||||
	if (!dst.pages.empty()) {
 | 
			
		||||
		for (size_t i = 0; i < dst.pages[0].size(); ++i) {
 | 
			
		||||
			dst.colorInfo.emplace_back(ox::sfmt("Color {}", i));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error PaletteToCompactPaletteConverter::convert(
 | 
			
		||||
		keel::Context&,
 | 
			
		||||
		Palette &src,
 | 
			
		||||
		CompactPalette &dst) const noexcept {
 | 
			
		||||
	dst.pages = std::move(src.pages);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetV1ToTileSheetV2Converter::convert(
 | 
			
		||||
		keel::Context&,
 | 
			
		||||
		TileSheetV1 &src,
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,15 @@ class NostalgiaPaletteToPaletteV1Converter: public keel::Converter<NostalgiaPale
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PaletteV1ToPaletteV2Converter: public keel::Converter<PaletteV1, PaletteV2> {
 | 
			
		||||
	ox::Error convert(keel::Context&, PaletteV1 &src, PaletteV2 &dst) const noexcept final;
 | 
			
		||||
		ox::Error convert(keel::Context&, PaletteV1 &src, PaletteV2 &dst) const noexcept final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PaletteV2ToPaletteV3Converter: public keel::Converter<PaletteV2, PaletteV3> {
 | 
			
		||||
		ox::Error convert(keel::Context&, PaletteV2 &src, PaletteV3 &dst) const noexcept final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PaletteToCompactPaletteConverter: public keel::Converter<Palette, CompactPalette> {
 | 
			
		||||
	ox::Error convert(keel::Context&, Palette &src, CompactPalette &dst) const noexcept final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TileSheetV1ToTileSheetV2Converter: public keel::Converter<TileSheetV1, TileSheetV2> {
 | 
			
		||||
 
 | 
			
		||||
@@ -364,7 +364,7 @@ static void loadPalette(
 | 
			
		||||
		ox::Array<GLfloat, 1024> &palette,
 | 
			
		||||
		size_t palOffset,
 | 
			
		||||
		GLuint shaderPgrm,
 | 
			
		||||
		Palette const&pal,
 | 
			
		||||
		CompactPalette const&pal,
 | 
			
		||||
		size_t page = 0) noexcept {
 | 
			
		||||
	static constexpr std::size_t ColorCnt = 256;
 | 
			
		||||
	for (auto i = palOffset; auto const c : pal.pages[page]) {
 | 
			
		||||
@@ -385,7 +385,8 @@ static void setSprite(
 | 
			
		||||
		uint_t const idx,
 | 
			
		||||
		Sprite const&s) noexcept {
 | 
			
		||||
	// Tonc Table 8.4
 | 
			
		||||
	static constexpr ox::Array<ox::Vec<uint_t>, 12> dimensions{
 | 
			
		||||
	struct Sz { uint_t x{}, y{}; };
 | 
			
		||||
	static constexpr ox::Array<Sz, 12> dimensions{
 | 
			
		||||
			// col 0
 | 
			
		||||
			{1, 1}, // 0, 0
 | 
			
		||||
			{2, 2}, // 0, 1
 | 
			
		||||
@@ -515,7 +516,7 @@ static ox::Result<TileSheetData> normalizeTileSheet(
 | 
			
		||||
ox::Error loadBgPalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		size_t palBank,
 | 
			
		||||
		Palette const&palette,
 | 
			
		||||
		CompactPalette const&palette,
 | 
			
		||||
		size_t page) noexcept {
 | 
			
		||||
	renderer::loadPalette(ctx.bgPalette, palBank * 16 * 4, ctx.bgShader, palette, page);
 | 
			
		||||
	return {};
 | 
			
		||||
@@ -523,7 +524,7 @@ ox::Error loadBgPalette(
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpritePalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		Palette const&palette,
 | 
			
		||||
		CompactPalette const&palette,
 | 
			
		||||
		size_t page) noexcept {
 | 
			
		||||
	ox::Array<GLfloat, 1024> pal;
 | 
			
		||||
	renderer::loadPalette(pal, 0, ctx.spriteShader, palette, page);
 | 
			
		||||
@@ -535,7 +536,7 @@ ox::Error loadBgPalette(
 | 
			
		||||
		size_t palBank,
 | 
			
		||||
		ox::FileAddress const&paletteAddr) noexcept {
 | 
			
		||||
	auto &kctx = keelCtx(ctx.turbineCtx);
 | 
			
		||||
	oxRequire(palette, readObj<Palette>(kctx, paletteAddr));
 | 
			
		||||
	oxRequire(palette, readObj<CompactPalette>(kctx, paletteAddr));
 | 
			
		||||
	renderer::loadPalette(ctx.bgPalette, palBank * 16 * 4, ctx.bgShader, *palette);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
@@ -544,7 +545,7 @@ ox::Error loadSpritePalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::FileAddress const&paletteAddr) noexcept {
 | 
			
		||||
	auto &kctx = keelCtx(ctx.turbineCtx);
 | 
			
		||||
	oxRequire(palette, readObj<Palette>(kctx, paletteAddr));
 | 
			
		||||
	oxRequire(palette, readObj<CompactPalette>(kctx, paletteAddr));
 | 
			
		||||
	ox::Array<GLfloat, 1024> pal;
 | 
			
		||||
	renderer::loadPalette(pal, 0, ctx.spriteShader, *palette);
 | 
			
		||||
	return {};
 | 
			
		||||
 
 | 
			
		||||
@@ -15,11 +15,16 @@
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
PaletteEditorImGui::PaletteEditorImGui(studio::StudioContext &sctx, ox::CRStringView path):
 | 
			
		||||
	Editor(path),
 | 
			
		||||
	m_sctx(sctx),
 | 
			
		||||
	m_tctx(sctx.tctx),
 | 
			
		||||
	m_pal(*keel::readObj<Palette>(keelCtx(m_tctx), ox::FileAddress(itemPath())).unwrapThrow()) {
 | 
			
		||||
namespace ig = studio::ig;
 | 
			
		||||
 | 
			
		||||
PaletteEditorImGui::PaletteEditorImGui(studio::StudioContext &sctx, ox::StringView const path):
 | 
			
		||||
		Editor(path),
 | 
			
		||||
		m_sctx(sctx),
 | 
			
		||||
		m_tctx(sctx.tctx),
 | 
			
		||||
		m_pal(*keel::readObj<Palette>(keelCtx(m_tctx), itemPath()).unwrapThrow()) {
 | 
			
		||||
	if (!valid(m_pal)) {
 | 
			
		||||
		throw OxException(1, "PaletteEditorImGui: invalid Palette object");
 | 
			
		||||
	}
 | 
			
		||||
	undoStack()->changeTriggered.connect(this, &PaletteEditorImGui::handleCommand);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -36,10 +41,9 @@ void PaletteEditorImGui::keyStateChanged(turbine::Key key, bool down) {
 | 
			
		||||
		if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) {
 | 
			
		||||
			m_selectedColorRow =
 | 
			
		||||
					ox::min<std::size_t>(
 | 
			
		||||
					  static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), m_pal.pages.size() - 1);
 | 
			
		||||
						static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), m_pal.pages.size() - 1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PaletteEditorImGui::draw(studio::StudioContext&) noexcept {
 | 
			
		||||
@@ -58,7 +62,12 @@ void PaletteEditorImGui::draw(studio::StudioContext&) noexcept {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error PaletteEditorImGui::saveItem() noexcept {
 | 
			
		||||
	return m_sctx.project->writeObj(itemPath(), m_pal);
 | 
			
		||||
	return m_sctx.project->writeObj(itemPath(), m_pal, ox::ClawFormat::Organic);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PaletteEditorImGui::drawColumnLeftAlign(ox::CStringView txt) noexcept {
 | 
			
		||||
	ImGui::TableNextColumn();
 | 
			
		||||
	ImGui::Text("%s", txt.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PaletteEditorImGui::drawColumn(ox::CStringView txt) noexcept {
 | 
			
		||||
@@ -71,42 +80,39 @@ void PaletteEditorImGui::drawColumn(ox::CStringView txt) noexcept {
 | 
			
		||||
void PaletteEditorImGui::drawColorsEditor() noexcept {
 | 
			
		||||
	constexpr auto tableFlags = ImGuiTableFlags_RowBg;
 | 
			
		||||
	auto const colorsSz = ImGui::GetContentRegionAvail();
 | 
			
		||||
	auto const colorEditor = m_selectedColorRow < colors(m_pal, m_page);
 | 
			
		||||
	auto const colorEditor = m_selectedColorRow < colorCnt(m_pal, m_page);
 | 
			
		||||
	auto const colorEditorWidth = 220;
 | 
			
		||||
	static constexpr auto toolbarHeight = 40;
 | 
			
		||||
	{
 | 
			
		||||
		auto const sz = ImVec2(70, 24);
 | 
			
		||||
		if (ImGui::Button("Add", sz)) {
 | 
			
		||||
			auto const colorSz = static_cast<int>(colors(m_pal, m_page));
 | 
			
		||||
			auto const colorSz = colorCnt(m_pal, m_page);
 | 
			
		||||
			constexpr Color16 c = 0;
 | 
			
		||||
			undoStack()->push(ox::make_unique<AddColorCommand>(&m_pal, c, m_page, colorSz));
 | 
			
		||||
			std::ignore = pushCommand<AddColorCommand>(m_pal, c, colorSz);
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::SameLine();
 | 
			
		||||
		ImGui::BeginDisabled(m_selectedColorRow >= colors(m_pal, m_page));
 | 
			
		||||
		ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page));
 | 
			
		||||
		{
 | 
			
		||||
			if (ImGui::Button("Remove", sz)) {
 | 
			
		||||
				undoStack()->push(
 | 
			
		||||
						ox::make_unique<RemoveColorCommand>(
 | 
			
		||||
								&m_pal,
 | 
			
		||||
								color(m_pal, m_page, static_cast<std::size_t>(m_selectedColorRow)),
 | 
			
		||||
								m_page,
 | 
			
		||||
								static_cast<int>(m_selectedColorRow)));
 | 
			
		||||
				m_selectedColorRow = ox::min(colors(m_pal, m_page) - 1, m_selectedColorRow);
 | 
			
		||||
				std::ignore = pushCommand<RemoveColorCommand>(m_pal, m_selectedColorRow);
 | 
			
		||||
				m_selectedColorRow = ox::min(colorCnt(m_pal, m_page) - 1, m_selectedColorRow);
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			ImGui::BeginDisabled(m_selectedColorRow <= 0);
 | 
			
		||||
			{
 | 
			
		||||
				if (ImGui::Button("Move Up", sz)) {
 | 
			
		||||
					undoStack()->push(ox::make_unique<MoveColorCommand>(&m_pal, m_page, m_selectedColorRow, -1));
 | 
			
		||||
					std::ignore = pushCommand<MoveColorCommand>(
 | 
			
		||||
							m_pal, m_page, m_selectedColorRow, m_selectedColorRow - 1);
 | 
			
		||||
					--m_selectedColorRow;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::EndDisabled();
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			ImGui::BeginDisabled(m_selectedColorRow >= colors(m_pal, m_page) - 1);
 | 
			
		||||
			ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page) - 1);
 | 
			
		||||
			{
 | 
			
		||||
				if (ImGui::Button("Move Down", sz)) {
 | 
			
		||||
					undoStack()->push(ox::make_unique<MoveColorCommand>(&m_pal, m_page, m_selectedColorRow, 1));
 | 
			
		||||
					std::ignore = pushCommand<MoveColorCommand>(
 | 
			
		||||
							m_pal, m_page, m_selectedColorRow, m_selectedColorRow + 1);
 | 
			
		||||
					++m_selectedColorRow;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -114,26 +120,35 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndDisabled();
 | 
			
		||||
	}
 | 
			
		||||
	auto const tblWidth = (colorsSz.x - colorEditorWidth - 8) * colorEditor;
 | 
			
		||||
	ImGui::BeginTable("Colors", 5, tableFlags, ImVec2(tblWidth, colorsSz.y - (toolbarHeight + 5)));
 | 
			
		||||
	auto const tblWidth = (colorsSz.x - static_cast<float>(colorEditorWidth) - 8.f)
 | 
			
		||||
			* static_cast<float>(colorEditor);
 | 
			
		||||
	ImGui::BeginTable(
 | 
			
		||||
			"Colors",
 | 
			
		||||
			6,
 | 
			
		||||
			tableFlags,
 | 
			
		||||
			ImVec2(tblWidth, colorsSz.y - (toolbarHeight + 5)));
 | 
			
		||||
	{
 | 
			
		||||
		ImGui::TableSetupColumn("Idx", ImGuiTableColumnFlags_WidthFixed, 25);
 | 
			
		||||
		ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 50);
 | 
			
		||||
		ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 50);
 | 
			
		||||
		ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 50);
 | 
			
		||||
		ImGui::TableSetupColumn("Color Preview", ImGuiTableColumnFlags_NoHide);
 | 
			
		||||
		ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 100);
 | 
			
		||||
		ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 40);
 | 
			
		||||
		ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 40);
 | 
			
		||||
		ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 40);
 | 
			
		||||
		ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_NoHide);
 | 
			
		||||
		ImGui::TableHeadersRow();
 | 
			
		||||
		for (auto i = 0u; auto const c : m_pal.pages[m_page]) {
 | 
			
		||||
		for (auto i = 0u; auto const&c : m_pal.pages[m_page]) {
 | 
			
		||||
			ImGui::PushID(static_cast<int>(i));
 | 
			
		||||
			ImGui::TableNextRow();
 | 
			
		||||
			drawColumn(i);
 | 
			
		||||
			drawColumn(i + 1);
 | 
			
		||||
			drawColumnLeftAlign(m_pal.colorInfo[i].name);
 | 
			
		||||
			drawColumn(red16(c));
 | 
			
		||||
			drawColumn(green16(c));
 | 
			
		||||
			drawColumn(blue16(c));
 | 
			
		||||
			ImGui::TableNextColumn();
 | 
			
		||||
			auto const ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
 | 
			
		||||
			auto const ic = ImGui::GetColorU32(
 | 
			
		||||
					ImVec4(redf(c), greenf(c), bluef(c), 1));
 | 
			
		||||
			ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
 | 
			
		||||
			if (ImGui::Selectable("##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) {
 | 
			
		||||
			if (ImGui::Selectable(
 | 
			
		||||
					"##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) {
 | 
			
		||||
				m_selectedColorRow = i;
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::PopID();
 | 
			
		||||
@@ -155,28 +170,30 @@ void PaletteEditorImGui::drawPagesEditor() noexcept {
 | 
			
		||||
	constexpr auto toolbarHeight = 40;
 | 
			
		||||
	auto const btnSz = ImVec2(paneSz.x / 3 - 5.5f, 24);
 | 
			
		||||
	if (ImGui::Button("Add", btnSz)) {
 | 
			
		||||
		undoStack()->push(ox::make_unique<AddPageCommand>(m_pal));
 | 
			
		||||
		std::ignore = pushCommand<DuplicatePageCommand>(m_pal, 0u, m_pal.pages.size());
 | 
			
		||||
		m_page = m_pal.pages.size() - 1;
 | 
			
		||||
	}
 | 
			
		||||
	ImGui::SameLine();
 | 
			
		||||
	if (ImGui::Button("Remove", btnSz)) {
 | 
			
		||||
		undoStack()->push(ox::make_unique<RemovePageCommand>(m_pal, m_page));
 | 
			
		||||
		std::ignore = pushCommand<RemovePageCommand>(m_pal, m_page);
 | 
			
		||||
		m_page = std::min(m_page, m_pal.pages.size() - 1);
 | 
			
		||||
	}
 | 
			
		||||
	ImGui::SameLine();
 | 
			
		||||
	if (ImGui::Button("Duplicate", btnSz)) {
 | 
			
		||||
		undoStack()->push(ox::make_unique<DuplicatePageCommand>(m_pal, m_page, m_pal.pages.size()));
 | 
			
		||||
		std::ignore = pushCommand<DuplicatePageCommand>(m_pal, m_page, m_pal.pages.size());
 | 
			
		||||
	}
 | 
			
		||||
	ImGui::BeginTable("PageSelect", 2, tableFlags, ImVec2(paneSz.x, paneSz.y - (toolbarHeight + 5)));
 | 
			
		||||
	ImGui::BeginTable(
 | 
			
		||||
			"PageSelect",
 | 
			
		||||
			2,
 | 
			
		||||
			tableFlags,
 | 
			
		||||
			ImVec2(paneSz.x, paneSz.y - (toolbarHeight + 5)));
 | 
			
		||||
	{
 | 
			
		||||
		ImGui::TableSetupColumn("Page", ImGuiTableColumnFlags_WidthFixed, 60);
 | 
			
		||||
		ImGui::TableSetupColumn("Colors", ImGuiTableColumnFlags_NoHide);
 | 
			
		||||
		ImGui::TableHeadersRow();
 | 
			
		||||
		for (auto i = 0u; i < m_pal.pages.size(); ++i) {
 | 
			
		||||
			ImGui::PushID(static_cast<int>(i));
 | 
			
		||||
			ImGui::TableNextRow();
 | 
			
		||||
			drawColumn(i + 1);
 | 
			
		||||
			drawColumn(colors(m_pal, i));
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			if (ImGui::Selectable("##PageRow", i == m_page, ImGuiSelectableFlags_SpanAllColumns)) {
 | 
			
		||||
				m_page = i;
 | 
			
		||||
@@ -193,20 +210,33 @@ void PaletteEditorImGui::drawColorEditor() noexcept {
 | 
			
		||||
	int g = green16(c);
 | 
			
		||||
	int b = blue16(c);
 | 
			
		||||
	int const a = alpha16(c);
 | 
			
		||||
	auto const¤tName = m_pal.colorInfo[m_selectedColorRow].name;
 | 
			
		||||
	ox::IString<50> name;
 | 
			
		||||
	name = currentName;
 | 
			
		||||
	ImGui::InputText("Name", name.data(), name.cap() + 1);
 | 
			
		||||
	ImGui::Separator();
 | 
			
		||||
	ImGui::InputInt("Red", &r, 1, 5);
 | 
			
		||||
	ImGui::InputInt("Green", &g, 1, 5);
 | 
			
		||||
	ImGui::InputInt("Blue", &b, 1, 5);
 | 
			
		||||
	if (ig::PushButton("Apply to all pages", {-1, ig::BtnSz.y})) {
 | 
			
		||||
		std::ignore = pushCommand<ApplyColorAllPagesCommand>(
 | 
			
		||||
				m_pal, m_page, m_selectedColorRow);
 | 
			
		||||
	}
 | 
			
		||||
	r = ox::max(r, 0);
 | 
			
		||||
	g = ox::max(g, 0);
 | 
			
		||||
	b = ox::max(b, 0);
 | 
			
		||||
	auto const newColor = color16(r, g, b, a);
 | 
			
		||||
	if (c != newColor) {
 | 
			
		||||
		undoStack()->push(ox::make_unique<UpdateColorCommand>(
 | 
			
		||||
				&m_pal, m_page, static_cast<int>(m_selectedColorRow), c, newColor));
 | 
			
		||||
		std::ignore = pushCommand<UpdateColorCommand>(m_pal, m_page, m_selectedColorRow, newColor);
 | 
			
		||||
	}
 | 
			
		||||
	if (currentName != name.data()) {
 | 
			
		||||
		std::ignore = pushCommand<UpdateColorInfoCommand>(
 | 
			
		||||
				m_pal, m_selectedColorRow, Palette::ColorInfo{name.data()});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error PaletteEditorImGui::handleCommand(studio::UndoCommand const*cmd) noexcept {
 | 
			
		||||
	if (dynamic_cast<AddPageCommand const*>(cmd)) {
 | 
			
		||||
		m_page = m_pal.pages.size() - 1;
 | 
			
		||||
	} else if (dynamic_cast<RemovePageCommand const*>(cmd)) {
 | 
			
		||||
	if (dynamic_cast<RemovePageCommand const*>(cmd)) {
 | 
			
		||||
		m_page = ox::min(m_page, m_pal.pages.size() - 1);
 | 
			
		||||
	} else if (auto const dupPageCmd = dynamic_cast<DuplicatePageCommand const*>(cmd)) {
 | 
			
		||||
		m_page = ox::clamp<size_t>(dupPageCmd->insertIdx(), 0, m_pal.pages.size() - 1);
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ class PaletteEditorImGui: public studio::Editor {
 | 
			
		||||
		size_t m_page = 0;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		PaletteEditorImGui(studio::StudioContext &sctx, ox::CRStringView path);
 | 
			
		||||
		PaletteEditorImGui(studio::StudioContext &sctx, ox::StringView path);
 | 
			
		||||
 | 
			
		||||
		void keyStateChanged(turbine::Key key, bool down) override;
 | 
			
		||||
 | 
			
		||||
@@ -31,6 +31,8 @@ class PaletteEditorImGui: public studio::Editor {
 | 
			
		||||
		ox::Error saveItem() noexcept final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		static void drawColumnLeftAlign(ox::CStringView txt) noexcept;
 | 
			
		||||
 | 
			
		||||
		static void drawColumn(ox::CStringView txt) noexcept;
 | 
			
		||||
 | 
			
		||||
		static void drawColumn(ox::Integer_c auto i) noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,20 +6,44 @@
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
AddPageCommand::AddPageCommand(Palette &pal) noexcept:
 | 
			
		||||
	m_pal(pal) {
 | 
			
		||||
ApplyColorAllPagesCommand::ApplyColorAllPagesCommand(Palette &pal, size_t const page, size_t const idx):
 | 
			
		||||
	m_pal(pal),
 | 
			
		||||
	m_page(page),
 | 
			
		||||
	m_idx(idx),
 | 
			
		||||
	m_origColors([this] {
 | 
			
		||||
		ox::Vector<Color16> colors;
 | 
			
		||||
		colors.reserve(m_pal.pages.size());
 | 
			
		||||
		for (auto const&p : m_pal.pages) {
 | 
			
		||||
			colors.emplace_back(p[m_idx]);
 | 
			
		||||
		}
 | 
			
		||||
		return colors;
 | 
			
		||||
	}()) {
 | 
			
		||||
	auto const c = color(m_pal, m_page, m_idx);
 | 
			
		||||
	if (ox::all_of(m_pal.pages.begin(), m_pal.pages.end(), [this, c](ox::SpanView<Color16> const page) {
 | 
			
		||||
			return page[m_idx] == c;
 | 
			
		||||
		})) {
 | 
			
		||||
		throw studio::NoChangesException();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int AddPageCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::AddPage);
 | 
			
		||||
int ApplyColorAllPagesCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::ApplyColorAllPages);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddPageCommand::redo() noexcept {
 | 
			
		||||
	m_pal.pages.emplace_back();
 | 
			
		||||
ox::Error ApplyColorAllPagesCommand::redo() noexcept {
 | 
			
		||||
	auto const c = color(m_pal, m_page, m_idx);
 | 
			
		||||
	for (auto &page : m_pal.pages) {
 | 
			
		||||
		page[m_idx] = c;
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddPageCommand::undo() noexcept {
 | 
			
		||||
	std::ignore = m_pal.pages.erase(static_cast<std::size_t>(m_pal.pages.size() - 1));
 | 
			
		||||
ox::Error ApplyColorAllPagesCommand::undo() noexcept {
 | 
			
		||||
	for (size_t p = 0u; auto &page : m_pal.pages) {
 | 
			
		||||
		page[m_idx] = m_origColors[p];
 | 
			
		||||
		++p;
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -37,13 +61,14 @@ int DuplicatePageCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::DuplicatePage);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DuplicatePageCommand::redo() noexcept {
 | 
			
		||||
ox::Error DuplicatePageCommand::redo() noexcept {
 | 
			
		||||
	m_pal.pages.emplace(m_dstIdx, std::move(m_page));
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DuplicatePageCommand::undo() noexcept {
 | 
			
		||||
ox::Error DuplicatePageCommand::undo() noexcept {
 | 
			
		||||
	m_page = std::move(m_pal.pages[m_dstIdx]);
 | 
			
		||||
	std::ignore = m_pal.pages.erase(static_cast<std::size_t>(m_dstIdx));
 | 
			
		||||
	return m_pal.pages.erase(static_cast<std::size_t>(m_dstIdx)).error;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t DuplicatePageCommand::insertIdx() const noexcept {
 | 
			
		||||
@@ -53,86 +78,144 @@ size_t DuplicatePageCommand::insertIdx() const noexcept {
 | 
			
		||||
 | 
			
		||||
RemovePageCommand::RemovePageCommand(Palette &pal, size_t idx) noexcept:
 | 
			
		||||
	m_pal(pal),
 | 
			
		||||
	m_idx(idx) {
 | 
			
		||||
}
 | 
			
		||||
	m_idx(idx) {}
 | 
			
		||||
 | 
			
		||||
int RemovePageCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::RemovePage);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RemovePageCommand::redo() noexcept {
 | 
			
		||||
	m_page = std::move(m_pal.pages[m_idx]);
 | 
			
		||||
	std::ignore = m_pal.pages.erase(static_cast<std::size_t>(m_idx));
 | 
			
		||||
ox::Error RemovePageCommand::redo() noexcept {
 | 
			
		||||
	m_page = std::move(colors(m_pal, m_idx));
 | 
			
		||||
	return m_pal.pages.erase(static_cast<std::size_t>(m_idx)).error;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RemovePageCommand::undo() noexcept {
 | 
			
		||||
	m_pal.pages.insert(m_idx, std::move(m_page));
 | 
			
		||||
ox::Error RemovePageCommand::undo() noexcept {
 | 
			
		||||
	m_pal.pages.emplace(m_idx, std::move(m_page));
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AddColorCommand::AddColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept {
 | 
			
		||||
	m_pal = pal;
 | 
			
		||||
	m_color = color;
 | 
			
		||||
	m_page = page;
 | 
			
		||||
	m_idx = idx;
 | 
			
		||||
}
 | 
			
		||||
AddColorCommand::AddColorCommand(Palette &pal, Color16 const color, size_t const idx) noexcept:
 | 
			
		||||
	m_pal(pal),
 | 
			
		||||
	m_color(color),
 | 
			
		||||
	m_idx(idx) {}
 | 
			
		||||
 | 
			
		||||
int AddColorCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::AddColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddColorCommand::redo() noexcept {
 | 
			
		||||
	m_pal->pages[m_page].insert(static_cast<std::size_t>(m_idx), m_color);
 | 
			
		||||
ox::Error AddColorCommand::redo() noexcept {
 | 
			
		||||
	for (auto &page : m_pal.pages) {
 | 
			
		||||
		page.emplace(static_cast<size_t>(m_idx), m_color);
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddColorCommand::undo() noexcept {
 | 
			
		||||
	std::ignore = m_pal->pages[m_page].erase(static_cast<std::size_t>(m_idx));
 | 
			
		||||
ox::Error AddColorCommand::undo() noexcept {
 | 
			
		||||
	for (auto &page : m_pal.pages) {
 | 
			
		||||
		oxReturnError(page.erase(static_cast<std::size_t>(m_idx)));
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
RemoveColorCommand::RemoveColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept {
 | 
			
		||||
	m_pal = pal;
 | 
			
		||||
	m_color = color;
 | 
			
		||||
	m_page = page;
 | 
			
		||||
	m_idx = idx;
 | 
			
		||||
}
 | 
			
		||||
RemoveColorCommand::RemoveColorCommand(Palette &pal, size_t const idx) noexcept:
 | 
			
		||||
	m_pal(pal),
 | 
			
		||||
	m_idx(idx),
 | 
			
		||||
	m_colors([this] {
 | 
			
		||||
		ox::Vector<Color16> colors;
 | 
			
		||||
		colors.reserve(m_pal.pages.size());
 | 
			
		||||
		for (auto const&p : m_pal.pages) {
 | 
			
		||||
			colors.emplace_back(p[m_idx]);
 | 
			
		||||
		}
 | 
			
		||||
		return colors;
 | 
			
		||||
	}()) {}
 | 
			
		||||
 | 
			
		||||
int RemoveColorCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::RemoveColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RemoveColorCommand::redo() noexcept {
 | 
			
		||||
	std::ignore = m_pal->pages[m_page].erase(static_cast<std::size_t>(m_idx));
 | 
			
		||||
ox::Error RemoveColorCommand::redo() noexcept {
 | 
			
		||||
	for (auto &page : m_pal.pages) {
 | 
			
		||||
		oxReturnError(page.erase(m_idx));
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RemoveColorCommand::undo() noexcept {
 | 
			
		||||
m_pal->pages[m_page].insert(static_cast<std::size_t>(m_idx), m_color);
 | 
			
		||||
ox::Error RemoveColorCommand::undo() noexcept {
 | 
			
		||||
	for (size_t p = 0; auto &page : m_pal.pages) {
 | 
			
		||||
		page.emplace(m_idx, m_colors[p]);
 | 
			
		||||
		++p;
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
UpdateColorInfoCommand::UpdateColorInfoCommand(
 | 
			
		||||
		Palette &pal,
 | 
			
		||||
		size_t idx,
 | 
			
		||||
		Palette::ColorInfo newColorInfo):
 | 
			
		||||
		m_pal(pal),
 | 
			
		||||
		m_idx(idx),
 | 
			
		||||
		m_altColorInfo(std::move(newColorInfo)) {
 | 
			
		||||
	if (m_pal.colorInfo[m_idx].name == m_altColorInfo.name) {
 | 
			
		||||
		throw studio::NoChangesException();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool UpdateColorInfoCommand::mergeWith(UndoCommand const&cmd) noexcept {
 | 
			
		||||
	if (cmd.commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	auto ucCmd = static_cast<UpdateColorInfoCommand const*>(&cmd);
 | 
			
		||||
	if (m_idx != ucCmd->m_idx) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
int UpdateColorInfoCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::UpdateColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error UpdateColorInfoCommand::redo() noexcept {
 | 
			
		||||
	swap();
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error UpdateColorInfoCommand::undo() noexcept {
 | 
			
		||||
	swap();
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateColorInfoCommand::swap() noexcept {
 | 
			
		||||
	std::swap(m_pal.colorInfo[m_idx], m_altColorInfo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
UpdateColorCommand::UpdateColorCommand(
 | 
			
		||||
		Palette *pal,
 | 
			
		||||
		size_t page,
 | 
			
		||||
		int idx,
 | 
			
		||||
		Color16 oldColor,
 | 
			
		||||
		Color16 newColor) noexcept {
 | 
			
		||||
	m_pal = pal;
 | 
			
		||||
	m_page = page;
 | 
			
		||||
	m_idx = idx;
 | 
			
		||||
	m_oldColor = oldColor;
 | 
			
		||||
	m_newColor = newColor;
 | 
			
		||||
	//setObsolete(m_oldColor == m_newColor);
 | 
			
		||||
	Palette &pal,
 | 
			
		||||
	size_t page,
 | 
			
		||||
	size_t idx,
 | 
			
		||||
	Color16 newColor):
 | 
			
		||||
		m_pal(pal),
 | 
			
		||||
		m_page(page),
 | 
			
		||||
		m_idx(idx),
 | 
			
		||||
		m_altColor(newColor) {
 | 
			
		||||
	if (color(m_pal, m_page, m_idx) == newColor) {
 | 
			
		||||
		throw studio::NoChangesException();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool UpdateColorCommand::mergeWith(const UndoCommand *cmd) noexcept {
 | 
			
		||||
	if (cmd->commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) {
 | 
			
		||||
bool UpdateColorCommand::mergeWith(UndoCommand const&cmd) noexcept {
 | 
			
		||||
	if (cmd.commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	auto ucCmd = static_cast<const UpdateColorCommand*>(cmd);
 | 
			
		||||
	auto ucCmd = static_cast<UpdateColorCommand const*>(&cmd);
 | 
			
		||||
	if (m_idx != ucCmd->m_idx) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	m_newColor = ucCmd->m_newColor;
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -141,38 +224,47 @@ int UpdateColorCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::UpdateColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateColorCommand::redo() noexcept {
 | 
			
		||||
	m_pal->pages[m_page][static_cast<std::size_t>(m_idx)] = m_newColor;
 | 
			
		||||
ox::Error UpdateColorCommand::redo() noexcept {
 | 
			
		||||
	swap();
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateColorCommand::undo() noexcept {
 | 
			
		||||
	m_pal->pages[m_page][static_cast<std::size_t>(m_idx)] = m_oldColor;
 | 
			
		||||
ox::Error UpdateColorCommand::undo() noexcept {
 | 
			
		||||
	swap();
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateColorCommand::swap() noexcept {
 | 
			
		||||
	auto &dst = colors(m_pal, m_page)[m_idx];
 | 
			
		||||
	std::swap(dst, m_altColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MoveColorCommand::MoveColorCommand(Palette *pal, size_t page, std::size_t idx, int offset) noexcept {
 | 
			
		||||
	m_pal = pal;
 | 
			
		||||
	m_page = page;
 | 
			
		||||
	m_idx = idx;
 | 
			
		||||
	m_offset = offset;
 | 
			
		||||
}
 | 
			
		||||
MoveColorCommand::MoveColorCommand(
 | 
			
		||||
		Palette &pal, size_t page, size_t srcIdx, size_t dstIdx) noexcept:
 | 
			
		||||
	m_pal(pal),
 | 
			
		||||
	m_page(page),
 | 
			
		||||
	m_srcIdx(srcIdx),
 | 
			
		||||
	m_dstIdx(dstIdx) {}
 | 
			
		||||
 | 
			
		||||
int MoveColorCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::MoveColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MoveColorCommand::redo() noexcept {
 | 
			
		||||
	moveColor(static_cast<int>(m_idx), m_offset);
 | 
			
		||||
ox::Error MoveColorCommand::redo() noexcept {
 | 
			
		||||
	moveColor(m_srcIdx, m_dstIdx);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MoveColorCommand::undo() noexcept {
 | 
			
		||||
	moveColor(static_cast<int>(m_idx) + m_offset, -m_offset);
 | 
			
		||||
ox::Error MoveColorCommand::undo() noexcept {
 | 
			
		||||
	moveColor(m_dstIdx, m_srcIdx);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MoveColorCommand::moveColor(int idx, int offset) noexcept {
 | 
			
		||||
	const auto c = m_pal->pages[m_page][static_cast<std::size_t>(idx)];
 | 
			
		||||
	std::ignore = m_pal->pages[m_page].erase(static_cast<std::size_t>(idx));
 | 
			
		||||
	m_pal->pages[m_page].insert(static_cast<std::size_t>(idx + offset), c);
 | 
			
		||||
void MoveColorCommand::moveColor(size_t srcIdx, size_t dstIdx) noexcept {
 | 
			
		||||
	auto const c = color(m_pal, m_page, srcIdx);
 | 
			
		||||
	std::ignore = colors(m_pal, m_page).erase(srcIdx);
 | 
			
		||||
	colors(m_pal, m_page).emplace(dstIdx, c);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,32 +13,35 @@
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
enum class PaletteEditorCommandId {
 | 
			
		||||
	AddPage,
 | 
			
		||||
	ApplyColorAllPages,
 | 
			
		||||
	DuplicatePage,
 | 
			
		||||
	RemovePage,
 | 
			
		||||
	AddColor,
 | 
			
		||||
	RemoveColor,
 | 
			
		||||
	UpdateColorInfo,
 | 
			
		||||
	UpdateColor,
 | 
			
		||||
	MoveColor,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AddPageCommand: public studio::UndoCommand {
 | 
			
		||||
class ApplyColorAllPagesCommand: public studio::UndoCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		Palette &m_pal;
 | 
			
		||||
		size_t const m_page{};
 | 
			
		||||
		size_t const m_idx{};
 | 
			
		||||
		ox::Vector<Color16> const m_origColors;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		AddPageCommand(Palette &pal) noexcept;
 | 
			
		||||
		ApplyColorAllPagesCommand(Palette &pal, size_t page, size_t idx);
 | 
			
		||||
 | 
			
		||||
		~AddPageCommand() noexcept override = default;
 | 
			
		||||
		~ApplyColorAllPagesCommand() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DuplicatePageCommand: public studio::UndoCommand {
 | 
			
		||||
@@ -55,9 +58,9 @@ class DuplicatePageCommand: public studio::UndoCommand {
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		size_t insertIdx() const noexcept;
 | 
			
		||||
@@ -78,88 +81,121 @@ class RemovePageCommand: public studio::UndoCommand {
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AddColorCommand: public studio::UndoCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		Palette *m_pal = nullptr;
 | 
			
		||||
		Palette &m_pal;
 | 
			
		||||
		Color16 m_color = 0;
 | 
			
		||||
		int m_idx = -1;
 | 
			
		||||
		size_t m_page = 0;
 | 
			
		||||
		size_t const m_idx = 0;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		AddColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept;
 | 
			
		||||
		AddColorCommand(Palette &pal, Color16 color, size_t idx) noexcept;
 | 
			
		||||
 | 
			
		||||
		~AddColorCommand() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept override;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept override;
 | 
			
		||||
		ox::Error redo() noexcept override;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept override;
 | 
			
		||||
		ox::Error undo() noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RemoveColorCommand: public studio::UndoCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		Palette *m_pal = nullptr;
 | 
			
		||||
		Color16 m_color = 0;
 | 
			
		||||
		size_t m_page = 0;
 | 
			
		||||
		int m_idx = -1;
 | 
			
		||||
		Palette &m_pal;
 | 
			
		||||
		size_t const m_idx = 0;
 | 
			
		||||
		ox::Vector<Color16> const m_colors;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		RemoveColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept;
 | 
			
		||||
		RemoveColorCommand(Palette &pal, size_t idx) noexcept;
 | 
			
		||||
 | 
			
		||||
		~RemoveColorCommand() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept override;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept override;
 | 
			
		||||
		ox::Error redo() noexcept override;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept override;
 | 
			
		||||
		ox::Error undo() noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class UpdateColorInfoCommand: public studio::UndoCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		Palette &m_pal;
 | 
			
		||||
		size_t const m_idx{};
 | 
			
		||||
		Palette::ColorInfo m_altColorInfo;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		UpdateColorInfoCommand(
 | 
			
		||||
				Palette &pal,
 | 
			
		||||
				size_t idx,
 | 
			
		||||
				Palette::ColorInfo newColorInfo);
 | 
			
		||||
 | 
			
		||||
		~UpdateColorInfoCommand() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		bool mergeWith(const UndoCommand &cmd) noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void swap() noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class UpdateColorCommand: public studio::UndoCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		Palette *m_pal = nullptr;
 | 
			
		||||
		Color16 m_oldColor = 0;
 | 
			
		||||
		Color16 m_newColor = 0;
 | 
			
		||||
		size_t m_page = 0;
 | 
			
		||||
		int m_idx = -1;
 | 
			
		||||
		Palette &m_pal;
 | 
			
		||||
		size_t const m_page = 0;
 | 
			
		||||
		size_t const m_idx{};
 | 
			
		||||
		Color16 m_altColor{};
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		UpdateColorCommand(Palette *pal, size_t page, int idx, Color16 oldColor, Color16 newColor) noexcept;
 | 
			
		||||
		UpdateColorCommand(
 | 
			
		||||
				Palette &pal,
 | 
			
		||||
				size_t page,
 | 
			
		||||
				size_t idx,
 | 
			
		||||
				Color16 newColor);
 | 
			
		||||
 | 
			
		||||
		~UpdateColorCommand() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		bool mergeWith(const UndoCommand *cmd) noexcept final;
 | 
			
		||||
		bool mergeWith(const UndoCommand &cmd) noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void swap() noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MoveColorCommand: public studio::UndoCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		Palette *m_pal = nullptr;
 | 
			
		||||
		size_t m_page = 0;
 | 
			
		||||
		std::size_t m_idx = 0;
 | 
			
		||||
		int m_offset = 0;
 | 
			
		||||
		Palette &m_pal;
 | 
			
		||||
		size_t const m_page = 0;
 | 
			
		||||
		std::size_t const m_srcIdx = 0;
 | 
			
		||||
		std::size_t const m_dstIdx = 0;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		MoveColorCommand(Palette *pal, size_t page, std::size_t idx, int offset) noexcept;
 | 
			
		||||
		MoveColorCommand(Palette &pal, size_t page, size_t srcIdx, size_t dstIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
		~MoveColorCommand() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
@@ -167,12 +203,12 @@ class MoveColorCommand: public studio::UndoCommand {
 | 
			
		||||
		int commandId() const noexcept override;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		void redo() noexcept override;
 | 
			
		||||
		ox::Error redo() noexcept override;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept override;
 | 
			
		||||
		ox::Error undo() noexcept override;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void moveColor(int idx, int offset) noexcept;
 | 
			
		||||
		void moveColor(size_t srcIdx, size_t dstIdx) noexcept;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ AddSubSheetCommand::AddSubSheetCommand(
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddSubSheetCommand::redo() noexcept {
 | 
			
		||||
ox::Error AddSubSheetCommand::redo() noexcept {
 | 
			
		||||
	auto &parent = getSubSheet(m_img, m_parentIdx);
 | 
			
		||||
	if (m_addedSheets.size() < 2) {
 | 
			
		||||
		auto i = parent.subsheets.size();
 | 
			
		||||
@@ -35,9 +35,10 @@ void AddSubSheetCommand::redo() noexcept {
 | 
			
		||||
		parent.columns = 0;
 | 
			
		||||
		parent.subsheets.emplace_back(m_img.idIt++, "Subsheet 1", 1, 1, m_img.bpp);
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddSubSheetCommand::undo() noexcept {
 | 
			
		||||
ox::Error AddSubSheetCommand::undo() noexcept {
 | 
			
		||||
	auto &parent = getSubSheet(m_img, m_parentIdx);
 | 
			
		||||
	if (parent.subsheets.size() == 2) {
 | 
			
		||||
		auto s = parent.subsheets[0];
 | 
			
		||||
@@ -47,9 +48,10 @@ void AddSubSheetCommand::undo() noexcept {
 | 
			
		||||
		parent.subsheets.clear();
 | 
			
		||||
	} else {
 | 
			
		||||
		for (auto idx = m_addedSheets.rbegin(); idx != m_addedSheets.rend(); ++idx) {
 | 
			
		||||
			oxLogError(rmSubSheet(m_img, *idx));
 | 
			
		||||
			oxReturnError(rmSubSheet(m_img, *idx));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int AddSubSheetCommand::commandId() const noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,9 @@ class AddSubSheetCommand: public TileSheetCommand {
 | 
			
		||||
	public:
 | 
			
		||||
		AddSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx parentIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 
 | 
			
		||||
@@ -41,18 +41,20 @@ CutPasteCommand::CutPasteCommand(
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CutPasteCommand::redo() noexcept {
 | 
			
		||||
ox::Error CutPasteCommand::redo() noexcept {
 | 
			
		||||
	auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
 | 
			
		||||
	for (const auto &c : m_changes) {
 | 
			
		||||
		setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(c.newPalIdx));
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CutPasteCommand::undo() noexcept {
 | 
			
		||||
ox::Error CutPasteCommand::undo() noexcept {
 | 
			
		||||
	auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
 | 
			
		||||
	for (const auto &c : m_changes) {
 | 
			
		||||
		setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int CutPasteCommand::commandId() const noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -69,9 +69,9 @@ class CutPasteCommand: public TileSheetCommand {
 | 
			
		||||
				ox::Point const&dstEnd,
 | 
			
		||||
				TileSheetClipboard const&cb) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ core::DeleteTilesCommand::DeleteTilesCommand(
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void core::DeleteTilesCommand::redo() noexcept {
 | 
			
		||||
ox::Error core::DeleteTilesCommand::redo() noexcept {
 | 
			
		||||
	auto &s = getSubSheet(m_img, m_idx);
 | 
			
		||||
	auto &p = s.pixels;
 | 
			
		||||
	auto srcPos = m_deletePos + m_deleteSz;
 | 
			
		||||
@@ -37,9 +37,10 @@ void core::DeleteTilesCommand::redo() noexcept {
 | 
			
		||||
	const auto dst2 = p.data() + (p.size() - m_deleteSz);
 | 
			
		||||
	ox::memmove(dst1, src, p.size() - srcPos);
 | 
			
		||||
	ox::memset(dst2, 0, m_deleteSz * sizeof(decltype(p[0])));
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DeleteTilesCommand::undo() noexcept {
 | 
			
		||||
ox::Error DeleteTilesCommand::undo() noexcept {
 | 
			
		||||
	auto &s = getSubSheet(m_img, m_idx);
 | 
			
		||||
	auto &p = s.pixels;
 | 
			
		||||
	const auto src = p.data() + m_deletePos;
 | 
			
		||||
@@ -48,6 +49,7 @@ void DeleteTilesCommand::undo() noexcept {
 | 
			
		||||
	const auto sz = p.size() - m_deletePos - m_deleteSz;
 | 
			
		||||
	ox::memmove(dst1, src, sz);
 | 
			
		||||
	ox::memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int DeleteTilesCommand::commandId() const noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,9 @@ class DeleteTilesCommand: public TileSheetCommand {
 | 
			
		||||
				std::size_t tileIdx,
 | 
			
		||||
				std::size_t tileCnt) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 
 | 
			
		||||
@@ -56,18 +56,20 @@ bool DrawCommand::append(const ox::Vector<std::size_t> &idxList) noexcept {
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DrawCommand::redo() noexcept {
 | 
			
		||||
ox::Error DrawCommand::redo() noexcept {
 | 
			
		||||
	auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
 | 
			
		||||
	for (const auto &c : m_changes) {
 | 
			
		||||
		setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(m_palIdx));
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DrawCommand::undo() noexcept {
 | 
			
		||||
ox::Error DrawCommand::undo() noexcept {
 | 
			
		||||
	auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
 | 
			
		||||
	for (const auto &c : m_changes) {
 | 
			
		||||
		setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int DrawCommand::commandId() const noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -40,9 +40,9 @@ class DrawCommand: public TileSheetCommand {
 | 
			
		||||
 | 
			
		||||
		bool append(const ox::Vector<std::size_t> &idxList) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ core::InsertTilesCommand::InsertTilesCommand(
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void InsertTilesCommand::redo() noexcept {
 | 
			
		||||
ox::Error InsertTilesCommand::redo() noexcept {
 | 
			
		||||
	auto &s = getSubSheet(m_img, m_idx);
 | 
			
		||||
	auto &p = s.pixels;
 | 
			
		||||
	auto dstPos = m_insertPos + m_insertCnt;
 | 
			
		||||
@@ -36,9 +36,10 @@ void InsertTilesCommand::redo() noexcept {
 | 
			
		||||
	const auto src = p.data() + m_insertPos;
 | 
			
		||||
	ox::memmove(dst, src, p.size() - dstPos);
 | 
			
		||||
	ox::memset(src, 0, m_insertCnt * sizeof(decltype(p[0])));
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void InsertTilesCommand::undo() noexcept {
 | 
			
		||||
ox::Error InsertTilesCommand::undo() noexcept {
 | 
			
		||||
	auto &s = getSubSheet(m_img, m_idx);
 | 
			
		||||
	auto &p = s.pixels;
 | 
			
		||||
	const auto srcIdx = m_insertPos + m_insertCnt;
 | 
			
		||||
@@ -48,6 +49,7 @@ void InsertTilesCommand::undo() noexcept {
 | 
			
		||||
	const auto sz = p.size() - srcIdx;
 | 
			
		||||
	ox::memmove(dst1, src, sz);
 | 
			
		||||
	ox::memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int InsertTilesCommand::commandId() const noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,9 @@ class InsertTilesCommand: public TileSheetCommand {
 | 
			
		||||
				std::size_t tileIdx,
 | 
			
		||||
				std::size_t tileCnt) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 
 | 
			
		||||
@@ -16,12 +16,14 @@ core::PaletteChangeCommand::PaletteChangeCommand(
 | 
			
		||||
		m_newPalette(ox::FileAddress(ox::sfmt<ox::IString<43>>("uuid://{}", newPalette))) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PaletteChangeCommand::redo() noexcept {
 | 
			
		||||
ox::Error PaletteChangeCommand::redo() noexcept {
 | 
			
		||||
	m_img.defaultPalette = m_newPalette;
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PaletteChangeCommand::undo() noexcept {
 | 
			
		||||
ox::Error PaletteChangeCommand::undo() noexcept {
 | 
			
		||||
	m_img.defaultPalette = m_oldPalette;
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int PaletteChangeCommand::commandId() const noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,9 +21,9 @@ class PaletteChangeCommand: public TileSheetCommand {
 | 
			
		||||
				TileSheet &img,
 | 
			
		||||
				ox::CRStringView newPalette) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 
 | 
			
		||||
@@ -11,19 +11,20 @@ core::RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetId
 | 
			
		||||
		m_idx(std::move(idx)),
 | 
			
		||||
		m_parentIdx(m_idx) {
 | 
			
		||||
	m_parentIdx.pop_back();
 | 
			
		||||
	auto &parent = getSubSheet(m_img, m_parentIdx);
 | 
			
		||||
	m_sheet = parent.subsheets[*m_idx.back().value];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RmSubSheetCommand::redo() noexcept {
 | 
			
		||||
ox::Error RmSubSheetCommand::redo() noexcept {
 | 
			
		||||
	auto &parent = getSubSheet(m_img, m_parentIdx);
 | 
			
		||||
	oxLogError(parent.subsheets.erase(*m_idx.back().value).error);
 | 
			
		||||
	m_sheet = std::move(parent.subsheets[*m_idx.back().value]);
 | 
			
		||||
	oxReturnError(parent.subsheets.erase(*m_idx.back().value).error);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RmSubSheetCommand::undo() noexcept {
 | 
			
		||||
ox::Error RmSubSheetCommand::undo() noexcept {
 | 
			
		||||
	auto &parent = getSubSheet(m_img, m_parentIdx);
 | 
			
		||||
	auto i = *m_idx.back().value;
 | 
			
		||||
	parent.subsheets.insert(i, m_sheet);
 | 
			
		||||
	auto const i = *m_idx.back().value;
 | 
			
		||||
	parent.subsheets.insert(i, std::move(m_sheet));
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int RmSubSheetCommand::commandId() const noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,9 @@ class RmSubSheetCommand: public TileSheetCommand {
 | 
			
		||||
	public:
 | 
			
		||||
		RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 
 | 
			
		||||
@@ -20,17 +20,17 @@ core::UpdateSubSheetCommand::UpdateSubSheetCommand(
 | 
			
		||||
		m_newRows(rows) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateSubSheetCommand::redo() noexcept {
 | 
			
		||||
ox::Error UpdateSubSheetCommand::redo() noexcept {
 | 
			
		||||
	auto &sheet = getSubSheet(m_img, m_idx);
 | 
			
		||||
	sheet.name = m_newName;
 | 
			
		||||
	sheet.columns = m_newCols;
 | 
			
		||||
	sheet.rows = m_newRows;
 | 
			
		||||
	oxLogError(setPixelCount(sheet, m_img.bpp, static_cast<std::size_t>(PixelsPerTile * m_newCols * m_newRows)));
 | 
			
		||||
	oxLogError(resizeSubsheet(sheet, m_img.bpp, {m_newCols, m_newRows}));
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateSubSheetCommand::undo() noexcept {
 | 
			
		||||
ox::Error UpdateSubSheetCommand::undo() noexcept {
 | 
			
		||||
	auto &sheet = getSubSheet(m_img, m_idx);
 | 
			
		||||
	sheet = m_sheet;
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int UpdateSubSheetCommand::commandId() const noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,9 +25,9 @@ class UpdateSubSheetCommand: public TileSheetCommand {
 | 
			
		||||
				int cols,
 | 
			
		||||
				int rows) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
		ox::Error redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
		ox::Error undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
 | 
			
		||||
#include <ox/std/point.hpp>
 | 
			
		||||
#include <keel/media.hpp>
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
#include "tilesheeteditor-imgui.hpp"
 | 
			
		||||
 | 
			
		||||
@@ -14,6 +15,36 @@ namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
namespace ig = studio::ig;
 | 
			
		||||
 | 
			
		||||
static ox::String configName(ox::StringView str) noexcept {
 | 
			
		||||
	auto out = ox::String{str};
 | 
			
		||||
	for (auto &c : out) {
 | 
			
		||||
		if (c == '/' || c == '\\') {
 | 
			
		||||
			c = '%';
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct TileSheetEditorConfig {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetEditorConfig";
 | 
			
		||||
	static constexpr auto TypeVersion = 1;
 | 
			
		||||
	TileSheet::SubSheetIdx activeSubsheet{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetEditorConfig)
 | 
			
		||||
	oxModelFieldRename(activeSubsheet, active_subsheet)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
struct TileSheetEditorConfigs {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetEditorConfigs";
 | 
			
		||||
	static constexpr auto TypeVersion = 1;
 | 
			
		||||
	ox::HashMap<ox::String, TileSheetEditorConfig> configs;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetEditorConfigs)
 | 
			
		||||
	oxModelField(configs)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
static ox::Vector<uint32_t> normalizePixelSizes(
 | 
			
		||||
		ox::Vector<uint8_t> const&inPixels,
 | 
			
		||||
		int const bpp) noexcept {
 | 
			
		||||
@@ -75,7 +106,7 @@ static ox::Error toPngFile(
 | 
			
		||||
					8)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::CRStringView path):
 | 
			
		||||
TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringView const path):
 | 
			
		||||
		Editor(path),
 | 
			
		||||
		m_sctx(sctx),
 | 
			
		||||
		m_tctx(m_sctx.tctx),
 | 
			
		||||
@@ -87,6 +118,12 @@ TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::CRSt
 | 
			
		||||
	m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
 | 
			
		||||
	m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubhseetToPng);
 | 
			
		||||
	m_model.paletteChanged.connect(this, &TileSheetEditorImGui::setPaletteSelection);
 | 
			
		||||
	// load config
 | 
			
		||||
	auto const&config = studio::readConfig<TileSheetEditorConfig>(
 | 
			
		||||
			keelCtx(m_sctx), configName(itemPath()));
 | 
			
		||||
	if (config.ok()) {
 | 
			
		||||
		m_model.setActiveSubsheet(validateSubSheetIdx(m_model.img(), config.value.activeSubsheet));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::exportFile() {
 | 
			
		||||
@@ -105,6 +142,10 @@ void TileSheetEditorImGui::paste() {
 | 
			
		||||
	m_model.paste();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TileSheetEditorImGui::acceptsClipboardPayload() const noexcept {
 | 
			
		||||
	return m_model.acceptsClipboardPayload();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
 | 
			
		||||
	if (!down) {
 | 
			
		||||
		return;
 | 
			
		||||
@@ -116,20 +157,20 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
 | 
			
		||||
	auto const popupOpen = m_subsheetEditor.isOpen() && m_exportMenu.isOpen();
 | 
			
		||||
	auto const pal = m_model.pal();
 | 
			
		||||
	if (!popupOpen) {
 | 
			
		||||
		auto const colorCnt = pal.pages[m_model.palettePage()].size();
 | 
			
		||||
		auto const colorCnt = core::colorCnt(pal, m_model.palettePage());
 | 
			
		||||
		if (key == turbine::Key::Alpha_D) {
 | 
			
		||||
			m_tool = Tool::Draw;
 | 
			
		||||
			m_tool = TileSheetTool::Draw;
 | 
			
		||||
			setCopyEnabled(false);
 | 
			
		||||
			setCutEnabled(false);
 | 
			
		||||
			setPasteEnabled(false);
 | 
			
		||||
			m_model.clearSelection();
 | 
			
		||||
		} else if (key == turbine::Key::Alpha_S) {
 | 
			
		||||
			m_tool = Tool::Select;
 | 
			
		||||
			m_tool = TileSheetTool::Select;
 | 
			
		||||
			setCopyEnabled(true);
 | 
			
		||||
			setCutEnabled(true);
 | 
			
		||||
			setPasteEnabled(true);
 | 
			
		||||
		} else if (key == turbine::Key::Alpha_F) {
 | 
			
		||||
			m_tool = Tool::Fill;
 | 
			
		||||
			m_tool = TileSheetTool::Fill;
 | 
			
		||||
			setCopyEnabled(false);
 | 
			
		||||
			setCutEnabled(false);
 | 
			
		||||
			setPasteEnabled(false);
 | 
			
		||||
@@ -140,7 +181,8 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
 | 
			
		||||
						static_cast<uint32_t>(key - turbine::Key::Num_1), m_model.pal().pages.size() - 1);
 | 
			
		||||
				m_model.setPalettePage(idx);
 | 
			
		||||
			} else if (key <= turbine::Key::Num_0 + colorCnt) {
 | 
			
		||||
				auto const idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1), colorCnt - 1);
 | 
			
		||||
				auto const idx = ox::min<std::size_t>(
 | 
			
		||||
						static_cast<uint32_t>(key - turbine::Key::Num_1), colorCnt - 1);
 | 
			
		||||
				m_view.setPalIdx(idx);
 | 
			
		||||
			}
 | 
			
		||||
		} else if (key == turbine::Key::Num_0) {
 | 
			
		||||
@@ -173,17 +215,17 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
 | 
			
		||||
		ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true);
 | 
			
		||||
		{
 | 
			
		||||
			auto const btnSz = ImVec2(45, 14);
 | 
			
		||||
			if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) {
 | 
			
		||||
				m_tool = Tool::Select;
 | 
			
		||||
			if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) {
 | 
			
		||||
				m_tool = TileSheetTool::Select;
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) {
 | 
			
		||||
				m_tool = Tool::Draw;
 | 
			
		||||
			if (ImGui::Selectable("Draw", m_tool == TileSheetTool::Draw, 0, btnSz)) {
 | 
			
		||||
				m_tool = TileSheetTool::Draw;
 | 
			
		||||
				m_model.clearSelection();
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) {
 | 
			
		||||
				m_tool = Tool::Fill;
 | 
			
		||||
			if (ImGui::Selectable("Fill", m_tool == TileSheetTool::Fill, 0, btnSz)) {
 | 
			
		||||
				m_tool = TileSheetTool::Fill;
 | 
			
		||||
				m_model.clearSelection();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -204,7 +246,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
 | 
			
		||||
				auto const&parent = m_model.activeSubSheet();
 | 
			
		||||
				m_model.addSubsheet(insertOnIdx);
 | 
			
		||||
				insertOnIdx.emplace_back(parent.subsheets.size() - 1);
 | 
			
		||||
				m_model.setActiveSubsheet(insertOnIdx);
 | 
			
		||||
				setActiveSubsheet(insertOnIdx);
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			if (ig::PushButton("-", btnSize)) {
 | 
			
		||||
@@ -237,7 +279,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
 | 
			
		||||
	}
 | 
			
		||||
	ImGui::EndChild();
 | 
			
		||||
	m_subsheetEditor.draw(m_tctx);
 | 
			
		||||
	m_exportMenu.draw(m_sctx);
 | 
			
		||||
	m_exportMenu.draw(m_tctx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::drawSubsheetSelector(
 | 
			
		||||
@@ -257,7 +299,7 @@ void TileSheetEditorImGui::drawSubsheetSelector(
 | 
			
		||||
	auto const open = ImGui::TreeNodeEx(lbl.c_str(), flags);
 | 
			
		||||
	ImGui::SameLine();
 | 
			
		||||
	if (ImGui::IsItemClicked()) {
 | 
			
		||||
		m_model.setActiveSubsheet(path);
 | 
			
		||||
		setActiveSubsheet(path);
 | 
			
		||||
	}
 | 
			
		||||
	if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
 | 
			
		||||
		showSubsheetEditor();
 | 
			
		||||
@@ -370,16 +412,16 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
 | 
			
		||||
		if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) {
 | 
			
		||||
			m_prevMouseDownPos = mousePos;
 | 
			
		||||
			switch (m_tool) {
 | 
			
		||||
				case Tool::Draw:
 | 
			
		||||
				case TileSheetTool::Draw:
 | 
			
		||||
					m_view.clickDraw(fbSize, clickPos(winPos, mousePos));
 | 
			
		||||
					break;
 | 
			
		||||
				case Tool::Fill:
 | 
			
		||||
				case TileSheetTool::Fill:
 | 
			
		||||
					m_view.clickFill(fbSize, clickPos(winPos, mousePos));
 | 
			
		||||
					break;
 | 
			
		||||
				case Tool::Select:
 | 
			
		||||
				case TileSheetTool::Select:
 | 
			
		||||
					m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
 | 
			
		||||
					break;
 | 
			
		||||
				case Tool::None:
 | 
			
		||||
				case TileSheetTool::None:
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -396,7 +438,7 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
 | 
			
		||||
	}
 | 
			
		||||
	if (io.MouseReleased[0]) {
 | 
			
		||||
		m_prevMouseDownPos = {-1, -1};
 | 
			
		||||
		m_view.releaseMouseButton();
 | 
			
		||||
		m_view.releaseMouseButton(m_tool);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -428,31 +470,39 @@ void TileSheetEditorImGui::drawPaletteSelector() noexcept {
 | 
			
		||||
		ImGui::Indent(-20);
 | 
			
		||||
	}
 | 
			
		||||
	// header
 | 
			
		||||
	if (ImGui::BeginTable("PaletteTable", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
 | 
			
		||||
		ImGui::TableSetupColumn("No.", 0, 0.45f);
 | 
			
		||||
	if (ImGui::BeginTable(
 | 
			
		||||
			"PaletteTable", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
 | 
			
		||||
		ImGui::TableSetupColumn("Idx", 0, 0.6f);
 | 
			
		||||
		ImGui::TableSetupColumn("", 0, 0.22f);
 | 
			
		||||
		ImGui::TableSetupColumn("Name", 0, 3);
 | 
			
		||||
		ImGui::TableSetupColumn("Color16", 0, 3);
 | 
			
		||||
		ImGui::TableHeadersRow();
 | 
			
		||||
		{
 | 
			
		||||
			auto const&pal = m_model.pal();
 | 
			
		||||
			for (auto i = 0u; auto c: pal.pages[m_model.palettePage()]) {
 | 
			
		||||
				ImGui::PushID(static_cast<int>(i));
 | 
			
		||||
				// Column: color idx
 | 
			
		||||
				ImGui::TableNextColumn();
 | 
			
		||||
				auto const label = ox::itoa(i + 1);
 | 
			
		||||
				auto const rowSelected = i == m_view.palIdx();
 | 
			
		||||
				if (ImGui::Selectable(label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
 | 
			
		||||
					m_view.setPalIdx(i);
 | 
			
		||||
			if (pal.pages.size() > m_model.palettePage()) {
 | 
			
		||||
				for (auto i = 0u; auto const&c: pal.pages[m_model.palettePage()]) {
 | 
			
		||||
					ImGui::PushID(static_cast<int>(i));
 | 
			
		||||
					// Column: color idx
 | 
			
		||||
					ImGui::TableNextColumn();
 | 
			
		||||
					auto const label = ox::itoa(i + 1);
 | 
			
		||||
					auto const rowSelected = i == m_view.palIdx();
 | 
			
		||||
					if (ImGui::Selectable(
 | 
			
		||||
							label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
 | 
			
		||||
						m_view.setPalIdx(i);
 | 
			
		||||
					}
 | 
			
		||||
					// Column: color RGB
 | 
			
		||||
					ImGui::TableNextColumn();
 | 
			
		||||
					auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
 | 
			
		||||
					ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
 | 
			
		||||
					ImGui::TableNextColumn();
 | 
			
		||||
					auto const&name = pal.colorInfo[i].name;
 | 
			
		||||
					ImGui::Text("%s", name.c_str());
 | 
			
		||||
					ImGui::TableNextColumn();
 | 
			
		||||
					ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));
 | 
			
		||||
					ImGui::TableNextRow();
 | 
			
		||||
					ImGui::PopID();
 | 
			
		||||
					++i;
 | 
			
		||||
				}
 | 
			
		||||
				// Column: color RGB
 | 
			
		||||
				ImGui::TableNextColumn();
 | 
			
		||||
				auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
 | 
			
		||||
				ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
 | 
			
		||||
				ImGui::TableNextColumn();
 | 
			
		||||
				ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));
 | 
			
		||||
				ImGui::TableNextRow();
 | 
			
		||||
				ImGui::PopID();
 | 
			
		||||
				++i;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndTable();
 | 
			
		||||
@@ -476,12 +526,20 @@ ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept {
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept {
 | 
			
		||||
	m_model.setActiveSubsheet(path);
 | 
			
		||||
	studio::editConfig<TileSheetEditorConfig>(keelCtx(m_sctx), configName(itemPath()),
 | 
			
		||||
		[&path](TileSheetEditorConfig *config) {
 | 
			
		||||
			config->activeSubsheet = std::move(path);
 | 
			
		||||
		});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorImGui::markUnsavedChanges(studio::UndoCommand const*) noexcept {
 | 
			
		||||
	setUnsavedChanges(true);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &sctx) noexcept {
 | 
			
		||||
void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &tctx) noexcept {
 | 
			
		||||
	constexpr auto popupName = "Edit Subsheet";
 | 
			
		||||
	if (!m_show) {
 | 
			
		||||
		return;
 | 
			
		||||
@@ -490,7 +548,7 @@ void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &sctx) noexcept
 | 
			
		||||
	auto constexpr popupWidth = 235.f;
 | 
			
		||||
	auto const popupHeight = modSize ? 130.f : 85.f;
 | 
			
		||||
	auto const popupSz = ImVec2(popupWidth, popupHeight);
 | 
			
		||||
	if (ig::BeginPopup(sctx, popupName, m_show, popupSz)) {
 | 
			
		||||
	if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) {
 | 
			
		||||
		ImGui::InputText("Name", m_name.data(), m_name.cap());
 | 
			
		||||
		if (modSize) {
 | 
			
		||||
			ImGui::InputInt("Columns", &m_cols);
 | 
			
		||||
@@ -507,7 +565,7 @@ void TileSheetEditorImGui::SubSheetEditor::close() noexcept {
 | 
			
		||||
	m_show = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::ExportMenu::draw(studio::StudioContext &sctx) noexcept {
 | 
			
		||||
void TileSheetEditorImGui::ExportMenu::draw(turbine::Context &tctx) noexcept {
 | 
			
		||||
	constexpr auto popupName = "Export Tile Sheet";
 | 
			
		||||
	if (!m_show) {
 | 
			
		||||
		return;
 | 
			
		||||
@@ -515,7 +573,7 @@ void TileSheetEditorImGui::ExportMenu::draw(studio::StudioContext &sctx) noexcep
 | 
			
		||||
	constexpr auto popupWidth = 235.f;
 | 
			
		||||
	constexpr auto popupHeight = 85.f;
 | 
			
		||||
	constexpr auto popupSz = ImVec2(popupWidth, popupHeight);
 | 
			
		||||
	if (ig::BeginPopup(sctx.tctx, popupName, m_show, popupSz)) {
 | 
			
		||||
	if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) {
 | 
			
		||||
		ImGui::InputInt("Scale", &m_scale);
 | 
			
		||||
		m_scale = ox::clamp(m_scale, 1, 50);
 | 
			
		||||
		if (ig::PopupControlsOkCancel(popupWidth, m_show) == ig::PopupResponse::OK) {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,13 +16,6 @@
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
enum class Tool {
 | 
			
		||||
	None,
 | 
			
		||||
	Draw,
 | 
			
		||||
	Fill,
 | 
			
		||||
	Select,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TileSheetEditorImGui: public studio::Editor {
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
@@ -55,7 +48,7 @@ class TileSheetEditorImGui: public studio::Editor {
 | 
			
		||||
					m_show = true;
 | 
			
		||||
					m_scale = 5;
 | 
			
		||||
				}
 | 
			
		||||
				void draw(studio::StudioContext &sctx) noexcept;
 | 
			
		||||
				void draw(turbine::Context &sctx) noexcept;
 | 
			
		||||
				void close() noexcept;
 | 
			
		||||
				[[nodiscard]]
 | 
			
		||||
				inline bool isOpen() const noexcept { return m_show; }
 | 
			
		||||
@@ -71,10 +64,10 @@ class TileSheetEditorImGui: public studio::Editor {
 | 
			
		||||
		TileSheetEditorModel &m_model;
 | 
			
		||||
		float m_palViewWidth = 300;
 | 
			
		||||
		ox::Vec2 m_prevMouseDownPos;
 | 
			
		||||
		Tool m_tool = Tool::Draw;
 | 
			
		||||
		TileSheetTool m_tool = TileSheetTool::Draw;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		TileSheetEditorImGui(studio::StudioContext &sctx, ox::CRStringView path);
 | 
			
		||||
		TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringView path);
 | 
			
		||||
 | 
			
		||||
		~TileSheetEditorImGui() override = default;
 | 
			
		||||
 | 
			
		||||
@@ -86,6 +79,9 @@ class TileSheetEditorImGui: public studio::Editor {
 | 
			
		||||
 | 
			
		||||
		void paste() override;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		bool acceptsClipboardPayload() const noexcept override;
 | 
			
		||||
 | 
			
		||||
		void keyStateChanged(turbine::Key key, bool down) override;
 | 
			
		||||
 | 
			
		||||
		void draw(studio::StudioContext&) noexcept override;
 | 
			
		||||
@@ -115,6 +111,8 @@ class TileSheetEditorImGui: public studio::Editor {
 | 
			
		||||
	private:
 | 
			
		||||
		ox::Error markUnsavedChanges(studio::UndoCommand const*) noexcept;
 | 
			
		||||
 | 
			
		||||
		void setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,8 @@
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
Palette const TileSheetEditorModel::s_defaultPalette = {
 | 
			
		||||
	.pages = {ox::Vector<Color16>(128)},
 | 
			
		||||
	.colorInfo = {ox::Vector<Palette::ColorInfo>{{}}},
 | 
			
		||||
	.pages = {{ox::Vector<Color16>(128)}},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// delete pixels of all non-leaf nodes
 | 
			
		||||
@@ -55,55 +56,63 @@ TileSheetEditorModel::TileSheetEditorModel(studio::StudioContext &sctx, ox::Stri
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::cut() {
 | 
			
		||||
	if (!m_selection) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	TileSheetClipboard blankCb;
 | 
			
		||||
	auto cb = ox::make_unique<TileSheetClipboard>();
 | 
			
		||||
	const auto &s = activeSubSheet();
 | 
			
		||||
	for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
 | 
			
		||||
		for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
 | 
			
		||||
			auto pt = ox::Point(x, y);
 | 
			
		||||
			const auto idx = core::idx(s, pt);
 | 
			
		||||
			const auto c = getPixel(s, m_img.bpp, idx);
 | 
			
		||||
			pt.x -= m_selectionBounds.x;
 | 
			
		||||
			pt.y -= m_selectionBounds.y;
 | 
			
		||||
			cb->addPixel(pt, c);
 | 
			
		||||
			blankCb.addPixel(pt, 0);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
 | 
			
		||||
	const auto pt2 = ox::Point(s.columns * TileWidth, s.rows * TileHeight);
 | 
			
		||||
	auto const&s = activeSubSheet();
 | 
			
		||||
	iterateSelectionRows(*m_selection, [&](int x, int y) {
 | 
			
		||||
		auto pt = ox::Point{x, y};
 | 
			
		||||
		auto const idx = core::idx(s, pt);
 | 
			
		||||
		auto const c = getPixel(s, m_img.bpp, idx);
 | 
			
		||||
		pt -= m_selection->a;
 | 
			
		||||
		cb->addPixel(pt, c);
 | 
			
		||||
		blankCb.addPixel(pt, 0);
 | 
			
		||||
	});
 | 
			
		||||
	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));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::copy() {
 | 
			
		||||
	auto cb = ox::make_unique<TileSheetClipboard>();
 | 
			
		||||
	for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
 | 
			
		||||
		for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
 | 
			
		||||
			auto pt = ox::Point(x, y);
 | 
			
		||||
			const auto &s = activeSubSheet();
 | 
			
		||||
			const auto idx = core::idx(s, pt);
 | 
			
		||||
			const auto c = getPixel(s, m_img.bpp, idx);
 | 
			
		||||
			pt.x -= m_selectionBounds.x;
 | 
			
		||||
			pt.y -= m_selectionBounds.y;
 | 
			
		||||
			cb->addPixel(pt, c);
 | 
			
		||||
		}
 | 
			
		||||
	if (!m_selection) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	auto cb = ox::make_unique<TileSheetClipboard>();
 | 
			
		||||
	iterateSelectionRows(*m_selection, [&](int x, int y) {
 | 
			
		||||
		auto pt = ox::Point{x, y};
 | 
			
		||||
		const auto&s = activeSubSheet();
 | 
			
		||||
		const auto idx = core::idx(s, pt);
 | 
			
		||||
		const auto c = getPixel(s, m_img.bpp, idx);
 | 
			
		||||
		pt -= m_selection->a;
 | 
			
		||||
		cb->addPixel(pt, c);
 | 
			
		||||
	});
 | 
			
		||||
	turbine::setClipboardObject(m_tctx, std::move(cb));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::paste() {
 | 
			
		||||
	if (!m_selection) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	auto [cb, err] = turbine::getClipboardObject<TileSheetClipboard>(m_tctx);
 | 
			
		||||
	if (err) {
 | 
			
		||||
		oxLogError(err);
 | 
			
		||||
		oxErrf("Could not read clipboard: {}", toStr(err));
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	const auto &s = activeSubSheet();
 | 
			
		||||
	const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
 | 
			
		||||
	const auto pt2 = ox::Point(s.columns * TileWidth, s.rows * TileHeight);
 | 
			
		||||
	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));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TileSheetEditorModel::acceptsClipboardPayload() const noexcept {
 | 
			
		||||
	auto const cb = getClipboardObject<TileSheetClipboard>(m_tctx);
 | 
			
		||||
	return cb.ok();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::StringView TileSheetEditorModel::palPath() const noexcept {
 | 
			
		||||
	auto [path, err] = m_img.defaultPalette.getPath();
 | 
			
		||||
	if (err) {
 | 
			
		||||
@@ -206,30 +215,26 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::select(ox::Point const&pt) noexcept {
 | 
			
		||||
	if (!m_selectionOngoing) {
 | 
			
		||||
		m_selectionOrigin = pt;
 | 
			
		||||
		m_selectionOngoing = true;
 | 
			
		||||
		m_selectionBounds = {pt, pt};
 | 
			
		||||
		m_updated = true;
 | 
			
		||||
	} else if (m_selectionBounds.pt2() != pt) {
 | 
			
		||||
		m_selectionBounds = {m_selectionOrigin, pt};
 | 
			
		||||
	if (m_selTracker.updateCursorPoint(pt)) {
 | 
			
		||||
		m_selection.emplace(m_selTracker.selection());
 | 
			
		||||
		m_updated = true;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::completeSelection() noexcept {
 | 
			
		||||
	m_selectionOngoing = false;
 | 
			
		||||
	auto &s = activeSubSheet();
 | 
			
		||||
	auto pt = m_selectionBounds.pt2();
 | 
			
		||||
	pt.x = ox::min(s.columns * TileWidth, pt.x);
 | 
			
		||||
	pt.y = ox::min(s.rows * TileHeight, pt.y);
 | 
			
		||||
	m_selectionBounds.setPt2(pt);
 | 
			
		||||
	if (m_selTracker.selectionOngoing()) {
 | 
			
		||||
		m_selTracker.finishSelection();
 | 
			
		||||
		m_selection.emplace(m_selTracker.selection());
 | 
			
		||||
		auto&pt = m_selection->b;
 | 
			
		||||
		auto&s = activeSubSheet();
 | 
			
		||||
		pt.x = ox::min(s.columns * TileWidth - 1, pt.x);
 | 
			
		||||
		pt.y = ox::min(s.rows * TileHeight - 1, pt.y);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::clearSelection() noexcept {
 | 
			
		||||
	m_updated = true;
 | 
			
		||||
	m_selectionOrigin = {-1, -1};
 | 
			
		||||
	m_selectionBounds = {{-1, -1}, {-1, -1}};
 | 
			
		||||
	m_selection.reset();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TileSheetEditorModel::updated() const noexcept {
 | 
			
		||||
@@ -266,9 +271,9 @@ ox::Error TileSheetEditorModel::saveFile() noexcept {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept {
 | 
			
		||||
	const auto &s = activeSubSheet();
 | 
			
		||||
	const auto pt = idxToPt(static_cast<int>(idx), s.columns);
 | 
			
		||||
	return m_selectionBounds.contains(pt);
 | 
			
		||||
	auto const&s = activeSubSheet();
 | 
			
		||||
	auto const pt = idxToPt(static_cast<int>(idx), s.columns);
 | 
			
		||||
	return m_selection && m_selection->contains(pt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept {
 | 
			
		||||
@@ -305,7 +310,7 @@ void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int o
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
 | 
			
		||||
	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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,10 +33,9 @@ class TileSheetEditorModel: public ox::SignalHandler {
 | 
			
		||||
		size_t m_palettePage{};
 | 
			
		||||
		studio::UndoStack &m_undoStack;
 | 
			
		||||
		class DrawCommand *m_ongoingDrawCommand = nullptr;
 | 
			
		||||
		studio::SelectionTracker m_selTracker;
 | 
			
		||||
		ox::Optional<studio::Selection> m_selection;
 | 
			
		||||
		bool m_updated = false;
 | 
			
		||||
		bool m_selectionOngoing = false;
 | 
			
		||||
		ox::Point m_selectionOrigin = {-1, -1};
 | 
			
		||||
		ox::Bounds m_selectionBounds = {{-1, -1}, {-1, -1}};
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack);
 | 
			
		||||
@@ -49,6 +48,9 @@ class TileSheetEditorModel: public ox::SignalHandler {
 | 
			
		||||
 | 
			
		||||
		void paste();
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		bool acceptsClipboardPayload() const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr TileSheet const&img() const noexcept;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -84,9 +84,18 @@ void TileSheetEditorView::clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clic
 | 
			
		||||
	m_model.fill(pt, static_cast<int>(m_palIdx));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::releaseMouseButton() noexcept {
 | 
			
		||||
	m_model.endDrawCommand();
 | 
			
		||||
	m_model.completeSelection();
 | 
			
		||||
void TileSheetEditorView::releaseMouseButton(TileSheetTool tool) noexcept {
 | 
			
		||||
	switch (tool) {
 | 
			
		||||
		case TileSheetTool::Draw:
 | 
			
		||||
			m_model.endDrawCommand();
 | 
			
		||||
			break;
 | 
			
		||||
		case TileSheetTool::Select:
 | 
			
		||||
			m_model.completeSelection();
 | 
			
		||||
			break;
 | 
			
		||||
		case TileSheetTool::Fill:
 | 
			
		||||
		case TileSheetTool::None:
 | 
			
		||||
			break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::resizeView(ox::Vec2 const&sz) noexcept {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,8 @@
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
enum class TileSheetTool: int {
 | 
			
		||||
enum class TileSheetTool {
 | 
			
		||||
	None,
 | 
			
		||||
	Select,
 | 
			
		||||
	Draw,
 | 
			
		||||
	Fill,
 | 
			
		||||
@@ -33,6 +34,8 @@ constexpr auto toString(TileSheetTool t) noexcept {
 | 
			
		||||
			return "Draw";
 | 
			
		||||
		case TileSheetTool::Fill:
 | 
			
		||||
			return "Fill";
 | 
			
		||||
		case TileSheetTool::None:
 | 
			
		||||
			return "None";
 | 
			
		||||
	}
 | 
			
		||||
	return "";
 | 
			
		||||
}
 | 
			
		||||
@@ -66,7 +69,7 @@ class TileSheetEditorView: public ox::SignalHandler {
 | 
			
		||||
 | 
			
		||||
		void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
 | 
			
		||||
 | 
			
		||||
		void releaseMouseButton() noexcept;
 | 
			
		||||
		void releaseMouseButton(TileSheetTool tool) noexcept;
 | 
			
		||||
 | 
			
		||||
		void scrollV(ox::Vec2 const&paneSz, float wheel, bool zoomMod) noexcept;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -139,10 +139,7 @@ void TileSheetPixels::setBufferObjects(ox::Vec2 const&paneSize) noexcept {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		if (m_model.pixelSelected(i)) {
 | 
			
		||||
			auto const r = red16(color) / 2;
 | 
			
		||||
			auto const g = (green16(color) + 20) / 2;
 | 
			
		||||
			auto const b = (blue16(color) + 31) / 2;
 | 
			
		||||
			color = color16(r, g, b);
 | 
			
		||||
			color = applySelectionColor(color);
 | 
			
		||||
		}
 | 
			
		||||
		setPixelBufferObject(paneSize, static_cast<unsigned>(i * VertexVboRows), fx, fy, color, vbo, ebo);
 | 
			
		||||
	});
 | 
			
		||||
 
 | 
			
		||||
@@ -15,11 +15,11 @@ std::size_t idx(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
static TileSheet::SubSheet const*getSubsheet(TileSheet::SubSheet const&ss, SubSheetId const id) noexcept {
 | 
			
		||||
static TileSheet::SubSheet const *getSubsheet(TileSheet::SubSheet const&ss, SubSheetId const id) noexcept {
 | 
			
		||||
	if (ss.id == id) {
 | 
			
		||||
		return &ss;
 | 
			
		||||
	}
 | 
			
		||||
	for (auto const&child : ss.subsheets) {
 | 
			
		||||
	for (auto const&child: ss.subsheets) {
 | 
			
		||||
		if (auto out = getSubsheet(child, id)) {
 | 
			
		||||
			return out;
 | 
			
		||||
		}
 | 
			
		||||
@@ -34,7 +34,7 @@ static size_t getTileCnt(TileSheet::SubSheet const&ss, int const bpp) noexcept {
 | 
			
		||||
		return ss.pixels.size() / bytesPerTile;
 | 
			
		||||
	} else {
 | 
			
		||||
		size_t out{};
 | 
			
		||||
		for (auto const&child : ss.subsheets) {
 | 
			
		||||
		for (auto const&child: ss.subsheets) {
 | 
			
		||||
			out += getTileCnt(child, bpp);
 | 
			
		||||
		}
 | 
			
		||||
		return out;
 | 
			
		||||
@@ -45,7 +45,7 @@ size_t getTileCnt(TileSheet const&ts) noexcept {
 | 
			
		||||
	return getTileCnt(ts.subsheet, ts.bpp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheet const*getSubsheet(TileSheet const&ts, SubSheetId const id) noexcept {
 | 
			
		||||
TileSheet::SubSheet const *getSubsheet(TileSheet const&ts, SubSheetId const id) noexcept {
 | 
			
		||||
	return getSubsheet(ts.subsheet, id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -54,7 +54,7 @@ static ox::Optional<size_t> getPixelIdx(
 | 
			
		||||
		SubSheetId const id,
 | 
			
		||||
		size_t idx,
 | 
			
		||||
		int8_t const bpp) noexcept {
 | 
			
		||||
	for (auto const&child : ss.subsheets) {
 | 
			
		||||
	for (auto const&child: ss.subsheets) {
 | 
			
		||||
		if (child.id == id) {
 | 
			
		||||
			return ox::Optional<size_t>(ox::in_place, idx);
 | 
			
		||||
		}
 | 
			
		||||
@@ -106,8 +106,8 @@ uint8_t getPixel(TileSheet::SubSheet const&ss, int8_t pBpp, ox::Point const&pt)
 | 
			
		||||
	return getPixel(ss, pBpp, idx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
 | 
			
		||||
	auto &pixel = ss.pixels[static_cast<std::size_t>(idx / 2)];
 | 
			
		||||
static void setPixel(ox::Vector<uint8_t> &pixels, int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
 | 
			
		||||
	auto &pixel = pixels[static_cast<std::size_t>(idx / 2)];
 | 
			
		||||
	if (pBpp == 4) {
 | 
			
		||||
		if (idx & 1) {
 | 
			
		||||
			pixel = static_cast<uint8_t>((pixel & 0b0000'1111) | (palIdx << 4));
 | 
			
		||||
@@ -119,22 +119,39 @@ void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, uint64_t idx, uint8_t palIdx
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
 | 
			
		||||
	setPixel(ss.pixels, pBpp, idx, palIdx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void setPixel(ox::Vector<uint8_t> &pixels, int columns, int8_t pBpp, ox::Point const&pt, uint8_t palIdx) noexcept {
 | 
			
		||||
	const auto idx = ptToIdx(pt, columns);
 | 
			
		||||
	setPixel(pixels, pBpp, idx, palIdx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, ox::Point const&pt, uint8_t palIdx) noexcept {
 | 
			
		||||
	const auto idx = ptToIdx(pt, ss.columns);
 | 
			
		||||
	setPixel(ss, pBpp, idx, palIdx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error setPixelCount(TileSheet::SubSheet &ss, int8_t pBpp, std::size_t cnt) noexcept {
 | 
			
		||||
static ox::Error setPixelCount(ox::Vector<uint8_t> &pixels, int8_t pBpp, std::size_t cnt) noexcept {
 | 
			
		||||
	size_t sz{};
 | 
			
		||||
	switch (pBpp) {
 | 
			
		||||
		case 4:
 | 
			
		||||
			ss.pixels.resize(cnt / 2);
 | 
			
		||||
			return OxError(0);
 | 
			
		||||
			sz = cnt / 2;
 | 
			
		||||
			break;
 | 
			
		||||
		case 8:
 | 
			
		||||
			ss.pixels.resize(cnt);
 | 
			
		||||
			return OxError(0);
 | 
			
		||||
			sz = cnt;
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			return OxError(1, "Invalid pBpp used for TileSheet::SubSheet::setPixelCount");
 | 
			
		||||
	}
 | 
			
		||||
	pixels.reserve(sz);
 | 
			
		||||
	pixels.resize(sz);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error setPixelCount(TileSheet::SubSheet &ss, int8_t pBpp, std::size_t cnt) noexcept {
 | 
			
		||||
	return setPixelCount(ss.pixels, pBpp, cnt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unsigned pixelCnt(TileSheet::SubSheet const&ss, int8_t pBpp) noexcept {
 | 
			
		||||
@@ -142,6 +159,23 @@ unsigned pixelCnt(TileSheet::SubSheet const&ss, int8_t pBpp) noexcept {
 | 
			
		||||
	return pBpp == 4 ? pixelsSize * 2 : pixelsSize;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error resizeSubsheet(TileSheet::SubSheet &ss, int8_t pBpp, ox::Size const&sz) noexcept {
 | 
			
		||||
	ox::Vector<uint8_t> out;
 | 
			
		||||
	oxReturnError(setPixelCount(out, pBpp, static_cast<size_t>(sz.width * sz.height) * PixelsPerTile));
 | 
			
		||||
	auto const w = ss.columns * TileWidth;
 | 
			
		||||
	auto const h = ss.rows * TileHeight;
 | 
			
		||||
	for (auto x = 0; x < w; ++x) {
 | 
			
		||||
		for (auto y = 0; y < h; ++y) {
 | 
			
		||||
			auto const palIdx = getPixel(ss, pBpp, {x, y});
 | 
			
		||||
			setPixel(out, sz.width, pBpp, {x, y}, palIdx);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ss.columns = sz.width;
 | 
			
		||||
	ss.rows = sz.height;
 | 
			
		||||
	ss.pixels = std::move(out);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Result<ox::StringView> getNameFor(TileSheet::SubSheet const&ss, SubSheetId pId) noexcept {
 | 
			
		||||
	if (ss.id == pId) {
 | 
			
		||||
		return ox::StringView(ss.name);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user