Compare commits

..

No commits in common. "e6803af22f0def52310b1723f1906503042bed40" and "819e93bb1c479251db63291aa5eb7a9079491bf6" have entirely different histories.

13 changed files with 135 additions and 160 deletions

View File

@ -111,7 +111,7 @@ extern template struct GLObject<deleteProgram>;
extern template struct GLObject<deleteShader>; extern template struct GLObject<deleteShader>;
using GLBuffer = GLObject<deleteBuffer>; using GLBuffer = GLObject<deleteBuffer>;
using GLFrameBuffer = GLObject<deleteFrameBuffer>; using GLFrameBuffer = GLObject<deleteBuffer>;
using GLRenderBuffer = GLObject<deleteRenderBuffer>; using GLRenderBuffer = GLObject<deleteRenderBuffer>;
using GLShader = GLObject<deleteShader>; using GLShader = GLObject<deleteShader>;
using GLProgram = GLObject<deleteProgram>; using GLProgram = GLObject<deleteProgram>;

View File

@ -45,14 +45,9 @@ FileAddress &FileAddress::operator=(const FileAddress &other) noexcept {
switch (m_type) { switch (m_type) {
case FileAddressType::Path: case FileAddressType::Path:
{ {
if (other.m_data.path) { auto strSize = ox_strlen(other.m_data.path) + 1;
auto strSize = ox_strlen(other.m_data.path) + 1; m_data.path = new char[strSize];
m_data.path = new char[strSize]; ox_memcpy(m_data.path, other.m_data.path, strSize);
ox_memcpy(m_data.path, other.m_data.path, strSize);
} else {
m_data.constPath = "";
m_type = FileAddressType::ConstPath;
}
break; break;
} }
case FileAddressType::ConstPath: case FileAddressType::ConstPath:

View File

@ -47,11 +47,7 @@ class ModelHandlerInterface {
template<std::size_t len> template<std::size_t len>
constexpr Error fieldCString(const char *name, const char val[len]) noexcept requires(opType_v != OpType::Read) { constexpr Error fieldCString(const char *name, const char val[len]) noexcept requires(opType_v != OpType::Read) {
if constexpr(opType_v != OpType::Read) { return m_handler->fieldCString(name, &val[0], len);
return m_handler->fieldCString(name, &val[0], len);
} else {
return {};
}
} }
constexpr Error fieldCString(const char *name, char **val) noexcept { constexpr Error fieldCString(const char *name, char **val) noexcept {
@ -59,21 +55,11 @@ class ModelHandlerInterface {
} }
constexpr Error fieldCString(const char *name, const char *const*val) noexcept requires(opType_v != OpType::Read) { constexpr Error fieldCString(const char *name, const char *const*val) noexcept requires(opType_v != OpType::Read) {
// this check looks pointless, but it's to address a Clang bug return m_handler->fieldCString(name, val);
if constexpr(opType_v != OpType::Read) {
return m_handler->fieldCString(name, val);
} else {
return {};
}
} }
constexpr Error fieldCString(const char *name, const char **val) noexcept requires(opType_v != OpType::Read) { constexpr Error fieldCString(const char *name, const char **val) noexcept requires(opType_v != OpType::Read) {
// this check looks pointless, but it's to address a Clang bug return m_handler->fieldCString(name, val);
if constexpr(opType_v != OpType::Read) {
return m_handler->fieldCString(name, val);
} else {
return {};
}
} }
constexpr Error fieldCString(const char *name, char **val, std::size_t buffLen) noexcept { constexpr Error fieldCString(const char *name, char **val, std::size_t buffLen) noexcept {
@ -81,12 +67,7 @@ class ModelHandlerInterface {
} }
constexpr Error fieldCString(const char *name, const char **val, std::size_t buffLen) noexcept requires(opType_v != OpType::Read) { constexpr Error fieldCString(const char *name, const char **val, std::size_t buffLen) noexcept requires(opType_v != OpType::Read) {
// this check looks pointless, but it's to address a Clang bug return m_handler->fieldCString(name, val, buffLen);
if constexpr(opType_v != OpType::Read) {
return m_handler->fieldCString(name, val, buffLen);
} else {
return {};
}
} }
constexpr Error fieldCString(const char *name, char *val, std::size_t buffLen) noexcept { constexpr Error fieldCString(const char *name, char *val, std::size_t buffLen) noexcept {

View File

@ -27,7 +27,7 @@ Error OrganicClawWriter::fieldCString(const char *key, const char *const*val, in
} }
Error OrganicClawWriter::fieldCString(const char *key, const char *const*val) noexcept { Error OrganicClawWriter::fieldCString(const char *key, const char *const*val) noexcept {
return fieldCString(key, const_cast<const char**>(val), static_cast<int>(ox_strlen(val))); return fieldCString(key, const_cast<const char**>(val), {});
} }
Error OrganicClawWriter::field(const char *key, const UUID *uuid) noexcept { Error OrganicClawWriter::field(const char *key, const UUID *uuid) noexcept {

View File

@ -42,15 +42,14 @@ ox::Error toPngFile(
} }
TileSheetEditorImGui::TileSheetEditorImGui(turbine::Context &ctx, ox::CRStringView path): TileSheetEditorImGui::TileSheetEditorImGui(turbine::Context &ctx, ox::CRStringView path):
Editor(path), Editor(path),
m_ctx(ctx), m_ctx(ctx),
m_view(m_ctx, path, *undoStack()), m_tileSheetEditor(m_ctx, path, *undoStack()) {
m_model(m_view.model()) {
oxIgnoreError(setPaletteSelection()); oxIgnoreError(setPaletteSelection());
// connect signal/slots // connect signal/slots
undoStack()->changeTriggered.connect(this, &TileSheetEditorImGui::markUnsavedChanges); undoStack()->changeTriggered.connect(this, &TileSheetEditorImGui::markUnsavedChanges);
m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet); m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
m_model.paletteChanged.connect(this, &TileSheetEditorImGui::setPaletteSelection); model()->paletteChanged.connect(this, &TileSheetEditorImGui::setPaletteSelection);
} }
void TileSheetEditorImGui::exportFile() { void TileSheetEditorImGui::exportFile() {
@ -58,15 +57,15 @@ void TileSheetEditorImGui::exportFile() {
} }
void TileSheetEditorImGui::cut() { void TileSheetEditorImGui::cut() {
m_model.cut(); model()->cut();
} }
void TileSheetEditorImGui::copy() { void TileSheetEditorImGui::copy() {
m_model.copy(); model()->copy();
} }
void TileSheetEditorImGui::paste() { void TileSheetEditorImGui::paste() {
m_model.paste(); model()->paste();
} }
void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) { void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
@ -76,32 +75,23 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
if (key == turbine::Key::Escape) { if (key == turbine::Key::Escape) {
m_subsheetEditor.close(); m_subsheetEditor.close();
} }
auto pal = m_model.pal(); auto pal = model()->pal();
if (pal) { if (pal) {
const auto colorCnt = pal->colors.size(); const auto colorCnt = pal->colors.size();
if (key == turbine::Key::Alpha_D) { if (key == turbine::Key::Alpha_D) {
m_tool = Tool::Draw; m_tool = Tool::Draw;
setCopyEnabled(false); model()->clearSelection();
setCutEnabled(false);
setPasteEnabled(false);
m_model.clearSelection();
} else if (key == turbine::Key::Alpha_S) { } else if (key == turbine::Key::Alpha_S) {
m_tool = Tool::Select; m_tool = Tool::Select;
setCopyEnabled(true);
setCutEnabled(true);
setPasteEnabled(true);
} else if (key == turbine::Key::Alpha_F) { } else if (key == turbine::Key::Alpha_F) {
m_tool = Tool::Fill; m_tool = Tool::Fill;
setCopyEnabled(false); model()->clearSelection();
setCutEnabled(false);
setPasteEnabled(false);
m_model.clearSelection();
} else if (key >= turbine::Key::Num_1 && key <= turbine::Key::Num_9 && key <= turbine::Key::Num_0 + colorCnt) { } else if (key >= turbine::Key::Num_1 && key <= turbine::Key::Num_9 && key <= turbine::Key::Num_0 + colorCnt) {
auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1), colorCnt - 1); auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1), colorCnt - 1);
m_view.setPalIdx(idx); m_tileSheetEditor.setPalIdx(idx);
} else if (key == turbine::Key::Num_0 && colorCnt >= 10) { } else if (key == turbine::Key::Num_0 && colorCnt >= 10) {
auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), colorCnt - 1); auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), colorCnt - 1);
m_view.setPalIdx(idx); m_tileSheetEditor.setPalIdx(idx);
} }
} }
} }
@ -128,12 +118,12 @@ void TileSheetEditorImGui::draw(turbine::Context&) noexcept {
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) { if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) {
m_tool = Tool::Draw; m_tool = Tool::Draw;
m_model.clearSelection(); model()->clearSelection();
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) { if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) {
m_tool = Tool::Fill; m_tool = Tool::Fill;
m_model.clearSelection(); model()->clearSelection();
} }
} }
ImGui::EndChild(); ImGui::EndChild();
@ -149,17 +139,17 @@ void TileSheetEditorImGui::draw(turbine::Context&) noexcept {
static constexpr auto btnHeight = 18; static constexpr auto btnHeight = 18;
const auto btnSize = ImVec2(18, btnHeight); const auto btnSize = ImVec2(18, btnHeight);
if (ImGui::Button("+", btnSize)) { if (ImGui::Button("+", btnSize)) {
auto insertOnIdx = m_model.activeSubSheetIdx(); auto insertOnIdx = model()->activeSubSheetIdx();
const auto &parent = *m_model.activeSubSheet(); const auto &parent = *model()->activeSubSheet();
m_model.addSubsheet(insertOnIdx); model()->addSubsheet(insertOnIdx);
insertOnIdx.emplace_back(parent.subsheets.size() - 1); insertOnIdx.emplace_back(parent.subsheets.size() - 1);
m_model.setActiveSubsheet(insertOnIdx); model()->setActiveSubsheet(insertOnIdx);
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("-", btnSize)) { if (ImGui::Button("-", btnSize)) {
const auto &activeSubsheetIdx = m_model.activeSubSheetIdx(); const auto &activeSubsheetIdx = model()->activeSubSheetIdx();
if (activeSubsheetIdx.size() > 0) { if (activeSubsheetIdx.size() > 0) {
m_model.rmSubsheet(activeSubsheetIdx); model()->rmSubsheet(activeSubsheetIdx);
} }
} }
ImGui::SameLine(); ImGui::SameLine();
@ -177,7 +167,7 @@ void TileSheetEditorImGui::draw(turbine::Context&) noexcept {
ImGui::TableSetupColumn("Columns", ImGuiTableColumnFlags_WidthFixed, 50); ImGui::TableSetupColumn("Columns", ImGuiTableColumnFlags_WidthFixed, 50);
ImGui::TableSetupColumn("Rows", ImGuiTableColumnFlags_WidthFixed, 50); ImGui::TableSetupColumn("Rows", ImGuiTableColumnFlags_WidthFixed, 50);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
drawSubsheetSelector(&m_view.img().subsheet, &path); drawSubsheetSelector(&m_tileSheetEditor.img().subsheet, &path);
ImGui::EndTable(); ImGui::EndTable();
} }
} }
@ -192,7 +182,7 @@ void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, T
using Str = ox::BasicString<100>; using Str = ox::BasicString<100>;
auto pathStr = ox::join<Str>("##", *path).value; auto pathStr = ox::join<Str>("##", *path).value;
auto lbl = ox::sfmt<Str>("{}##{}", subsheet->name, pathStr); auto lbl = ox::sfmt<Str>("{}##{}", subsheet->name, pathStr);
const auto rowSelected = *path == m_model.activeSubSheetIdx(); const auto rowSelected = *path == model()->activeSubSheetIdx();
const auto flags = ImGuiTreeNodeFlags_SpanFullWidth const auto flags = ImGuiTreeNodeFlags_SpanFullWidth
| ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnArrow
| ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_DefaultOpen
@ -202,7 +192,7 @@ void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, T
const auto open = ImGui::TreeNodeEx(lbl.c_str(), flags); const auto open = ImGui::TreeNodeEx(lbl.c_str(), flags);
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::IsItemClicked()) { if (ImGui::IsItemClicked()) {
m_model.setActiveSubsheet(*path); model()->setActiveSubsheet(*path);
} }
if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) { if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
showSubsheetEditor(); showSubsheetEditor();
@ -239,11 +229,11 @@ ox::Vec2 TileSheetEditorImGui::clickPos(ImVec2 const&winPos, ox::Vec2 clickPos)
} }
ox::Error TileSheetEditorImGui::saveItem() noexcept { ox::Error TileSheetEditorImGui::saveItem() noexcept {
return m_model.saveFile(); return model()->saveFile();
} }
void TileSheetEditorImGui::showSubsheetEditor() noexcept { void TileSheetEditorImGui::showSubsheetEditor() noexcept {
const auto sheet = m_model.activeSubSheet(); const auto sheet = model()->activeSubSheet();
if (sheet->subsheets.size()) { if (sheet->subsheets.size()) {
m_subsheetEditor.show(sheet->name, -1, -1); m_subsheetEditor.show(sheet->name, -1, -1);
} else { } else {
@ -257,9 +247,9 @@ void TileSheetEditorImGui::exportSubhseetToPng() noexcept {
return; return;
} }
// subsheet to png // subsheet to png
const auto &img = m_model.img(); const auto &img = model()->img();
const auto &s = *m_model.activeSubSheet(); const auto &s = *model()->activeSubSheet();
const auto &pal = m_model.pal(); const auto &pal = model()->pal();
err = toPngFile(path, s, *pal, img.bpp); err = toPngFile(path, s, *pal, img.bpp);
if (err) { if (err) {
oxErrorf("Tilesheet export failed: {}", toStr(err)); oxErrorf("Tilesheet export failed: {}", toStr(err));
@ -271,17 +261,20 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
const auto fbSizei = ox::Size(static_cast<int>(fbSize.x), static_cast<int>(fbSize.y)); const auto fbSizei = ox::Size(static_cast<int>(fbSize.x), static_cast<int>(fbSize.y));
if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) { if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) {
glutils::resizeInitFrameBuffer(m_framebuffer, fbSizei.width, fbSizei.height); glutils::resizeInitFrameBuffer(m_framebuffer, fbSizei.width, fbSizei.height);
m_view.resizeView(fbSize); m_tileSheetEditor.resizeView(fbSize);
} else if (m_view.updated()) { } else if (m_tileSheetEditor.updated()) {
m_view.ackUpdate(); m_tileSheetEditor.ackUpdate();
} }
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
// clear screen and draw // clear screen and draw
glViewport(0, 0, fbSizei.width, fbSizei.height); glViewport(0, 0, fbSizei.width, fbSizei.height);
m_view.draw(); m_tileSheetEditor.draw();
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
ImTextureID buffId{};
static_assert(sizeof(ImTextureID) >= sizeof(m_framebuffer.color.id));
memcpy(&buffId, &m_framebuffer.color.id, sizeof(m_framebuffer.color.id));
ImGui::Image( ImGui::Image(
std::bit_cast<ImTextureID>(uintptr_t{m_framebuffer.color.id}), buffId,
static_cast<ImVec2>(fbSize), static_cast<ImVec2>(fbSize),
ImVec2(0, 1), ImVec2(0, 1),
ImVec2(1, 0)); ImVec2(1, 0));
@ -294,22 +287,22 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
if (wheel != 0) { if (wheel != 0) {
const auto zoomMod = ox::defines::OS == ox::OS::Darwin ? const auto zoomMod = ox::defines::OS == ox::OS::Darwin ?
io.KeySuper : turbine::buttonDown(m_ctx, turbine::Key::Mod_Ctrl); io.KeySuper : turbine::buttonDown(m_ctx, turbine::Key::Mod_Ctrl);
m_view.scrollV(fbSize, wheel, zoomMod); m_tileSheetEditor.scrollV(fbSize, wheel, zoomMod);
} }
if (wheelh != 0) { if (wheelh != 0) {
m_view.scrollH(fbSize, wheelh); m_tileSheetEditor.scrollH(fbSize, wheelh);
} }
if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) { if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) {
m_prevMouseDownPos = mousePos; m_prevMouseDownPos = mousePos;
switch (m_tool) { switch (m_tool) {
case Tool::Draw: case Tool::Draw:
m_view.clickDraw(fbSize, clickPos(winPos, mousePos)); m_tileSheetEditor.clickDraw(fbSize, clickPos(winPos, mousePos));
break; break;
case Tool::Fill: case Tool::Fill:
m_view.clickFill(fbSize, clickPos(winPos, mousePos)); m_tileSheetEditor.clickFill(fbSize, clickPos(winPos, mousePos));
break; break;
case Tool::Select: case Tool::Select:
m_view.clickSelect(fbSize, clickPos(winPos, mousePos)); m_tileSheetEditor.clickSelect(fbSize, clickPos(winPos, mousePos));
break; break;
case Tool::None: case Tool::None:
break; break;
@ -319,22 +312,22 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
if (ImGui::BeginPopupContextItem("TileMenu", ImGuiPopupFlags_MouseButtonRight)) { if (ImGui::BeginPopupContextItem("TileMenu", ImGuiPopupFlags_MouseButtonRight)) {
const auto popupPos = ox::Vec2(ImGui::GetWindowPos()); const auto popupPos = ox::Vec2(ImGui::GetWindowPos());
if (ImGui::MenuItem("Insert Tile")) { if (ImGui::MenuItem("Insert Tile")) {
m_view.insertTile(fbSize, clickPos(winPos, popupPos)); m_tileSheetEditor.insertTile(fbSize, clickPos(winPos, popupPos));
} }
if (ImGui::MenuItem("Delete Tile")) { if (ImGui::MenuItem("Delete Tile")) {
m_view.deleteTile(fbSize, clickPos(winPos, popupPos)); m_tileSheetEditor.deleteTile(fbSize, clickPos(winPos, popupPos));
} }
ImGui::EndPopup(); ImGui::EndPopup();
} }
if (io.MouseReleased[0]) { if (io.MouseReleased[0]) {
m_prevMouseDownPos = {-1, -1}; m_prevMouseDownPos = {-1, -1};
m_view.releaseMouseButton(); m_tileSheetEditor.releaseMouseButton();
} }
} }
void TileSheetEditorImGui::drawPaletteSelector() noexcept { void TileSheetEditorImGui::drawPaletteSelector() noexcept {
auto &sctx = *applicationData<studio::StudioContext>(m_ctx); auto sctx = applicationData<studio::StudioContext>(m_ctx);
const auto &files = sctx.project->fileList(core::FileExt_npal); const auto &files = sctx->project->fileList(core::FileExt_npal);
const auto first = m_selectedPaletteIdx < files.size() ? const auto first = m_selectedPaletteIdx < files.size() ?
files[m_selectedPaletteIdx].c_str() : ""; files[m_selectedPaletteIdx].c_str() : "";
if (ImGui::BeginCombo("Palette", first, 0)) { if (ImGui::BeginCombo("Palette", first, 0)) {
@ -342,7 +335,7 @@ void TileSheetEditorImGui::drawPaletteSelector() noexcept {
const auto selected = (m_selectedPaletteIdx == n); const auto selected = (m_selectedPaletteIdx == n);
if (ImGui::Selectable(files[n].c_str(), selected) && m_selectedPaletteIdx != n) { if (ImGui::Selectable(files[n].c_str(), selected) && m_selectedPaletteIdx != n) {
m_selectedPaletteIdx = n; m_selectedPaletteIdx = n;
oxLogError(m_model.setPalette(files[n])); oxLogError(model()->setPalette(files[n]));
} }
if (selected) { if (selected) {
ImGui::SetItemDefaultFocus(); ImGui::SetItemDefaultFocus();
@ -356,15 +349,15 @@ void TileSheetEditorImGui::drawPaletteSelector() noexcept {
ImGui::TableSetupColumn("", 0, 0.22f); ImGui::TableSetupColumn("", 0, 0.22f);
ImGui::TableSetupColumn("Color16", 0, 3); ImGui::TableSetupColumn("Color16", 0, 3);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
if (auto pal = m_view.pal()) { if (auto pal = m_tileSheetEditor.pal()) {
for (auto i = 0u; auto c: pal->colors) { for (auto i = 0u; auto c: pal->colors) {
ImGui::PushID(static_cast<int>(i)); ImGui::PushID(static_cast<int>(i));
// Column: color idx // Column: color idx
ImGui::TableNextColumn(); ImGui::TableNextColumn();
const auto label = ox::BString<8>() + (i + 1); const auto label = ox::BString<8>() + (i + 1);
const auto rowSelected = i == m_view.palIdx(); const auto rowSelected = i == m_tileSheetEditor.palIdx();
if (ImGui::Selectable(label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) { if (ImGui::Selectable(label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
m_view.setPalIdx(i); m_tileSheetEditor.setPalIdx(i);
} }
// Column: color RGB // Column: color RGB
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -382,13 +375,13 @@ void TileSheetEditorImGui::drawPaletteSelector() noexcept {
} }
ox::Error TileSheetEditorImGui::updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept { ox::Error TileSheetEditorImGui::updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept {
return m_model.updateSubsheet(m_model.activeSubSheetIdx(), name, cols, rows); return model()->updateSubsheet(model()->activeSubSheetIdx(), name, cols, rows);
} }
ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept { ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept {
const auto &palPath = m_model.palPath(); const auto &palPath = model()->palPath();
auto &sctx = *applicationData<studio::StudioContext>(m_ctx); auto sctx = applicationData<studio::StudioContext>(m_ctx);
const auto &palList = sctx.project->fileList(core::FileExt_npal); const auto &palList = sctx->project->fileList(core::FileExt_npal);
for (std::size_t i = 0; const auto &pal : palList) { for (std::size_t i = 0; const auto &pal : palList) {
if (palPath == pal) { if (palPath == pal) {
m_selectedPaletteIdx = i; m_selectedPaletteIdx = i;

View File

@ -47,8 +47,7 @@ class TileSheetEditorImGui: public studio::Editor {
ox::Vector<ox::String> m_paletteList; ox::Vector<ox::String> m_paletteList;
SubSheetEditor m_subsheetEditor; SubSheetEditor m_subsheetEditor;
glutils::FrameBuffer m_framebuffer; glutils::FrameBuffer m_framebuffer;
TileSheetEditorView m_view; TileSheetEditorView m_tileSheetEditor;
TileSheetEditorModel &m_model;
float m_palViewWidth = 300; float m_palViewWidth = 300;
ox::Vec2 m_prevMouseDownPos; ox::Vec2 m_prevMouseDownPos;
Tool m_tool = Tool::Draw; Tool m_tool = Tool::Draw;
@ -83,6 +82,16 @@ class TileSheetEditorImGui: public studio::Editor {
void exportSubhseetToPng() noexcept; void exportSubhseetToPng() noexcept;
[[nodiscard]]
constexpr auto model() const noexcept {
return m_tileSheetEditor.model();
}
[[nodiscard]]
constexpr auto model() noexcept {
return m_tileSheetEditor.model();
}
void drawTileSheet(ox::Vec2 const&fbSize) noexcept; void drawTileSheet(ox::Vec2 const&fbSize) noexcept;
void drawPaletteSelector() noexcept; void drawPaletteSelector() noexcept;

View File

@ -124,6 +124,18 @@ class TileSheetEditorModel: public ox::SignalHandler {
private: private:
void pushCommand(studio::UndoCommand *cmd) noexcept; void pushCommand(studio::UndoCommand *cmd) noexcept;
void setPalette();
void saveState();
void restoreState();
[[nodiscard]]
ox::String paletteName(ox::String const&palettePath) const;
[[nodiscard]]
ox::String palettePath(ox::String const&palettePath) const;
}; };
constexpr const TileSheet &TileSheetEditorModel::img() const noexcept { constexpr const TileSheet &TileSheetEditorModel::img() const noexcept {

View File

@ -84,13 +84,13 @@ class TileSheetEditorView: public ox::SignalHandler {
constexpr const Palette *pal() const noexcept; constexpr const Palette *pal() const noexcept;
[[nodiscard]] [[nodiscard]]
constexpr auto &model() noexcept { constexpr auto *model() noexcept {
return m_model; return &m_model;
} }
[[nodiscard]] [[nodiscard]]
constexpr auto &model() const noexcept { constexpr auto *model() const noexcept {
return m_model; return &m_model;
} }
constexpr auto setPalIdx(auto palIdx) noexcept { constexpr auto setPalIdx(auto palIdx) noexcept {

View File

@ -53,7 +53,7 @@ static ox::Error runApp(
turbine::setConstantRefresh(*ctx, false); turbine::setConstantRefresh(*ctx, false);
studio::StudioContext studioCtx; studio::StudioContext studioCtx;
turbine::setApplicationData(*ctx, &studioCtx); turbine::setApplicationData(*ctx, &studioCtx);
StudioUI ui(*ctx, projectDataDir); StudioUI ui(ctx.get(), projectDataDir);
studioCtx.ui = &ui; studioCtx.ui = &ui;
StudioUIDrawer drawer(ui); StudioUIDrawer drawer(ui);
turbine::gl::addDrawer(*ctx, &drawer); turbine::gl::addDrawer(*ctx, &drawer);

View File

@ -15,12 +15,7 @@
namespace studio { namespace studio {
static ox::Vector<const studio::Module*> modules; ox::Vector<const studio::Module*> modules;
void registerModule(studio::Module const*mod) noexcept {
modules.emplace_back(mod);
}
struct StudioConfig { struct StudioConfig {
static constexpr auto TypeName = "net.drinkingtea.studio.StudioConfig"; static constexpr auto TypeName = "net.drinkingtea.studio.StudioConfig";
@ -38,25 +33,23 @@ oxModelBegin(StudioConfig)
oxModelFieldRename(show_project_explorer, showProjectExplorer) oxModelFieldRename(show_project_explorer, showProjectExplorer)
oxModelEnd() oxModelEnd()
StudioUI::StudioUI(turbine::Context &ctx, ox::StringView projectDataDir) noexcept: StudioUI::StudioUI(turbine::Context *ctx, ox::StringView projectDir) noexcept:
m_ctx(ctx), m_ctx(*ctx),
m_projectDataDir(projectDataDir), m_projectDir(projectDir),
m_projectExplorer(ox::make_unique<ProjectExplorer>(m_ctx)), m_projectExplorer(ox::make_unique<ProjectExplorer>(m_ctx)),
m_aboutPopup(m_ctx) { m_aboutPopup(*ctx) {
m_projectExplorer->fileChosen.connect(this, &StudioUI::openFile); m_projectExplorer->fileChosen.connect(this, &StudioUI::openFile);
ImGui::GetIO().IniFilename = nullptr; ImGui::GetIO().IniFilename = nullptr;
loadModules(); loadModules();
// open project and files // open project and files
auto const [config, err] = studio::readConfig<StudioConfig>(keelCtx(m_ctx)); const auto [config, err] = studio::readConfig<StudioConfig>(keelCtx(*ctx));
m_showProjectExplorer = config.showProjectExplorer; m_showProjectExplorer = config.showProjectExplorer;
if (!err) { if (!err) {
auto const openProjErr = openProject(config.projectPath); oxIgnoreError(openProject(config.projectPath));
if (!openProjErr) { for (const auto &f : config.openFiles) {
for (const auto &f: config.openFiles) { auto openFileErr = openFileActiveTab(f, config.activeTabItemName == f);
auto openFileErr = openFileActiveTab(f, config.activeTabItemName == f); if (openFileErr) {
if (openFileErr) { oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr));
oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr));
}
} }
} }
} else { } else {
@ -90,9 +83,7 @@ void StudioUI::handleKeyEvent(turbine::Key key, bool down) noexcept {
toggleProjectExplorer(); toggleProjectExplorer();
break; break;
case turbine::Key::Alpha_C: case turbine::Key::Alpha_C:
if (m_activeEditor && m_activeEditor->copyEnabled()) { m_activeEditor->copy();
m_activeEditor->copy();
}
break; break;
case turbine::Key::Alpha_N: case turbine::Key::Alpha_N:
m_newMenu.open(); m_newMenu.open();
@ -107,14 +98,10 @@ void StudioUI::handleKeyEvent(turbine::Key key, bool down) noexcept {
save(); save();
break; break;
case turbine::Key::Alpha_V: case turbine::Key::Alpha_V:
if (m_activeEditor && m_activeEditor->pasteEnabled()) { m_activeEditor->paste();
m_activeEditor->paste();
}
break; break;
case turbine::Key::Alpha_X: case turbine::Key::Alpha_X:
if (m_activeEditor && m_activeEditor->cutEnabled()) { m_activeEditor->cut();
m_activeEditor->cut();
}
break; break;
case turbine::Key::Alpha_Y: case turbine::Key::Alpha_Y:
redo(); redo();
@ -182,19 +169,19 @@ void StudioUI::drawMenu() noexcept {
if (ImGui::BeginMenu("Edit")) { if (ImGui::BeginMenu("Edit")) {
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr; auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
if (ImGui::MenuItem("Undo", "Ctrl+Z", false, undoStack && undoStack->canUndo())) { if (ImGui::MenuItem("Undo", "Ctrl+Z", false, undoStack && undoStack->canUndo())) {
undoStack->undo(); m_activeEditor->undoStack()->undo();
} }
if (ImGui::MenuItem("Redo", "Ctrl+Y", false, undoStack && undoStack->canRedo())) { if (ImGui::MenuItem("Redo", "Ctrl+Y", false, undoStack && undoStack->canRedo())) {
undoStack->redo(); m_activeEditor->undoStack()->redo();
} }
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("Copy", "Ctrl+C", false, m_activeEditor && m_activeEditor->copyEnabled())) { if (ImGui::MenuItem("Copy", "Ctrl+C")) {
m_activeEditor->copy(); m_activeEditor->copy();
} }
if (ImGui::MenuItem("Cut", "Ctrl+X", false, m_activeEditor && m_activeEditor->cutEnabled())) { if (ImGui::MenuItem("Cut", "Ctrl+X")) {
m_activeEditor->cut(); m_activeEditor->cut();
} }
if (ImGui::MenuItem("Paste", "Ctrl+V", false, m_activeEditor && m_activeEditor->pasteEnabled()) && m_activeEditor) { if (ImGui::MenuItem("Paste", "Ctrl+V")) {
m_activeEditor->paste(); m_activeEditor->paste();
} }
ImGui::EndMenu(); ImGui::EndMenu();
@ -253,9 +240,6 @@ void StudioUI::drawTabs() noexcept {
} }
if (!open) { if (!open) {
e->close(); e->close();
if (m_activeEditor == (*it).get()) {
m_activeEditor = nullptr;
}
try { try {
oxThrowError(m_editors.erase(it).moveTo(&it)); oxThrowError(m_editors.erase(it).moveTo(&it));
} catch (const ox::Exception &ex) { } catch (const ox::Exception &ex) {
@ -321,7 +305,7 @@ ox::Error StudioUI::openProject(ox::CRStringView path) noexcept {
oxRequireM(fs, keel::loadRomFs(path)); oxRequireM(fs, keel::loadRomFs(path));
oxReturnError(keel::setRomFs(keelCtx(m_ctx), std::move(fs))); oxReturnError(keel::setRomFs(keelCtx(m_ctx), std::move(fs)));
turbine::setWindowTitle(m_ctx, ox::sfmt("{} - {}", keelCtx(m_ctx).appName, path)); turbine::setWindowTitle(m_ctx, ox::sfmt("{} - {}", keelCtx(m_ctx).appName, path));
m_project = ox::make_unique<studio::Project>(keelCtx(m_ctx), ox::String(path), m_projectDataDir); m_project = ox::make_unique<studio::Project>(keelCtx(m_ctx), ox::String(path), m_projectDir);
auto sctx = applicationData<studio::StudioContext>(m_ctx); auto sctx = applicationData<studio::StudioContext>(m_ctx);
sctx->project = m_project.get(); sctx->project = m_project.get();
m_project->fileAdded.connect(m_projectExplorer.get(), &ProjectExplorer::refreshProjectTreeModel); m_project->fileAdded.connect(m_projectExplorer.get(), &ProjectExplorer::refreshProjectTreeModel);
@ -340,9 +324,6 @@ ox::Error StudioUI::openFile(ox::CRStringView path) noexcept {
} }
ox::Error StudioUI::openFileActiveTab(ox::CRStringView path, bool makeActiveTab) noexcept { ox::Error StudioUI::openFileActiveTab(ox::CRStringView path, bool makeActiveTab) noexcept {
if (!m_project) {
return OxError(1, "No project open to open a file from");
}
if (m_openFiles.contains(path)) { if (m_openFiles.contains(path)) {
for (auto &e : m_editors) { for (auto &e : m_editors) {
if (makeActiveTab && e->itemPath() == path) { if (makeActiveTab && e->itemPath() == path) {
@ -351,7 +332,7 @@ ox::Error StudioUI::openFileActiveTab(ox::CRStringView path, bool makeActiveTab)
break; break;
} }
} }
return {}; return OxError(0);
} }
oxRequire(ext, studio::fileExt(path).to<ox::String>([](auto const&v) {return ox::String(v);})); oxRequire(ext, studio::fileExt(path).to<ox::String>([](auto const&v) {return ox::String(v);}));
// create Editor // create Editor
@ -391,14 +372,18 @@ ox::Error StudioUI::openFileActiveTab(ox::CRStringView path, bool makeActiveTab)
ox::Error StudioUI::closeFile(ox::CRStringView path) noexcept { ox::Error StudioUI::closeFile(ox::CRStringView path) noexcept {
if (!m_openFiles.contains(path)) { if (!m_openFiles.contains(path)) {
return {}; return OxError(0);
} }
oxIgnoreError(m_openFiles.erase(std::remove(m_openFiles.begin(), m_openFiles.end(), path))); oxIgnoreError(m_openFiles.erase(std::remove(m_openFiles.begin(), m_openFiles.end(), path)));
// save to config // save to config
studio::editConfig<StudioConfig>(keelCtx(m_ctx), [&](StudioConfig *config) { studio::editConfig<StudioConfig>(keelCtx(m_ctx), [&](StudioConfig *config) {
oxIgnoreError(config->openFiles.erase(std::remove(config->openFiles.begin(), config->openFiles.end(), path))); oxIgnoreError(config->openFiles.erase(std::remove(config->openFiles.begin(), config->openFiles.end(), path)));
}); });
return {}; return OxError(0);
}
void registerModule(const studio::Module *mod) noexcept {
modules.emplace_back(mod);
} }
} }

View File

@ -24,27 +24,27 @@ class StudioUI: public ox::SignalHandler {
private: private:
turbine::Context &m_ctx; turbine::Context &m_ctx;
ox::String m_projectDataDir; ox::String m_projectDir;
ox::UPtr<studio::Project> m_project; ox::UniquePtr<studio::Project> m_project;
studio::TaskRunner m_taskRunner; studio::TaskRunner m_taskRunner;
ox::Vector<ox::UPtr<studio::BaseEditor>> m_editors; ox::Vector<ox::UniquePtr<studio::BaseEditor>> m_editors;
ox::Vector<ox::UPtr<studio::Widget>> m_widgets; ox::Vector<ox::UniquePtr<studio::Widget>> m_widgets;
ox::HashMap<ox::String, studio::EditorMaker::Func> m_editorMakers; ox::HashMap<ox::String, studio::EditorMaker::Func> m_editorMakers;
ox::UPtr<ProjectExplorer> m_projectExplorer; ox::UniquePtr<ProjectExplorer> m_projectExplorer;
ox::Vector<ox::String> m_openFiles; ox::Vector<ox::String> m_openFiles;
studio::BaseEditor *m_activeEditorOnLastDraw = nullptr; studio::BaseEditor *m_activeEditorOnLastDraw = nullptr;
studio::BaseEditor *m_activeEditor = nullptr; studio::BaseEditor *m_activeEditor = nullptr;
studio::BaseEditor *m_activeEditorUpdatePending = nullptr; studio::BaseEditor *m_activeEditorUpdatePending = nullptr;
NewMenu m_newMenu; NewMenu m_newMenu;
AboutPopup m_aboutPopup; AboutPopup m_aboutPopup;
ox::Array<studio::Popup*, 2> const m_popups = { const ox::Array<studio::Popup*, 2> m_popups = {
&m_newMenu, &m_newMenu,
&m_aboutPopup &m_aboutPopup
}; };
bool m_showProjectExplorer = true; bool m_showProjectExplorer = true;
public: public:
explicit StudioUI(turbine::Context &ctx, ox::StringView projectDataDir) noexcept; explicit StudioUI(turbine::Context *ctx, ox::StringView projectDir) noexcept;
void update() noexcept; void update() noexcept;

View File

@ -76,17 +76,17 @@ class BaseEditor: public Widget {
void setCutEnabled(bool); void setCutEnabled(bool);
[[nodiscard]] [[nodiscard]]
bool cutEnabled() const noexcept; bool cutEnabled() const;
void setCopyEnabled(bool); void setCopyEnabled(bool);
[[nodiscard]] [[nodiscard]]
bool copyEnabled() const noexcept; bool copyEnabled() const;
void setPasteEnabled(bool); void setPasteEnabled(bool);
[[nodiscard]] [[nodiscard]]
bool pasteEnabled() const noexcept; bool pasteEnabled() const;
protected: protected:
/** /**

View File

@ -54,8 +54,8 @@ void BaseEditor::save() noexcept {
} }
void BaseEditor::setUnsavedChanges(bool uc) { void BaseEditor::setUnsavedChanges(bool uc) {
m_unsavedChanges = uc; m_unsavedChanges = uc;
unsavedChangesChanged.emit(uc); unsavedChangesChanged.emit(uc);
} }
bool BaseEditor::unsavedChanges() const noexcept { bool BaseEditor::unsavedChanges() const noexcept {
@ -76,7 +76,7 @@ void BaseEditor::setCutEnabled(bool v) {
cutEnabledChanged.emit(v); cutEnabledChanged.emit(v);
} }
bool BaseEditor::cutEnabled() const noexcept { bool BaseEditor::cutEnabled() const {
return m_cutEnabled; return m_cutEnabled;
} }
@ -85,7 +85,7 @@ void BaseEditor::setCopyEnabled(bool v) {
copyEnabledChanged.emit(v); copyEnabledChanged.emit(v);
} }
bool BaseEditor::copyEnabled() const noexcept { bool BaseEditor::copyEnabled() const {
return m_copyEnabled; return m_copyEnabled;
} }
@ -94,12 +94,12 @@ void BaseEditor::setPasteEnabled(bool v) {
pasteEnabledChanged.emit(v); pasteEnabledChanged.emit(v);
} }
bool BaseEditor::pasteEnabled() const noexcept { bool BaseEditor::pasteEnabled() const {
return m_pasteEnabled; return m_pasteEnabled;
} }
ox::Error BaseEditor::saveItem() noexcept { ox::Error BaseEditor::saveItem() noexcept {
return {}; return OxError(0);
} }
UndoStack *BaseEditor::undoStack() noexcept { UndoStack *BaseEditor::undoStack() noexcept {