Squashed 'deps/nostalgia/' changes from ab025e88..4d63a65f

4d63a65f [nostalgia/core/studio] Show Palette page names in TileSheetEditor
686db99d [nostalgia/core/studio] Disable Palette page hotkeys when Rename popup is open
52533c8c [nostalgia/core/studio] Add Palette page names to editor
ba4540e4 [ox/std] Add IString::unsafeResize
36057bb0 [nostalgia/core/studio] Fix Clang build
1a2b2b8b [nostalgia/core] Add PaletteV4, with support for page names, make PaletteColor object
6189193a [nostalgia] Add NFDE install
67a10d35 [nostalgia/sample_project] Update type descriptor

git-subtree-dir: deps/nostalgia
git-subtree-split: 4d63a65fbde480235edd961e5cd19d8b4de1b66d
This commit is contained in:
Gary Talent 2024-09-07 00:16:03 -05:00
parent a1a34f27f9
commit ca2d9eb534
23 changed files with 459 additions and 120 deletions

View File

@ -61,6 +61,7 @@ if(NOT BUILDCORE_TARGET STREQUAL "gba")
add_subdirectory(deps/glutils)
add_subdirectory(deps/imgui)
add_subdirectory(deps/lodepng)
set(NFD_INSTALL ON)
add_subdirectory(deps/nfde)
endif()

View File

@ -81,6 +81,11 @@ class IString {
constexpr ox::Error resize(size_t sz) noexcept;
/**
* Resizes without clearing memory between current end and new end.
*/
constexpr ox::Error unsafeResize(size_t sz) noexcept;
/**
* Returns the capacity of bytes for this string.
*/
@ -220,6 +225,15 @@ constexpr ox::Error IString<StrCap>::resize(size_t sz) noexcept {
return {};
}
template<std::size_t StrCap>
constexpr ox::Error IString<StrCap>::unsafeResize(size_t sz) noexcept {
if (sz > StrCap) [[unlikely]] {
return OxError(1, "Trying to extend IString beyond its cap");
}
m_size = sz;
return {};
}
template<size_t sz>
struct MaybeView<ox::IString<sz>> {
using type = ox::StringView;

View File

@ -21,6 +21,7 @@ O1;net.drinkingtea.ox.TypeDescriptor;1;{
"typeId" : "B.uint8;0"
}
],
"preloadable" : true,
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.core.CompactTileSheet",
"typeVersion" : 1

View File

@ -0,0 +1,12 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "name",
"typeId" : "net.drinkingtea.ox.BasicString#8#;1"
}
],
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.core.Palette.ColorInfo",
"typeVersion" : 3
}

View File

@ -0,0 +1,23 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "name",
"typeId" : "net.drinkingtea.ox.BasicString#8#;1"
},
{
"fieldName" : "colors",
"subscriptLevels" : 1,
"subscriptStack" :
[
{
"subscriptType" : 4
}
],
"typeId" : "net.drinkingtea.nostalgia.core.PaletteColor;1"
}
],
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.core.Palette.PalettePage",
"typeVersion" : 1
}

View File

@ -1,6 +1,17 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "colorInfo",
"subscriptLevels" : 1,
"subscriptStack" :
[
{
"subscriptType" : 4
}
],
"typeId" : "net.drinkingtea.nostalgia.core.Palette.ColorInfo;3"
},
{
"fieldName" : "pages",
"subscriptLevels" : 2,
@ -13,7 +24,7 @@ O1;net.drinkingtea.ox.TypeDescriptor;1;{
"subscriptType" : 4
}
],
"typeId" : "net.drinkingtea.nostalgia.core.Palette.Page;3"
"typeId" : "B.uint16;0"
}
],
"preloadable" : true,

View File

@ -0,0 +1,31 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "colorNames",
"subscriptLevels" : 1,
"subscriptStack" :
[
{
"subscriptType" : 4
}
],
"typeId" : "net.drinkingtea.ox.BasicString#8#;1"
},
{
"fieldName" : "pages",
"subscriptLevels" : 1,
"subscriptStack" :
[
{
"subscriptType" : 4
}
],
"typeId" : "net.drinkingtea.nostalgia.core.Palette.PalettePage;1"
}
],
"preloadable" : true,
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.core.Palette",
"typeVersion" : 4
}

View File

@ -0,0 +1,24 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "r",
"typeId" : "B.uint8;0"
},
{
"fieldName" : "g",
"typeId" : "B.uint8;0"
},
{
"fieldName" : "b",
"typeId" : "B.uint8;0"
},
{
"fieldName" : "a",
"typeId" : "B.uint8;0"
}
],
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.core.PaletteColor",
"typeVersion" : 1
}

View File

