Compare commits
	
		
			8 Commits
		
	
	
		
			stable
			...
			ba1bf950a8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ba1bf950a8 | |||
| 95950441d1 | |||
| 1b32bdfcad | |||
| 3544392fa8 | |||
| 144d234d09 | |||
| 465fb06f76 | |||
| db953dd0d1 | |||
| daab4dc4f5 | 
| @@ -233,7 +233,9 @@ struct TileSheetV4 { | ||||
| [[nodiscard]] | ||||
| constexpr bool valid(TileSheetV4::SubSheet const&ss, int bpp) noexcept { | ||||
| 	auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1); | ||||
| 	return ox::all_of(ss.subsheets.begin(), ss.subsheets.end(), | ||||
| 	return | ||||
| 		(ss.pixels.empty() || ss.subsheets.empty()) && | ||||
| 		ox::all_of(ss.subsheets.begin(), ss.subsheets.end(), | ||||
| 				[bpp, bytes](TileSheetV4::SubSheet const&s) { | ||||
| 			return bytes == s.pixels.size() && valid(s, bpp); | ||||
| 		}); | ||||
| @@ -245,8 +247,14 @@ constexpr bool valid(TileSheetV4 const&ts) noexcept { | ||||
| } | ||||
|  | ||||
| constexpr void repair(TileSheetV4::SubSheet &ss, int bpp) noexcept { | ||||
| 	if (ss.subsheets.empty()) { | ||||
| 		auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1); | ||||
| 		ss.pixels.resize(bytes); | ||||
| 	} else { | ||||
| 		ss.pixels.clear(); | ||||
| 		ss.columns = -1; | ||||
| 		ss.rows = -1; | ||||
| 	} | ||||
| 	for (auto &s : ss.subsheets) { | ||||
| 		repair(s, bpp); | ||||
| 	} | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| target_sources( | ||||
| 	NostalgiaCore-Studio PRIVATE | ||||
| 		commands/addcolorcommand.cpp | ||||
| 		commands/addpagecommand.cpp | ||||
| 		commands/applycolorallpagescommand.cpp | ||||
| 		commands/duplicatepagecommand.cpp | ||||
| 		commands/movecolorcommand.cpp | ||||
|   | ||||
| @@ -0,0 +1,28 @@ | ||||
| /* | ||||
|  * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. | ||||
|  */ | ||||
|  | ||||
| #include "commands.hpp" | ||||
|  | ||||
| #include "addpagecommand.hpp" | ||||
|  | ||||
| namespace nostalgia::core { | ||||
|  | ||||
| AddPageCommand::AddPageCommand(Palette &pal) noexcept: | ||||
| 		m_pal(pal) {} | ||||
|  | ||||
| int AddPageCommand::commandId() const noexcept { | ||||
| 	return static_cast<int>(PaletteEditorCommandId::AddPage); | ||||
| } | ||||
|  | ||||
| ox::Error AddPageCommand::redo() noexcept { | ||||
| 	m_pal.pages.emplace_back(ox::sfmt("Page {}", m_pal.pages.size() + 1), ox::Vector<PaletteColor>{}); | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| ox::Error AddPageCommand::undo() noexcept { | ||||
| 	m_pal.pages.pop_back(); | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,31 @@ | ||||
| /* | ||||
|  * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <studio/studio.hpp> | ||||
|  | ||||
| #include <nostalgia/core/palette.hpp> | ||||
|  | ||||
| namespace nostalgia::core { | ||||
|  | ||||
| class AddPageCommand: public studio::UndoCommand { | ||||
| 	private: | ||||
| 		Palette &m_pal; | ||||
|  | ||||
| 	public: | ||||
| 		explicit AddPageCommand(Palette &pal) noexcept; | ||||
|  | ||||
| 		~AddPageCommand() noexcept override = default; | ||||
|  | ||||
| 		[[nodiscard]] | ||||
| 		int commandId() const noexcept final; | ||||
|  | ||||
| 		ox::Error redo() noexcept final; | ||||
|  | ||||
| 		ox::Error undo() noexcept final; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
| @@ -9,6 +9,7 @@ namespace nostalgia::core { | ||||
| enum class PaletteEditorCommandId { | ||||
| 	ApplyColorAllPages, | ||||
| 	RenamePage, | ||||
| 	AddPage, | ||||
| 	DuplicatePage, | ||||
| 	RemovePage, | ||||
| 	AddColor, | ||||
|   | ||||
| @@ -23,7 +23,7 @@ int DuplicatePageCommand::commandId() const noexcept { | ||||
| } | ||||
|  | ||||
| ox::Error DuplicatePageCommand::redo() noexcept { | ||||
| 	m_pal.pages.emplace(m_dstIdx, "", std::move(m_page)); | ||||
| 	m_pal.pages.emplace(m_dstIdx, ox::sfmt("Page {}", m_pal.pages.size() + 1), std::move(m_page)); | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -7,6 +7,7 @@ | ||||
| #include <keel/media.hpp> | ||||
|  | ||||
| #include "commands/addcolorcommand.hpp" | ||||
| #include "commands/addpagecommand.hpp" | ||||
| #include "commands/applycolorallpagescommand.hpp" | ||||
| #include "commands/duplicatepagecommand.hpp" | ||||
| #include "commands/movecolorcommand.hpp" | ||||
| @@ -196,7 +197,11 @@ void PaletteEditorImGui::drawPagesEditor() noexcept { | ||||
| 	constexpr auto toolbarHeight = 40; | ||||
| 	auto const btnSz = ImVec2{paneSz.x / 4 - 5.5f, 24}; | ||||
| 	if (ImGui::Button("Add", btnSz)) { | ||||
| 		if (m_pal.pages.empty()) { | ||||
| 			std::ignore = pushCommand<AddPageCommand>(m_pal); | ||||
| 		} else { | ||||
| 			std::ignore = pushCommand<DuplicatePageCommand>(m_pal, 0u, m_pal.pages.size()); | ||||
| 		} | ||||
| 		m_page = m_pal.pages.size() - 1; | ||||
| 	} | ||||
| 	ImGui::SameLine(); | ||||
|   | ||||
| @@ -9,27 +9,24 @@ namespace nostalgia::core { | ||||
| core::UpdateSubSheetCommand::UpdateSubSheetCommand( | ||||
| 		TileSheet &img, | ||||
| 		TileSheet::SubSheetIdx idx, | ||||
| 		ox::String name, | ||||
| 		int cols, | ||||
| 		int rows) noexcept: | ||||
| 		m_img(img), | ||||
| 		m_idx(std::move(idx)), | ||||
| 		m_sheet(getSubSheet(m_img, m_idx)), | ||||
| 		m_newName(std::move(name)), | ||||
| 		m_newCols(cols), | ||||
| 		m_newRows(rows) { | ||||
| 		ox::StringParam name, | ||||
| 		int const cols, | ||||
| 		int const rows): | ||||
| 		m_img{img}, | ||||
| 		m_idx{std::move(idx)}, | ||||
| 		m_sheet{getSubSheet(m_img, m_idx)} { | ||||
| 	m_sheet = getSubSheet(m_img, m_idx); | ||||
| 	m_sheet.name = std::move(name); | ||||
| 	OX_THROW_ERROR(resizeSubsheet(m_sheet, m_img.bpp, {cols, rows})); | ||||
| } | ||||
|  | ||||
| ox::Error UpdateSubSheetCommand::redo() noexcept { | ||||
| 	auto &sheet = getSubSheet(m_img, m_idx); | ||||
| 	sheet.name = m_newName; | ||||
| 	oxLogError(resizeSubsheet(sheet, m_img.bpp, {m_newCols, m_newRows})); | ||||
| 	std::swap(m_sheet, getSubSheet(m_img, m_idx)); | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| ox::Error UpdateSubSheetCommand::undo() noexcept { | ||||
| 	auto &sheet = getSubSheet(m_img, m_idx); | ||||
| 	sheet = m_sheet; | ||||
| 	std::swap(m_sheet, getSubSheet(m_img, m_idx)); | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,17 +13,14 @@ class UpdateSubSheetCommand: public TileSheetCommand { | ||||
| 		TileSheet &m_img; | ||||
| 		TileSheet::SubSheetIdx m_idx; | ||||
| 		TileSheet::SubSheet m_sheet; | ||||
| 		ox::String m_newName; | ||||
| 		int m_newCols = 0; | ||||
| 		int m_newRows = 0; | ||||
|  | ||||
| 	public: | ||||
| 		UpdateSubSheetCommand( | ||||
| 				TileSheet &img, | ||||
| 				TileSheet::SubSheetIdx idx, | ||||
| 				ox::String name, | ||||
| 				ox::StringParam name, | ||||
| 				int cols, | ||||
| 				int rows) noexcept; | ||||
| 				int rows); | ||||
|  | ||||
| 		ox::Error redo() noexcept final; | ||||
|  | ||||
|   | ||||
| @@ -234,7 +234,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept { | ||||
| 		ImGui::BeginChild("SubSheets", {s_palViewWidth - 24, ySize / 2.f}, true); | ||||
| 		{ | ||||
| 			static constexpr auto btnHeight = ig::BtnSz.y; | ||||
| 			auto const btnSize = ImVec2{btnHeight, btnHeight}; | ||||
| 			auto constexpr btnSize = ImVec2{btnHeight, btnHeight}; | ||||
| 			if (ig::PushButton("+", btnSize)) { | ||||
| 				auto insertOnIdx = m_model.activeSubSheetIdx(); | ||||
| 				auto const&parent = m_model.activeSubSheet(); | ||||
| @@ -258,7 +258,10 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept { | ||||
| 				m_exportMenu.show(); | ||||
| 			} | ||||
| 			TileSheet::SubSheetIdx path; | ||||
| 			static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; | ||||
| 			static constexpr auto flags = | ||||
| 				ImGuiTableFlags_RowBg | | ||||
|                 ImGuiTableFlags_NoBordersInBody | | ||||
|                 ImGuiTableFlags_ScrollY; | ||||
|             if (ImGui::BeginTable("Subsheets", 4, flags)) { | ||||
|                 ImGui::TableSetupColumn("Subsheet", ImGuiTableColumnFlags_NoHide); | ||||
|                 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 25); | ||||
| @@ -462,8 +465,12 @@ void TileSheetEditorImGui::drawPaletteMenu() noexcept { | ||||
| 		} | ||||
| 	} | ||||
| 	// header | ||||
| 	auto constexpr palTblFlags = | ||||
| 		ImGuiTableFlags_RowBg | | ||||
|         ImGuiTableFlags_SizingStretchProp | | ||||
|         ImGuiTableFlags_ScrollY; | ||||
| 	if (ImGui::BeginTable( | ||||
| 			"PaletteTable", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) { | ||||
| 			"PaletteTable", 4, palTblFlags)) { | ||||
| 		ImGui::TableSetupColumn("Idx", 0, 0.6f); | ||||
| 		ImGui::TableSetupColumn("", 0, 0.22f); | ||||
| 		ImGui::TableSetupColumn("Name", 0, 3); | ||||
| @@ -544,7 +551,7 @@ void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &tctx) noexcept | ||||
| 	auto const popupHeight = modSize ? 130.f : 85.f; | ||||
| 	auto const popupSz = ImVec2{popupWidth, popupHeight}; | ||||
| 	if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) { | ||||
| 		ImGui::InputText("Name", m_name.data(), m_name.cap()); | ||||
| 		ig::InputText("Name", m_name); | ||||
| 		if (modSize) { | ||||
| 			ImGui::InputInt("Columns", &m_cols); | ||||
| 			ImGui::InputInt("Rows", &m_rows); | ||||
|   | ||||
| @@ -26,11 +26,6 @@ | ||||
|  | ||||
| namespace nostalgia::core { | ||||
|  | ||||
| Palette const TileSheetEditorModel::s_defaultPalette = { | ||||
| 	.colorNames = {ox::Vector<ox::String>{{}}}, | ||||
| 	.pages = {{"Page 1", ox::Vector<Color16>(128)}}, | ||||
| }; | ||||
|  | ||||
| // delete pixels of all non-leaf nodes | ||||
| static void normalizeSubsheets(TileSheet::SubSheet &ss) noexcept { | ||||
| 	if (ss.subsheets.empty()) { | ||||
| @@ -42,7 +37,14 @@ static void normalizeSubsheets(TileSheet::SubSheet &ss) noexcept { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| TileSheetEditorModel::TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack): | ||||
|  | ||||
| Palette const TileSheetEditorModel::s_defaultPalette = { | ||||
| 	.colorNames = {ox::Vector<ox::String>{{}}}, | ||||
| 	.pages = {{"Page 1", ox::Vector<Color16>(128)}}, | ||||
| }; | ||||
|  | ||||
| TileSheetEditorModel::TileSheetEditorModel( | ||||
| 		studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack): | ||||
| 	m_sctx(sctx), | ||||
| 	m_tctx(m_sctx.tctx), | ||||
| 	m_path(path), | ||||
| @@ -62,7 +64,7 @@ void TileSheetEditorModel::cut() { | ||||
| 	TileSheetClipboard blankCb; | ||||
| 	auto cb = ox::make_unique<TileSheetClipboard>(); | ||||
| 	auto const&s = activeSubSheet(); | ||||
| 	iterateSelectionRows(*m_selection, [&](int x, int y) { | ||||
| 	iterateSelectionRows(*m_selection, [&](int const x, int const y) { | ||||
| 		auto pt = ox::Point{x, y}; | ||||
| 		auto const idx = core::idx(s, pt); | ||||
| 		auto const c = getPixel(s, m_img.bpp, idx); | ||||
| @@ -73,7 +75,8 @@ void TileSheetEditorModel::cut() { | ||||
| 	auto const pt1 = m_selection->a; | ||||
| 	auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight}; | ||||
| 	turbine::setClipboardObject(m_tctx, std::move(cb)); | ||||
| 	pushCommand(ox::make<CutPasteCommand>(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb)); | ||||
| 	pushCommand(ox::make<CutPasteCommand>( | ||||
| 		CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb)); | ||||
| } | ||||
|  | ||||
| void TileSheetEditorModel::copy() { | ||||
| @@ -81,7 +84,7 @@ void TileSheetEditorModel::copy() { | ||||
| 		return; | ||||
| 	} | ||||
| 	auto cb = ox::make_unique<TileSheetClipboard>(); | ||||
| 	iterateSelectionRows(*m_selection, [&](int x, int y) { | ||||
| 	iterateSelectionRows(*m_selection, [&](int const x, int const y) { | ||||
| 		auto pt = ox::Point{x, y}; | ||||
| 		const auto&s = activeSubSheet(); | ||||
| 		const auto idx = core::idx(s, pt); | ||||
| @@ -105,7 +108,8 @@ void TileSheetEditorModel::paste() { | ||||
| 	auto const&s = activeSubSheet(); | ||||
| 	auto const pt1 = m_selection->a; | ||||
| 	auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight}; | ||||
| 	pushCommand(ox::make<CutPasteCommand>(CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb)); | ||||
| 	pushCommand(ox::make<CutPasteCommand>( | ||||
| 		CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb)); | ||||
| } | ||||
|  | ||||
| bool TileSheetEditorModel::acceptsClipboardPayload() const noexcept { | ||||
| @@ -120,8 +124,8 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept { | ||||
| 	} | ||||
| 	constexpr ox::StringView uuidPrefix = "uuid://"; | ||||
| 	if (ox::beginsWith(path, uuidPrefix)) { | ||||
| 		auto uuid = ox::StringView(&path[uuidPrefix.bytes()], path.bytes() - uuidPrefix.bytes()); | ||||
| 		auto out = keelCtx(m_tctx).uuidToPath.at(uuid); | ||||
| 		auto const uuid = ox::StringView(&path[uuidPrefix.bytes()], path.bytes() - uuidPrefix.bytes()); | ||||
| 		auto const out = keelCtx(m_tctx).uuidToPath.at(uuid); | ||||
| 		if (out.error) { | ||||
| 			return {}; | ||||
| 		} | ||||
| @@ -131,13 +135,14 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| ox::Error TileSheetEditorModel::setPalette(ox::StringView path) noexcept { | ||||
| ox::Error TileSheetEditorModel::setPalette(ox::StringViewCR path) noexcept { | ||||
| 	OX_REQUIRE(uuid, keelCtx(m_tctx).pathToUuid.at(path)); | ||||
| 	pushCommand(ox::make<PaletteChangeCommand>(activeSubSheetIdx(), m_img, uuid->toString())); | ||||
| 	pushCommand(ox::make<PaletteChangeCommand>( | ||||
| 		activeSubSheetIdx(), m_img, uuid->toString())); | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| void TileSheetEditorModel::setPalettePage(size_t pg) noexcept { | ||||
| void TileSheetEditorModel::setPalettePage(size_t const pg) noexcept { | ||||
| 	m_palettePage = ox::clamp<size_t>(pg, 0, m_pal->pages.size() - 1); | ||||
| 	m_updated = true; | ||||
| } | ||||
| @@ -146,7 +151,7 @@ size_t TileSheetEditorModel::palettePage() const noexcept { | ||||
| 	return m_palettePage; | ||||
| } | ||||
|  | ||||
| void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept { | ||||
| void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t const palIdx) noexcept { | ||||
| 	const auto &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx); | ||||
| 	if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) { | ||||
| 		return; | ||||
| @@ -155,7 +160,8 @@ void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t palIdx) n | ||||
| 	if (m_ongoingDrawCommand) { | ||||
| 		m_updated = m_updated || m_ongoingDrawCommand->append(idx); | ||||
| 	} else if (getPixel(activeSubSheet, m_img.bpp, idx) != palIdx) { | ||||
| 		pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx))); | ||||
| 		pushCommand(ox::make<DrawCommand>( | ||||
| 			m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx))); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -171,16 +177,20 @@ void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept | ||||
| 	pushCommand(ox::make<RmSubSheetCommand>(m_img, idx)); | ||||
| } | ||||
|  | ||||
| void TileSheetEditorModel::insertTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { | ||||
| void TileSheetEditorModel::insertTiles( | ||||
| 	TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept { | ||||
| 	pushCommand(ox::make<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt)); | ||||
| } | ||||
|  | ||||
| void TileSheetEditorModel::deleteTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { | ||||
| void TileSheetEditorModel::deleteTiles( | ||||
| 	TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept { | ||||
| 	pushCommand(ox::make<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt)); | ||||
| } | ||||
|  | ||||
| ox::Error TileSheetEditorModel::updateSubsheet(TileSheet::SubSheetIdx const&idx, ox::StringView const&name, int cols, int rows) noexcept { | ||||
| 	pushCommand(ox::make<UpdateSubSheetCommand>(m_img, idx, ox::String(name), cols, rows)); | ||||
| ox::Error TileSheetEditorModel::updateSubsheet( | ||||
| 	TileSheet::SubSheetIdx const&idx, ox::StringViewCR name, int const cols, int const rows) noexcept { | ||||
| 	OX_REQUIRE(cmd, ox::makeCatch<UpdateSubSheetCommand>(m_img, idx, name, cols, rows)); | ||||
| 	pushCommand(cmd); | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| @@ -189,7 +199,7 @@ void TileSheetEditorModel::setActiveSubsheet(TileSheet::SubSheetIdx const&idx) n | ||||
| 	this->activeSubsheetChanged.emit(m_activeSubsSheetIdx); | ||||
| } | ||||
|  | ||||
| void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept { | ||||
| void TileSheetEditorModel::fill(ox::Point const&pt, int const palIdx) noexcept { | ||||
| 	auto const&activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx); | ||||
| 	// build idx list | ||||
| 	if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) { | ||||
| @@ -197,10 +207,10 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept { | ||||
| 	} | ||||
| 	ox::Array<bool, PixelsPerTile> updateMap = {}; | ||||
| 	auto const oldColor = getPixel(activeSubSheet, m_img.bpp, pt); | ||||
| 	getFillPixels(updateMap, pt, oldColor); | ||||
| 	getFillPixels(activeSubSheet, updateMap, pt, oldColor); | ||||
| 	ox::Vector<std::size_t> idxList; | ||||
| 	auto i = core::idx(activeSubSheet, pt) / PixelsPerTile * PixelsPerTile; | ||||
| 	for (auto u : updateMap) { | ||||
| 	for (auto const u : updateMap) { | ||||
| 		if (u) { | ||||
| 			idxList.emplace_back(i); | ||||
| 		} | ||||
| @@ -230,7 +240,7 @@ void TileSheetEditorModel::completeSelection() noexcept { | ||||
| 		m_selTracker.finishSelection(); | ||||
| 		m_selection.emplace(m_selTracker.selection()); | ||||
| 		auto&pt = m_selection->b; | ||||
| 		auto&s = activeSubSheet(); | ||||
| 		auto const&s = activeSubSheet(); | ||||
| 		pt.x = ox::min(s.columns * TileWidth - 1, pt.x); | ||||
| 		pt.y = ox::min(s.rows * TileHeight - 1, pt.y); | ||||
| 	} | ||||
| @@ -275,47 +285,44 @@ ox::Error TileSheetEditorModel::saveFile() noexcept { | ||||
| 	return m_sctx.project->writeObj(m_path, m_img, ox::ClawFormat::Metal); | ||||
| } | ||||
|  | ||||
| bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept { | ||||
| bool TileSheetEditorModel::pixelSelected(std::size_t const idx) const noexcept { | ||||
| 	auto const&s = activeSubSheet(); | ||||
| 	auto const pt = idxToPt(static_cast<int>(idx), s.columns); | ||||
| 	return m_selection && m_selection->contains(pt); | ||||
| } | ||||
|  | ||||
| void TileSheetEditorModel::getFillPixels(ox::Span<bool> pixels, ox::Point const&pt, int oldColor) const noexcept { | ||||
| 	const auto &activeSubSheet = this->activeSubSheet(); | ||||
| 	const auto tileIdx = [activeSubSheet](const ox::Point &pt) noexcept { | ||||
| 		return ptToIdx(pt, activeSubSheet.columns) / PixelsPerTile; | ||||
| 	}; | ||||
| 	// get points | ||||
| 	const auto leftPt = pt + ox::Point(-1, 0); | ||||
| 	const auto rightPt = pt + ox::Point(1, 0); | ||||
| 	const auto topPt = pt + ox::Point(0, -1); | ||||
| 	const auto bottomPt = pt + ox::Point(0, 1); | ||||
| 	// calculate indices | ||||
| 	const auto idx = ptToIdx(pt, activeSubSheet.columns); | ||||
| 	const auto leftIdx = ptToIdx(leftPt, activeSubSheet.columns); | ||||
| 	const auto rightIdx = ptToIdx(rightPt, activeSubSheet.columns); | ||||
| 	const auto topIdx = ptToIdx(topPt, activeSubSheet.columns); | ||||
| 	const auto bottomIdx = ptToIdx(bottomPt, activeSubSheet.columns); | ||||
| 	const auto tile = tileIdx(pt); | ||||
| void TileSheetEditorModel::getFillPixels( | ||||
| 		TileSheet::SubSheet const&activeSubSheet, | ||||
| 		ox::Span<bool> pixels, | ||||
| 		ox::Point const&pt, | ||||
| 		int const oldColor) const noexcept { | ||||
| 	auto const idx = ptToIdx(pt, activeSubSheet.columns); | ||||
| 	auto const relIdx = idx % PixelsPerTile; | ||||
| 	if (pixels[relIdx] || getPixel(activeSubSheet, m_img.bpp, idx) != oldColor) { | ||||
| 		return; | ||||
| 	} | ||||
| 	// mark pixels to update | ||||
| 	pixels[idx % PixelsPerTile] = true; | ||||
| 	if (!pixels[leftIdx % PixelsPerTile] && tile == tileIdx(leftPt) && getPixel(activeSubSheet, m_img.bpp, leftIdx) == oldColor) { | ||||
| 		getFillPixels(pixels, leftPt, oldColor); | ||||
| 	pixels[relIdx] = true; | ||||
| 	if (pt.x % TileWidth != 0) { | ||||
| 		auto const leftPt = pt + ox::Point{-1, 0}; | ||||
| 		getFillPixels(activeSubSheet, pixels, leftPt, oldColor); | ||||
| 	} | ||||
| 	if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && getPixel(activeSubSheet, m_img.bpp, rightIdx) == oldColor) { | ||||
| 		getFillPixels(pixels, rightPt, oldColor); | ||||
| 	if (pt.x % TileWidth != TileWidth - 1) { | ||||
| 		auto const rightPt = pt + ox::Point{1, 0}; | ||||
| 		getFillPixels(activeSubSheet, pixels, rightPt, oldColor); | ||||
| 	} | ||||
| 	if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && getPixel(activeSubSheet, m_img.bpp, topIdx) == oldColor) { | ||||
| 		getFillPixels(pixels, topPt, oldColor); | ||||
| 	if (pt.y % TileHeight != 0) { | ||||
| 		auto const topPt = pt + ox::Point{0, -1}; | ||||
| 		getFillPixels(activeSubSheet, pixels, topPt, oldColor); | ||||
| 	} | ||||
| 	if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && getPixel(activeSubSheet, m_img.bpp, bottomIdx) == oldColor) { | ||||
| 		getFillPixels(pixels, bottomPt, oldColor); | ||||
| 	if (pt.y % TileHeight != TileHeight - 1) { | ||||
| 		auto const bottomPt = pt + ox::Point{0, 1}; | ||||
| 		getFillPixels(activeSubSheet, pixels, bottomPt, oldColor); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept { | ||||
| 	std::ignore = m_undoStack.push(ox::UPtr<studio::UndoCommand>(cmd)); | ||||
| 	std::ignore = m_undoStack.push(ox::UPtr<studio::UndoCommand>{cmd}); | ||||
| 	m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd); | ||||
| 	m_updated = true; | ||||
| } | ||||
|   | ||||
| @@ -4,9 +4,7 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <ox/std/bounds.hpp> | ||||
| #include <ox/std/point.hpp> | ||||
| #include <ox/std/trace.hpp> | ||||
| #include <ox/std/string.hpp> | ||||
|  | ||||
| #include <studio/studio.hpp> | ||||
| @@ -38,7 +36,7 @@ class TileSheetEditorModel: public ox::SignalHandler { | ||||
| 		bool m_updated = false; | ||||
|  | ||||
| 	public: | ||||
| 		TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack); | ||||
| 		TileSheetEditorModel(studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack); | ||||
|  | ||||
| 		~TileSheetEditorModel() override = default; | ||||
|  | ||||
| @@ -63,7 +61,7 @@ class TileSheetEditorModel: public ox::SignalHandler { | ||||
| 		[[nodiscard]] | ||||
| 		ox::StringView palPath() const noexcept; | ||||
|  | ||||
| 		ox::Error setPalette(ox::StringView path) noexcept; | ||||
| 		ox::Error setPalette(ox::StringViewCR path) noexcept; | ||||
|  | ||||
| 		void setPalettePage(size_t pg) noexcept; | ||||
|  | ||||
| @@ -128,7 +126,11 @@ class TileSheetEditorModel: public ox::SignalHandler { | ||||
| 		bool pixelSelected(std::size_t idx) const noexcept; | ||||
|  | ||||
| 	private: | ||||
| 		void getFillPixels(ox::Span<bool> pixels, ox::Point const&pt, int oldColor) const noexcept; | ||||
| 		void getFillPixels( | ||||
| 			TileSheet::SubSheet const&activeSubSheet, | ||||
| 			ox::Span<bool> pixels, | ||||
| 			ox::Point const&pt, | ||||
| 			int oldColor) const noexcept; | ||||
|  | ||||
| 		void pushCommand(studio::UndoCommand *cmd) noexcept; | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,7 @@ target_link_libraries( | ||||
|  | ||||
| target_compile_definitions( | ||||
| 	NostalgiaStudio PUBLIC | ||||
| 		OLYMPIC_APP_VERSION="d2024.12.1" | ||||
| 		OLYMPIC_APP_VERSION="d2024.12.3" | ||||
| ) | ||||
|  | ||||
| install( | ||||
|   | ||||
| @@ -50,7 +50,7 @@ void NewProject::draw(studio::StudioContext &ctx) noexcept { | ||||
|  | ||||
| void NewProject::drawNewProjectName(studio::StudioContext &sctx) noexcept { | ||||
| 	drawWindow(sctx.tctx, &m_open, [this, &sctx] { | ||||
| 		ImGui::InputText("Name", m_projectName.data(), m_projectName.cap()); | ||||
| 		ig::InputText("Name", m_projectName); | ||||
| 		ImGui::Text("Path: %s", m_projectPath.c_str()); | ||||
| 		if (ImGui::Button("Browse")) { | ||||
| 			oxLogError(studio::chooseDirectory().moveTo(m_projectPath)); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user