@ -4,10 +4,6 @@
#pragma once
#include <ox/std/array.hpp>
#include <ox/std/point.hpp>
#include <ox/std/size.hpp>
#include <ox/std/types.hpp>
#include <ox/std/vector.hpp>
#include <ox/model/def.hpp>
@ -17,6 +13,54 @@
namespace nostalgia::core {
struct PaletteColorV1 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.PaletteColor";
static constexpr auto TypeVersion = 1;
uint8_t r{}, g{}, b{}, a{};
constexpr PaletteColorV1() noexcept = default;
constexpr PaletteColorV1(Color16 const c) noexcept:
r{red16(c)},
g{green16(c)},
b{blue16(c)},
a{alpha16(c)} {}
constexpr operator Color16() const noexcept { return color16(r, g, b, a); }
};
oxModelBegin(PaletteColorV1)
oxModelField(r)
oxModelField(g)
oxModelField(b)
oxModelField(a)
oxModelEnd()
using PaletteColor = PaletteColorV1;
struct PalettePageV1 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette.PalettePage";
static constexpr auto TypeVersion = 1;
ox::String name;
ox::Vector<PaletteColorV1> colors;
constexpr PalettePageV1() noexcept = default;
constexpr PalettePageV1(ox::StringParam pName, ox::Vector<PaletteColorV1> pColors) noexcept:
name(std::move(pName)), colors(std::move(pColors)) {}
constexpr PalettePageV1(ox::StringParam pName, ox::Vector<Color16> const&pColors) noexcept:
name(std::move(pName)) {
colors.reserve(pColors.size());
for (auto const c : pColors) {
colors.emplace_back(c);
}
}
};
oxModelBegin(PalettePageV1)
oxModelField(name)
oxModelField(colors)
oxModelEnd()
using PalettePage = PalettePageV1;
struct NostalgiaPalette {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette";
static constexpr auto TypeVersion = 1;
@ -36,6 +80,7 @@ struct PaletteV2 {
ox::Vector<ox::Vector<Color16>> pages;
};
struct PaletteV3 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
static constexpr auto TypeVersion = 3;
@ -45,16 +90,55 @@ struct PaletteV3 {
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)) {}
constexpr ColorInfo(ox::StringParam pName) noexcept: name{std::move(pName)} {}
};
ox::Vector<ColorInfo> colorInfo;
ox::Vector<ox::Vector<Color16>> pages;
};
using Palette = PaletteV3;
[[nodiscard]]
constexpr bool valid(PaletteV3 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;
});
}
constexpr ox::Error repair(PaletteV3 &p) noexcept {
auto const colors = p.colorInfo.size();
for (auto &page : p.pages) {
page.resize(colors);
}
return {};
}
struct PaletteV4 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
static constexpr auto TypeVersion = 4;
static constexpr auto Preloadable = true;
ox::Vector<ox::String> colorNames;
ox::Vector<PalettePageV1> pages;
};
[[nodiscard]]
constexpr bool valid(PaletteV4 const&p) noexcept {
auto const colors = p.colorNames.size();
return ox::all_of(p.pages.begin(), p.pages.end(), [colors](PalettePageV1 const&page) {
return page.colors.size() == colors;
});
}
constexpr ox::Error repair(PaletteV4 &p) noexcept {
auto const colors = p.colorNames.size();
for (auto &page : p.pages) {
page.colors.resize(colors);
}
return {};
}
using Palette = PaletteV4;
struct CompactPaletteV1 {
@ -64,29 +148,36 @@ struct CompactPaletteV1 {
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) {
constexpr bool valid(CompactPaletteV1 const&p) noexcept {
size_t colors{};
for (auto const&page : p.pages) {
colors = ox::max(colors, page.size());
}
return ox::all_of(p.pages.begin(), p.pages.end(), [colors](ox::Vector<Color16> const&page) {
return page.size() == colors;
});
}
[[nodiscard]]
constexpr ox::Error repair(Palette &p) noexcept {
auto const colors = p.colorInfo.size();
constexpr ox::Error repair(CompactPaletteV1 &p) noexcept {
size_t colors{};
for (auto const&page : p.pages) {
colors = ox::max(colors, page.size());
}
for (auto &page : p.pages) {
page.resize(colors);
}
return {};
}
using CompactPalette = CompactPaletteV1;
[[nodiscard]]
constexpr Color16 color(Palette 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];
if (page < pal.pages.size() && idx < pal.pages[page].colors.size()) [[likely]] {
return pal.pages[page].colors[idx];
}
return 0;
}
@ -111,7 +202,7 @@ constexpr Color16 color(CompactPalette const&pal, size_t idx) noexcept {
[[nodiscard]]
constexpr auto &colors(Palette &pal, size_t page = 0) noexcept {
return pal.pages[page];
return pal.pages[page].colors;
}
[[nodiscard]]
@ -132,7 +223,7 @@ constexpr auto &colors(CompactPalette const&pal, size_t page = 0) noexcept {
[[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 pal.pages[page].colors.size();
}
return 0;
}
@ -149,7 +240,7 @@ constexpr size_t colorCnt(CompactPalette const&pal, size_t page = 0) noexcept {
constexpr size_t largestPage(Palette const&pal) noexcept {
size_t out{};
for (auto const&page : pal.pages) {
out = ox::max(out, page.size());
out = ox::max(out, page.colors.size());
}
return out;
}
@ -184,6 +275,11 @@ oxModelBegin(PaletteV3)
oxModelField(pages)
oxModelEnd()
oxModelBegin(PaletteV4)
oxModelField(colorNames)
oxModelField(pages)
oxModelEnd()
oxModelBegin(CompactPaletteV1)
oxModelField(pages)
oxModelEnd()

View File

@ -19,6 +19,7 @@ static class: public keel::Module {
NostalgiaPaletteToPaletteV1Converter m_nostalgiaPaletteToPaletteV1Converter;
PaletteV1ToPaletteV2Converter m_paletteV1ToPaletteV2Converter;
PaletteV2ToPaletteV3Converter m_paletteV2ToPaletteV3Converter;
PaletteV3ToPaletteV4Converter m_paletteV3ToPaletteV4Converter;
PaletteToCompactPaletteConverter m_paletteToCompactPaletteConverter;
TileSheetV1ToTileSheetV2Converter m_tileSheetV1ToTileSheetV2Converter;
TileSheetV2ToTileSheetV3Converter m_tileSheetV2ToTileSheetV3Converter;
@ -41,6 +42,8 @@ static class: public keel::Module {
keel::generateTypeDesc<CompactTileSheetV1>,
keel::generateTypeDesc<PaletteV1>,
keel::generateTypeDesc<PaletteV2>,
keel::generateTypeDesc<PaletteV3>,
keel::generateTypeDesc<PaletteV4>,
keel::generateTypeDesc<CompactPaletteV1>,
};
}
@ -51,6 +54,7 @@ static class: public keel::Module {
&m_nostalgiaPaletteToPaletteV1Converter,
&m_paletteV1ToPaletteV2Converter,
&m_paletteV2ToPaletteV3Converter,
&m_paletteV3ToPaletteV4Converter,
&m_paletteToCompactPaletteConverter,
&m_tileSheetV1ToTileSheetV2Converter,
&m_tileSheetV2ToTileSheetV3Converter,
@ -78,7 +82,8 @@ static class: public keel::Module {
if (typeId == ox::ModelTypeId_v<NostalgiaPalette> ||
typeId == ox::ModelTypeId_v<PaletteV1> ||
typeId == ox::ModelTypeId_v<PaletteV2> ||
typeId == ox::ModelTypeId_v<PaletteV3>) {
typeId == ox::ModelTypeId_v<PaletteV3> ||
typeId == ox::ModelTypeId_v<PaletteV4>) {
oxReturnError(keel::convertBuffToBuff<CompactPalette>(
ctx, buff, ox::ClawFormat::Metal).moveTo(buff));
return true;

View File

@ -28,6 +28,7 @@ ox::Error PaletteV2ToPaletteV3Converter::convert(
PaletteV3 &dst) const noexcept {
dst.pages = std::move(src.pages);
if (!dst.pages.empty()) {
dst.colorInfo.reserve(dst.pages[0].size());
for (size_t i = 0; i < dst.pages[0].size(); ++i) {
dst.colorInfo.emplace_back(ox::sfmt("Color {}", i + 1));
}
@ -35,11 +36,33 @@ ox::Error PaletteV2ToPaletteV3Converter::convert(
return {};
}
ox::Error PaletteV3ToPaletteV4Converter::convert(
keel::Context&,
PaletteV3 &src,
PaletteV4 &dst) const noexcept {
dst.pages.reserve(src.pages.size());
for (auto i = 1; auto &page : src.pages) {
dst.pages.emplace_back(ox::sfmt("Page {}", i), std::move(page));
++i;
}
dst.colorNames.reserve(src.colorInfo.size());
for (auto &ci : src.colorInfo) {
dst.colorNames.emplace_back(std::move(ci.name));
}
return {};
}
ox::Error PaletteToCompactPaletteConverter::convert(
keel::Context&,
Palette &src,
CompactPalette &dst) const noexcept {
dst.pages = std::move(src.pages);
dst.pages.reserve(src.pages.size());
for (auto &page : src.pages) {
auto &p = dst.pages.emplace_back();
for (auto const c : page.colors) {
p.emplace_back(c);
}
}
return {};
}

View File

@ -28,6 +28,10 @@ class PaletteV2ToPaletteV3Converter: public keel::Converter<PaletteV2, PaletteV3
ox::Error convert(keel::Context&, PaletteV2 &src, PaletteV3 &dst) const noexcept final;
};
class PaletteV3ToPaletteV4Converter: public keel::Converter<PaletteV3, PaletteV4> {
ox::Error convert(keel::Context&, PaletteV3 &src, PaletteV4 &dst) const noexcept final;
};
class PaletteToCompactPaletteConverter: public keel::Converter<Palette, CompactPalette> {
ox::Error convert(keel::Context&, Palette &src, CompactPalette &dst) const noexcept final;
};

View File

@ -4,8 +4,6 @@
#include <imgui.h>
#include <ox/std/memory.hpp>
#include <keel/media.hpp>
#include <nostalgia/core/gfx.hpp>
@ -17,6 +15,27 @@ namespace nostalgia::core {
namespace ig = studio::ig;
void PaletteEditorImGui::PageRename::draw(turbine::Context &tctx) noexcept {
if (!m_show) {
return;
}
if (ig::BeginPopup(tctx, "Rename Page", m_show)) {
ig::InputText("Name", m_name);
switch (ig::PopupControlsOkCancel(m_show)) {
case ig::PopupResponse::OK:
inputSubmitted.emit(m_name);
[[fallthrough]];
case ig::PopupResponse::Cancel:
close();
default:
break;
}
ImGui::EndPopup();
}
}
PaletteEditorImGui::PaletteEditorImGui(studio::StudioContext &sctx, ox::StringParam path):
Editor(std::move(path)),
m_sctx(sctx),
@ -26,10 +45,11 @@ PaletteEditorImGui::PaletteEditorImGui(studio::StudioContext &sctx, ox::StringPa
throw OxException(1, "PaletteEditorImGui: invalid Palette object");
}
undoStack()->changeTriggered.connect(this, &PaletteEditorImGui::handleCommand);
m_pageRename.inputSubmitted.connect(this, &PaletteEditorImGui::renamePage);
}
void PaletteEditorImGui::keyStateChanged(turbine::Key key, bool down) {
if (!down) {
if (!down || m_pageRename.isOpen()) {
return;
}
if (key >= turbine::Key::Num_1 && key <= turbine::Key::Num_9) {
@ -49,16 +69,17 @@ void PaletteEditorImGui::keyStateChanged(turbine::Key key, bool down) {
void PaletteEditorImGui::draw(studio::StudioContext&) noexcept {
auto const paneSize = ImGui::GetContentRegionAvail();
{
ImGui::BeginChild("Pages", ImVec2(250, paneSize.y), true);
ImGui::BeginChild("Pages", {280, paneSize.y}, true);
drawPagesEditor();
ImGui::EndChild();
}
ImGui::SameLine();
{
ImGui::BeginChild("Colors", ImVec2(-1, paneSize.y), true);
ImGui::BeginChild("Colors", {-1, paneSize.y}, true);
drawColorsEditor();
ImGui::EndChild();
}
m_pageRename.draw(m_tctx);
}
ox::Error PaletteEditorImGui::saveItem() noexcept {
@ -84,7 +105,7 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
auto const colorEditorWidth = 220;
static constexpr auto toolbarHeight = 40;
{
auto const sz = ImVec2(70, 24);
auto constexpr sz = ImVec2{70, 24};
if (ImGui::Button("Add", sz)) {
auto const colorSz = colorCnt(m_pal, m_page);
constexpr Color16 c = 0;
@ -126,7 +147,7 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
"Colors",
6,
tableFlags,
ImVec2(tblWidth, colorsSz.y - (toolbarHeight + 5)));
{tblWidth, colorsSz.y - (toolbarHeight + 5)});
{
ImGui::TableSetupColumn("Idx", ImGuiTableColumnFlags_WidthFixed, 25);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 100);
@ -135,30 +156,28 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
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]) {
ImGui::PushID(static_cast<int>(i));
for (auto i = 0u; auto const&c : m_pal.pages[m_page].colors) {
ig::IDStackItem const idStackItem(static_cast<int>(i));
ImGui::TableNextRow();
drawColumn(i + 1);
drawColumnLeftAlign(m_pal.colorInfo[i].name);
drawColumnLeftAlign(m_pal.colorNames[i]);
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({redf(c), greenf(c), bluef(c), 1});
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
if (ImGui::Selectable(
"##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) {
m_selectedColorRow = i;
}
ImGui::PopID();
++i;
}
}
ImGui::EndTable();
if (colorEditor) {
ImGui::SameLine();
ImGui::BeginChild("ColorEditor", ImVec2(colorEditorWidth, -1), true);
ImGui::BeginChild("ColorEditor", {colorEditorWidth, -1}, true);
drawColorEditor();
ImGui::EndChild();
}
@ -168,7 +187,7 @@ void PaletteEditorImGui::drawPagesEditor() noexcept {
constexpr auto tableFlags = ImGuiTableFlags_RowBg;
auto const paneSz = ImGui::GetContentRegionAvail();
constexpr auto toolbarHeight = 40;
auto const btnSz = ImVec2(paneSz.x / 3 - 5.5f, 24);
auto const btnSz = ImVec2{paneSz.x / 4 - 5.5f, 24};
if (ImGui::Button("Add", btnSz)) {
std::ignore = pushCommand<DuplicatePageCommand>(m_pal, 0u, m_pal.pages.size());
m_page = m_pal.pages.size() - 1;
@ -179,26 +198,31 @@ void PaletteEditorImGui::drawPagesEditor() noexcept {
m_page = std::min(m_page, m_pal.pages.size() - 1);
}
ImGui::SameLine();
if (ImGui::Button("Duplicate", btnSz)) {
if (ImGui::Button("Clone", btnSz)) {
std::ignore = pushCommand<DuplicatePageCommand>(m_pal, m_page, m_pal.pages.size());
}
ImGui::SameLine();
if (ImGui::Button("Rename", btnSz)) {
m_pageRename.show(m_pal.pages[m_page].name);
}
ImGui::BeginTable(
"PageSelect",
2,
tableFlags,
ImVec2(paneSz.x, paneSz.y - (toolbarHeight + 5)));
{paneSz.x, paneSz.y - (toolbarHeight + 5)});
{
ImGui::TableSetupColumn("Page", ImGuiTableColumnFlags_WidthFixed, 60);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 200);
ImGui::TableHeadersRow();
for (auto i = 0u; i < m_pal.pages.size(); ++i) {
ImGui::PushID(static_cast<int>(i));
ig::IDStackItem const idStackItem(static_cast<int>(i));
ImGui::TableNextRow();
drawColumn(i + 1);
drawColumnLeftAlign(m_pal.pages[i].name);
ImGui::SameLine();
if (ImGui::Selectable("##PageRow", i == m_page, ImGuiSelectableFlags_SpanAllColumns)) {
m_page = i;
}
ImGui::PopID();
}
}
ImGui::EndTable();
@ -210,7 +234,7 @@ void PaletteEditorImGui::drawColorEditor() noexcept {
int g = green16(c);
int b = blue16(c);
int const a = alpha16(c);
auto const&currentName = m_pal.colorInfo[m_selectedColorRow].name;
auto const&currentName = m_pal.colorNames[m_selectedColorRow];
ox::IString<50> name;
name = currentName;
ImGui::InputText("Name", name.data(), name.cap() + 1);
@ -229,12 +253,16 @@ void PaletteEditorImGui::drawColorEditor() noexcept {
if (c != newColor) {
std::ignore = pushCommand<UpdateColorCommand>(m_pal, m_page, m_selectedColorRow, newColor);
}
if (currentName != name.data()) {
if (currentName != name) {
std::ignore = pushCommand<UpdateColorInfoCommand>(
m_pal, m_selectedColorRow, Palette::ColorInfo{name.data()});
m_pal, m_selectedColorRow, ox::String{name});
}
}
ox::Error PaletteEditorImGui::renamePage(ox::StringView name) noexcept {
return pushCommand<RenamePageCommand>(m_pal, m_page, name);
}
ox::Error PaletteEditorImGui::handleCommand(studio::UndoCommand const*cmd) noexcept {
if (dynamic_cast<RemovePageCommand const*>(cmd)) {
m_page = ox::min(m_page, m_pal.pages.size() - 1);

View File

@ -14,6 +14,23 @@ namespace nostalgia::core {
class PaletteEditorImGui: public studio::Editor {
private:
class PageRename {
private:
ox::IString<50> m_name;
bool m_show = false;
public:
ox::Signal<ox::Error(ox::StringView name)> inputSubmitted;
constexpr void show(ox::StringView const&name) noexcept {
m_show = true;
m_name = name;
}
constexpr void close() noexcept {
m_show = false;
}
[[nodiscard]]
constexpr bool isOpen() const noexcept { return m_show; }
void draw(turbine::Context &tctx) noexcept;
} m_pageRename;
studio::StudioContext &m_sctx;
turbine::Context &m_tctx;
Palette m_pal;
@ -45,6 +62,8 @@ class PaletteEditorImGui: public studio::Editor {
void drawColorEditor() noexcept;
ox::Error renamePage(ox::StringView name) noexcept;
ox::Error handleCommand(studio::UndoCommand const*) noexcept;
};

View File

@ -14,13 +14,13 @@ ApplyColorAllPagesCommand::ApplyColorAllPagesCommand(Palette &pal, size_t const
ox::Vector<Color16> colors;
colors.reserve(m_pal.pages.size());
for (auto const&p : m_pal.pages) {
colors.emplace_back(p[m_idx]);
colors.emplace_back(p.colors[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;
if (ox::all_of(m_pal.pages.begin(), m_pal.pages.end(), [this, c](PalettePage const&page) {
return page.colors[m_idx] == c;
})) {
throw studio::NoChangesException();
}
@ -33,26 +33,46 @@ int ApplyColorAllPagesCommand::commandId() const noexcept {
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;
page.colors[m_idx] = c;
}
return {};
}
ox::Error ApplyColorAllPagesCommand::undo() noexcept {
for (size_t p = 0u; auto &page : m_pal.pages) {
page[m_idx] = m_origColors[p];
page.colors[m_idx] = m_origColors[p];
++p;
}
return {};
}
RenamePageCommand::RenamePageCommand(Palette &pal, size_t const page, ox::StringParam name) noexcept:
m_pal(pal),
m_page(page),
m_name{std::move(name)} {}
int RenamePageCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::RenamePage);
}
ox::Error RenamePageCommand::redo() noexcept {
std::swap(m_pal.pages[m_page].name, m_name);
return {};
}
ox::Error RenamePageCommand::undo() noexcept {
std::swap(m_pal.pages[m_page].name, m_name);
return {};
}
DuplicatePageCommand::DuplicatePageCommand(Palette &pal, size_t srcIdx, size_t dstIdx) noexcept:
m_pal(pal),
m_dstIdx(dstIdx) {
auto const&src = m_pal.pages[srcIdx];
m_page.reserve(src.size());
for (auto const&s : src) {
m_page.reserve(src.colors.size());
for (auto const&s : src.colors) {
m_page.emplace_back(s);
}
}
@ -62,12 +82,12 @@ 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, "", std::move(m_page));
return {};
}
ox::Error DuplicatePageCommand::undo() noexcept {
m_page = std::move(m_pal.pages[m_dstIdx]);
m_page = std::move(m_pal.pages[m_dstIdx].colors);
return m_pal.pages.erase(m_dstIdx).error;
}
@ -85,7 +105,7 @@ int RemovePageCommand::commandId() const noexcept {
}
ox::Error RemovePageCommand::redo() noexcept {
m_page = std::move(colors(m_pal, m_idx));
m_page = std::move(m_pal.pages[m_idx]);
return m_pal.pages.erase(m_idx).error;
}
@ -105,17 +125,17 @@ int AddColorCommand::commandId() const noexcept {
}
ox::Error AddColorCommand::redo() noexcept {
m_pal.colorInfo.emplace(m_idx, ox::sfmt("Color {}", m_pal.colorInfo.size() + 1));
m_pal.colorNames.emplace(m_idx, ox::sfmt("Color {}", m_pal.colorNames.size() + 1));
for (auto &page : m_pal.pages) {
page.emplace(m_idx, m_color);
page.colors.emplace(m_idx, m_color);
}
return {};
}
ox::Error AddColorCommand::undo() noexcept {
oxReturnError(m_pal.colorInfo.erase(m_idx));
oxReturnError(m_pal.colorNames.erase(m_idx));
for (auto &page : m_pal.pages) {
oxReturnError(page.erase(m_idx));
oxReturnError(page.colors.erase(m_idx));
}
return {};
}
@ -128,7 +148,7 @@ RemoveColorCommand::RemoveColorCommand(Palette &pal, size_t const idx) noexcept:
ox::Vector<Color16> colors;
colors.reserve(m_pal.pages.size());
for (auto const&p : m_pal.pages) {
colors.emplace_back(p[m_idx]);
colors.emplace_back(p.colors[m_idx]);
}
return colors;
}()) {}
@ -138,18 +158,18 @@ int RemoveColorCommand::commandId() const noexcept {
}
ox::Error RemoveColorCommand::redo() noexcept {
m_colorInfo = std::move(m_pal.colorInfo[m_idx]);
oxReturnError(m_pal.colorInfo.erase(m_idx));
m_colorInfo = std::move(m_pal.colorNames[m_idx]);
oxReturnError(m_pal.colorNames.erase(m_idx));
for (auto &page : m_pal.pages) {
oxReturnError(page.erase(m_idx));
oxReturnError(page.colors.erase(m_idx));
}
return {};
}
ox::Error RemoveColorCommand::undo() noexcept {
m_pal.colorInfo.emplace(m_idx, std::move(m_colorInfo));
m_pal.colorNames.emplace(m_idx, std::move(m_colorInfo));
for (size_t p = 0; auto &page : m_pal.pages) {
page.emplace(m_idx, m_colors[p]);
page.colors.emplace(m_idx, m_colors[p]);
++p;
}
return {};
@ -159,11 +179,11 @@ ox::Error RemoveColorCommand::undo() noexcept {
UpdateColorInfoCommand::UpdateColorInfoCommand(
Palette &pal,
size_t idx,
Palette::ColorInfo newColorInfo):
ox::StringParam newColorInfo):
m_pal(pal),
m_idx(idx),
m_altColorInfo(std::move(newColorInfo)) {
if (m_pal.colorInfo[m_idx].name == m_altColorInfo.name) {
if (m_pal.colorNames[m_idx] == m_altColorInfo) {
throw studio::NoChangesException();
}
}
@ -179,7 +199,6 @@ bool UpdateColorInfoCommand::mergeWith(UndoCommand const&cmd) noexcept {
return true;
}
[[nodiscard]]
int UpdateColorInfoCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::UpdateColor);
}
@ -195,7 +214,7 @@ ox::Error UpdateColorInfoCommand::undo() noexcept {
}
void UpdateColorInfoCommand::swap() noexcept {
std::swap(m_pal.colorInfo[m_idx], m_altColorInfo);
std::swap(m_pal.colorNames[m_idx], m_altColorInfo);
}
@ -224,7 +243,6 @@ bool UpdateColorCommand::mergeWith(UndoCommand const&cmd) noexcept {
return true;
}
[[nodiscard]]
int UpdateColorCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::UpdateColor);
}

View File

@ -7,13 +7,13 @@
#include <studio/studio.hpp>
#include <nostalgia/core/color.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/core/palette.hpp>
namespace nostalgia::core {
enum class PaletteEditorCommandId {
ApplyColorAllPages,
RenamePage,
DuplicatePage,
RemovePage,
AddColor,
@ -44,11 +44,31 @@ class ApplyColorAllPagesCommand: public studio::UndoCommand {
ox::Error undo() noexcept final;
};
class RenamePageCommand: public studio::UndoCommand {
private:
Palette &m_pal;
size_t m_page = 0;
ox::String m_name;
public:
RenamePageCommand(Palette &pal, size_t page, ox::StringParam name) noexcept;
~RenamePageCommand() noexcept override = default;
[[nodiscard]]
int commandId() const noexcept final;
ox::Error redo() noexcept final;
ox::Error undo() noexcept final;
};
class DuplicatePageCommand: public studio::UndoCommand {
private:
Palette &m_pal;
size_t m_dstIdx = 0;
ox::Vector<Color16> m_page;
ox::Vector<PaletteColor> m_page;
public:
DuplicatePageCommand(Palette &pal, size_t srcIdx, size_t dstIdx) noexcept;
@ -71,7 +91,7 @@ class RemovePageCommand: public studio::UndoCommand {
private:
Palette &m_pal;
size_t m_idx = 0;
ox::Vector<Color16> m_page;
PalettePage m_page;
public:
RemovePageCommand(Palette &pal, size_t idx) noexcept;
@ -111,7 +131,7 @@ class RemoveColorCommand: public studio::UndoCommand {
private:
Palette &m_pal;
size_t const m_idx = 0;
Palette::ColorInfo m_colorInfo;
ox::String m_colorInfo;
ox::Vector<Color16> const m_colors;
public:
@ -132,13 +152,13 @@ class UpdateColorInfoCommand: public studio::UndoCommand {
private:
Palette &m_pal;
size_t const m_idx{};
Palette::ColorInfo m_altColorInfo;
ox::String m_altColorInfo;
public:
UpdateColorInfoCommand(
Palette &pal,
size_t idx,
Palette::ColorInfo newColorInfo);
ox::StringParam newColorInfo);
~UpdateColorInfoCommand() noexcept override = default;
@ -162,7 +182,7 @@ class UpdateColorCommand: public studio::UndoCommand {
Palette &m_pal;
size_t const m_page = 0;
size_t const m_idx{};
Color16 m_altColor{};
PaletteColor m_altColor{};
public:
UpdateColorCommand(
@ -203,7 +223,6 @@ class MoveColorCommand: public studio::UndoCommand {
[[nodiscard]]
int commandId() const noexcept override;
public:
ox::Error redo() noexcept override;
ox::Error undo() noexcept override;

View File

@ -343,7 +343,7 @@ void TileSheetEditorImGui::showSubsheetEditor() noexcept {
}
}
ox::Error TileSheetEditorImGui::exportSubhseetToPng(int scale) noexcept {
ox::Error TileSheetEditorImGui::exportSubhseetToPng(int const scale) const noexcept {
oxRequire(path, studio::saveFile({{"PNG", "png"}}));
// subsheet to png
auto const&img = m_model.img();
@ -441,12 +441,14 @@ void TileSheetEditorImGui::drawPaletteSelector() noexcept {
auto const pages = m_model.pal().pages.size();
if (pages > 1) {
ImGui::Indent(20);
auto numStr = ox::itoa(m_model.palettePage() + 1);
using Str = ox::IString<55>;
auto numStr = ox::sfmt<Str>(
"{} - {}", m_model.palettePage() + 1, m_model.pal().pages[m_model.palettePage()].name);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - comboWidthSub);
if (ImGui::BeginCombo("Page", numStr.c_str(), 0)) {
for (auto n = 0u; n < pages; ++n) {
auto const selected = (m_model.palettePage() == n);
numStr = ox::itoa(n + 1);
numStr = ox::sfmt<Str>("{} - {}", n + 1, m_model.pal().pages[n].name);
if (ImGui::Selectable(numStr.c_str(), selected) && m_model.palettePage() != n) {
m_model.setPalettePage(n);
}
@ -469,7 +471,7 @@ void TileSheetEditorImGui::drawPaletteSelector() noexcept {
{
auto const&pal = m_model.pal();
if (pal.pages.size() > m_model.palettePage()) {
for (auto i = 0u; auto const&c: pal.pages[m_model.palettePage()]) {
for (auto i = 0u; auto const&c: pal.pages[m_model.palettePage()].colors) {
ImGui::PushID(static_cast<int>(i));
// Column: color idx
ImGui::TableNextColumn();
@ -484,7 +486,7 @@ void TileSheetEditorImGui::drawPaletteSelector() noexcept {
auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
ImGui::TableNextColumn();
auto const&name = i < pal.colorInfo.size() ? pal.colorInfo[i].name.c_str() : "";
auto const&name = i < pal.colorNames.size() ? pal.colorNames[i].c_str() : "";
ImGui::Text("%s", name);
ImGui::TableNextColumn();
ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));

View File

@ -97,7 +97,7 @@ class TileSheetEditorImGui: public studio::Editor {
private:
void showSubsheetEditor() noexcept;
ox::Error exportSubhseetToPng(int scale) noexcept;
ox::Error exportSubhseetToPng(int scale) const noexcept;
void drawTileSheet(ox::Vec2 const&fbSize) noexcept;

View File

@ -27,8 +27,8 @@
namespace nostalgia::core {
Palette const TileSheetEditorModel::s_defaultPalette = {
.colorInfo = {ox::Vector<Palette::ColorInfo>{{}}},
.pages = {{ox::Vector<Color16>(128)}},
.colorNames = {ox::Vector<ox::String>{{}}},
.pages = {{"Page 1", ox::Vector<Color16>(128)}},
};
// delete pixels of all non-leaf nodes

View File

@ -29,7 +29,7 @@ bool AboutPopup::isOpen() const noexcept {
return m_stage == Stage::Open;
}
void AboutPopup::draw(studio::StudioContext &sctx) noexcept {
void AboutPopup::draw(StudioContext &sctx) noexcept {
switch (m_stage) {
case Stage::Closed:
break;
@ -40,15 +40,14 @@ void AboutPopup::draw(studio::StudioContext &sctx) noexcept {
case Stage::Open: {
constexpr auto modalFlags =
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
ImGui::SetNextWindowSize(ImVec2(215, 90));
studio::ig::centerNextWindow(sctx.tctx);
ig::centerNextWindow(sctx.tctx);
auto open = true;
if (ImGui::BeginPopupModal("About", &open, modalFlags)) {
ImGui::Text("%s", m_text.c_str());
ImGui::NewLine();
ImGui::Dummy(ImVec2(148.0f, 0.0f));
ImGui::Dummy({148.0f, 0.0f});
ImGui::SameLine();
if (ImGui::Button("Close")) {
if (ig::PushButton("Close")) {
ImGui::CloseCurrentPopup();
open = false;
}

View File

@ -18,7 +18,7 @@ struct StudioContext {
StudioUI &ui;
Project *project = nullptr;
turbine::Context &tctx;
inline StudioContext(StudioUI &pUi, turbine::Context &pTctx) noexcept:
StudioContext(StudioUI &pUi, turbine::Context &pTctx) noexcept:
ui(pUi), tctx(pTctx) {}
};

View File

@ -45,10 +45,10 @@ class DragDropSource {
private:
bool const m_active{};
public:
inline DragDropSource() noexcept:
DragDropSource() noexcept:
m_active(ImGui::BeginDragDropSource()) {
}
inline ~DragDropSource() noexcept {
~DragDropSource() noexcept {
if (m_active) {
ImGui::EndDragDropSource();
}
@ -58,7 +58,7 @@ class DragDropSource {
}
};
inline auto dragDropSource(auto const&cb) noexcept {
auto dragDropSource(auto const&cb) noexcept {
if constexpr(ox::is_same_v<decltype(cb()), ox::Error>) {
if (ig::DragDropSource const tgt; tgt) [[unlikely]] {
return cb();
@ -75,10 +75,10 @@ class DragDropTarget {
private:
bool const m_active{};
public:
inline DragDropTarget() noexcept:
DragDropTarget() noexcept:
m_active(ImGui::BeginDragDropTarget()) {
}
inline ~DragDropTarget() noexcept {
~DragDropTarget() noexcept {
if (m_active) {
ImGui::EndDragDropTarget();
}
@ -128,6 +128,21 @@ void centerNextWindow(turbine::Context &ctx) noexcept;
bool PushButton(ox::CStringView lbl, ImVec2 const&btnSz = BtnSz) noexcept;
template<size_t StrCap>
bool InputText(
ox::CStringView label,
ox::IString<StrCap> &text,
ImGuiInputTextFlags const flags = 0,
ImGuiInputTextCallback const callback = nullptr,
void *user_data = nullptr) noexcept {
auto const out = ImGui::InputText(
label.c_str(), text.data(), StrCap + 1, flags, callback, user_data);
if (out) {
std::ignore = text.unsafeResize(ox::strlen(text.c_str()));
}
return out;
}
enum class PopupResponse {
None,
OK,
@ -136,6 +151,8 @@ enum class PopupResponse {
PopupResponse PopupControlsOkCancel(float popupWidth, bool &popupOpen);
PopupResponse PopupControlsOkCancel(bool &popupOpen);
[[nodiscard]]
bool BeginPopup(turbine::Context &ctx, ox::CStringView popupName, bool &show, ImVec2 const&sz = {285, 0});

View File

@ -73,9 +73,13 @@ PopupResponse PopupControlsOkCancel(float popupWidth, bool &popupOpen) {
return out;
}
PopupResponse PopupControlsOkCancel(bool &popupOpen) {
return PopupControlsOkCancel(ImGui::GetContentRegionAvail().x + 17, popupOpen);
}
bool BeginPopup(turbine::Context &ctx, ox::CStringView popupName, bool &show, ImVec2 const&sz) {
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
ig::centerNextWindow(ctx);
centerNextWindow(ctx);
ImGui::OpenPopup(popupName.c_str());
ImGui::SetNextWindowSize(sz);
return ImGui::BeginPopupModal(popupName.c_str(), &show, modalFlags);
@ -122,23 +126,11 @@ bool ComboBox(
bool FileComboBox(
ox::CStringView lbl,
studio::StudioContext &sctx,
StudioContext &sctx,
ox::StringView fileExt,
size_t &selectedIdx) noexcept {
auto const&list = sctx.project->fileList(fileExt);
bool out{};
auto const first = selectedIdx < list.size() ? list[selectedIdx].c_str() : "";
if (ImGui::BeginCombo(lbl.c_str(), first, 0)) {
for (auto i = 0u; i < list.size(); ++i) {
const auto selected = (selectedIdx == i);
if (ImGui::Selectable(list[i].c_str(), selected) && selectedIdx != i) {
selectedIdx = i;
out = true;
}
}
ImGui::EndCombo();
}
return out;
return ComboBox(lbl, list, selectedIdx);
}
bool ListBox(
@ -164,14 +156,14 @@ bool ListBox(
}
bool ListBox(ox::CStringView name, ox::SpanView<ox::String> const&list, size_t &selIdx) noexcept {
return ig::ListBox(name, [list](size_t i) -> ox::CStringView {
return ListBox(name, [list](size_t i) -> ox::CStringView {
return list[i];
}, list.size(), selIdx);
}
FilePicker::FilePicker(
studio::StudioContext &sctx,
StudioContext &sctx,
ox::String title,
ox::String fileExt,
ImVec2 const&size) noexcept:
@ -186,12 +178,12 @@ void FilePicker::draw() noexcept {
return;
}
auto constexpr popupSz = ImVec2{450.f, 0};
ig::IDStackItem const idStackItem(m_title.c_str());
if (ig::BeginPopup(m_sctx.tctx, m_title.c_str(), m_show, popupSz)) {
IDStackItem const idStackItem(m_title);
if (BeginPopup(m_sctx.tctx, m_title, m_show, popupSz)) {
auto const&list = m_sctx.project->fileList(m_fileExt);
size_t selIdx{};
ig::ComboBox(m_title.c_str(), list, selIdx);
if (ig::PopupControlsOkCancel(popupSz.x, m_show) == ig::PopupResponse::OK) {
ComboBox(m_title, list, selIdx);
if (PopupControlsOkCancel(popupSz.x, m_show) == ig::PopupResponse::OK) {
filePicked.emit(list[selIdx]);
}
ImGui::EndPopup();