Compare commits
4 Commits
26f1a6051f
...
ed1160ec74
Author | SHA1 | Date | |
---|---|---|---|
ed1160ec74 | |||
1e21778059 | |||
78379f58c8 | |||
4322f720ba |
@ -1,5 +1,6 @@
|
||||
# d2025.06.0
|
||||
|
||||
* Add ability to remember recent projects in config
|
||||
* PaletteEditor: Add RGB key shortcuts for focusing color channels
|
||||
|
||||
# d2025.05.1
|
||||
|
@ -36,7 +36,7 @@ OX_MODEL_BEGIN(TileSheetEditorConfig)
|
||||
OX_MODEL_END()
|
||||
|
||||
static ox::Vector<uint32_t> normalizePixelSizes(
|
||||
ox::Vector<uint8_t> const&inPixels) noexcept {
|
||||
ox::Vector<uint8_t> const &inPixels) noexcept {
|
||||
ox::Vector<uint32_t> outPixels;
|
||||
outPixels.reserve(inPixels.size());
|
||||
outPixels.resize(inPixels.size());
|
||||
@ -47,7 +47,7 @@ static ox::Vector<uint32_t> normalizePixelSizes(
|
||||
}
|
||||
|
||||
static ox::Vector<uint32_t> normalizePixelArrangement(
|
||||
ox::Vector<uint32_t> const&inPixels,
|
||||
ox::Vector<uint32_t> const &inPixels,
|
||||
int const cols,
|
||||
int const scale) {
|
||||
auto const scalePt = ox::Point{scale, scale};
|
||||
@ -67,10 +67,10 @@ static ox::Vector<uint32_t> normalizePixelArrangement(
|
||||
}
|
||||
|
||||
static ox::Error toPngFile(
|
||||
ox::CStringView const&path,
|
||||
ox::CStringViewCR path,
|
||||
ox::Vector<uint32_t> &&pixels,
|
||||
Palette const&pal,
|
||||
size_t page,
|
||||
Palette const &pal,
|
||||
size_t const page,
|
||||
unsigned const width,
|
||||
unsigned const height) noexcept {
|
||||
for (auto &c : pixels) {
|
||||
@ -88,7 +88,7 @@ static ox::Error toPngFile(
|
||||
}
|
||||
|
||||
TileSheetEditorImGui::TileSheetEditorImGui(studio::Context &sctx, ox::StringParam path):
|
||||
Editor(sctx, std::move(path)),
|
||||
Editor{sctx, std::move(path)},
|
||||
m_sctx{sctx},
|
||||
m_tctx{m_sctx.tctx},
|
||||
m_palPicker{"Palette Chooser", keelCtx(sctx), FileExt_npal},
|
||||
@ -98,8 +98,11 @@ TileSheetEditorImGui::TileSheetEditorImGui(studio::Context &sctx, ox::StringPara
|
||||
m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
|
||||
m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubsheetToPng);
|
||||
// load config
|
||||
auto const&config = studio::readConfig<TileSheetEditorConfig>(
|
||||
keelCtx(m_sctx), itemPath());
|
||||
auto &kctx = keelCtx(m_sctx);
|
||||
auto const ip = itemPath();
|
||||
oxLogError(studio::headerizeConfigFile<TileSheetEditorConfig>(kctx, ip));
|
||||
auto const &config =
|
||||
studio::readConfig<TileSheetEditorConfig>(kctx, ip);
|
||||
if (config.ok()) {
|
||||
m_model.setActiveSubsheet(validateSubSheetIdx(m_model.img(), config.value.activeSubsheet));
|
||||
}
|
||||
@ -192,7 +195,7 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept {
|
||||
if (ig::mainWinHasFocus() && m_tool == TileSheetTool::Select) {
|
||||
if (ImGui::IsKeyDown(ImGuiKey_ModCtrl) && !m_palPathFocused) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_A)) {
|
||||
auto const&img = m_model.activeSubSheet();
|
||||
auto const &img = m_model.activeSubSheet();
|
||||
m_model.setSelection({{}, {img.columns * TileWidth - 1, img.rows * TileHeight - 1}});
|
||||
} else if (ImGui::IsKeyPressed(ImGuiKey_G)) {
|
||||
m_model.clearSelection();
|
||||
@ -271,14 +274,14 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept {
|
||||
auto constexpr btnSize = ImVec2{btnHeight, btnHeight};
|
||||
if (ig::PushButton("+", btnSize)) {
|
||||
auto insertOnIdx = m_model.activeSubSheetIdx();
|
||||
auto const&parent = m_model.activeSubSheet();
|
||||
auto const &parent = m_model.activeSubSheet();
|
||||
m_model.addSubsheet(insertOnIdx);
|
||||
insertOnIdx.emplace_back(parent.subsheets.size() - 1);
|
||||
setActiveSubsheet(insertOnIdx);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ig::PushButton("-", btnSize)) {
|
||||
auto const&activeSubsheetIdx = m_model.activeSubSheetIdx();
|
||||
auto const &activeSubsheetIdx = m_model.activeSubSheetIdx();
|
||||
if (!activeSubsheetIdx.empty()) {
|
||||
m_model.rmSubsheet(activeSubsheetIdx);
|
||||
}
|
||||
@ -378,7 +381,7 @@ void TileSheetEditorImGui::drawSubsheetSelector(
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vec2 TileSheetEditorImGui::clickPos(ImVec2 const&winPos, ox::Vec2 clickPos) noexcept {
|
||||
ox::Vec2 TileSheetEditorImGui::clickPos(ImVec2 const &winPos, ox::Vec2 clickPos) noexcept {
|
||||
clickPos.x -= winPos.x + 10;
|
||||
clickPos.y -= winPos.y + 10;
|
||||
return clickPos;
|
||||
@ -400,7 +403,7 @@ void TileSheetEditorImGui::navigateTo(ox::StringViewCR arg) noexcept {
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::showSubsheetEditor() noexcept {
|
||||
auto const&sheet = m_model.activeSubSheet();
|
||||
auto const &sheet = m_model.activeSubSheet();
|
||||
if (!sheet.subsheets.empty()) {
|
||||
m_subsheetEditor.show(sheet.name, -1, -1);
|
||||
} else {
|
||||
@ -411,8 +414,8 @@ void TileSheetEditorImGui::showSubsheetEditor() noexcept {
|
||||
ox::Error TileSheetEditorImGui::exportSubsheetToPng(int const scale) const noexcept {
|
||||
OX_REQUIRE(path, studio::saveFile({{"PNG", "png"}}));
|
||||
// subsheet to png
|
||||
auto const&s = m_model.activeSubSheet();
|
||||
auto const&pal = m_model.pal();
|
||||
auto const &s = m_model.activeSubSheet();
|
||||
auto const &pal = m_model.pal();
|
||||
auto const width = s.columns * TileWidth;
|
||||
auto const height = s.rows * TileHeight;
|
||||
auto pixels = normalizePixelSizes(s.pixels);
|
||||
@ -430,7 +433,7 @@ ox::Error TileSheetEditorImGui::exportSubsheetToPng(int const scale) const noexc
|
||||
return err;
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
|
||||
void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const &fbSize) noexcept {
|
||||
auto const winPos = ImGui::GetWindowPos();
|
||||
auto const fbSizei = ox::Size{static_cast<int>(fbSize.x), static_cast<int>(fbSize.y)};
|
||||
if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) {
|
||||
@ -451,7 +454,7 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
|
||||
{0, 1},
|
||||
{1, 0});
|
||||
// handle input, this must come after drawing
|
||||
auto const&io = ImGui::GetIO();
|
||||
auto const &io = ImGui::GetIO();
|
||||
auto const mousePos = ox::Vec2{ImGui::GetMousePos()};
|
||||
if (ImGui::IsItemHovered()) {
|
||||
auto const wheel = io.MouseWheel;
|
||||
@ -556,9 +559,9 @@ void TileSheetEditorImGui::drawPaletteMenu() noexcept {
|
||||
ImGui::TableSetupColumn("Color16", 0, 3);
|
||||
ImGui::TableHeadersRow();
|
||||
{
|
||||
auto const&pal = m_model.pal();
|
||||
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()].colors) {
|
||||
for (auto i = 0u; auto const &c: pal.pages[m_model.palettePage()].colors) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
// Column: color idx
|
||||
@ -580,7 +583,7 @@ void TileSheetEditorImGui::drawPaletteMenu() noexcept {
|
||||
auto const ic = ImGui::GetColorU32({redf(c), greenf(c), bluef(c), 1});
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
|
||||
ImGui::TableNextColumn();
|
||||
auto const&name = i < pal.colorNames.size() ? pal.colorNames[i].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));
|
||||
@ -594,7 +597,7 @@ void TileSheetEditorImGui::drawPaletteMenu() noexcept {
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorImGui::updateActiveSubsheet(
|
||||
ox::StringView const&name, int const cols, int const rows) noexcept {
|
||||
ox::StringViewCR name, int const cols, int const rows) noexcept {
|
||||
return m_model.updateSubsheet(m_model.activeSubSheetIdx(), name, cols, rows);
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
void drawSubsheetSelector(TileSheet::SubSheet&, TileSheet::SubSheetIdx &path);
|
||||
|
||||
[[nodiscard]]
|
||||
static ox::Vec2 clickPos(ImVec2 const&winPos, ox::Vec2 clickPos) noexcept;
|
||||
static ox::Vec2 clickPos(ImVec2 const &winPos, ox::Vec2 clickPos) noexcept;
|
||||
|
||||
protected:
|
||||
ox::Error saveItem() noexcept override;
|
||||
@ -94,11 +94,11 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
|
||||
ox::Error exportSubsheetToPng(int scale) const noexcept;
|
||||
|
||||
void drawTileSheet(ox::Vec2 const&fbSize) noexcept;
|
||||
void drawTileSheet(ox::Vec2 const &fbSize) noexcept;
|
||||
|
||||
void drawPaletteMenu() noexcept;
|
||||
|
||||
ox::Error updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept;
|
||||
ox::Error updateActiveSubsheet(ox::StringViewCR name, int cols, int rows) noexcept;
|
||||
|
||||
void setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept;
|
||||
|
||||
|
@ -260,7 +260,7 @@ ox::Result<DstType> convertObjToObj(
|
||||
template<typename DstType>
|
||||
ox::Result<DstType> convert(Context &ctx, ox::BufferView const&src) noexcept {
|
||||
OX_REQUIRE(out, convert(ctx, src, ox::ModelTypeName_v<DstType>, ox::ModelTypeVersion_v<DstType>));
|
||||
return std::move(wrapCast<DstType>(out));
|
||||
return std::move(wrapCast<DstType>(*out));
|
||||
}
|
||||
|
||||
template<typename DstType>
|
||||
|
@ -71,9 +71,9 @@ static ox::Error run(
|
||||
}
|
||||
|
||||
ox::Error run(
|
||||
ox::StringView project,
|
||||
ox::StringView appName,
|
||||
ox::StringView projectDataDir,
|
||||
ox::StringView const project,
|
||||
ox::StringView const appName,
|
||||
ox::StringView const projectDataDir,
|
||||
ox::SpanView<ox::CString> args) noexcept {
|
||||
return studio::run(ox::sfmt("{} {}", project, appName), projectDataDir, args);
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <ox/std/def.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
#include <glutils/glutils.hpp>
|
||||
#include <turbine/turbine.hpp>
|
||||
@ -17,6 +19,8 @@
|
||||
#include "font.hpp"
|
||||
#include "studioui.hpp"
|
||||
|
||||
#include <complex>
|
||||
|
||||
#ifdef OX_OS_Darwin
|
||||
#define STUDIO_CTRL "Cmd"
|
||||
#else
|
||||
@ -54,7 +58,7 @@ void registerModule(Module const*mod) noexcept {
|
||||
}
|
||||
|
||||
|
||||
struct StudioConfig {
|
||||
struct StudioConfigV1 {
|
||||
static constexpr auto TypeName = "net.drinkingtea.studio.StudioConfig";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
ox::String projectPath;
|
||||
@ -63,13 +67,65 @@ struct StudioConfig {
|
||||
bool showProjectExplorer = true;
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(StudioConfig)
|
||||
OX_MODEL_BEGIN(StudioConfigV1)
|
||||
OX_MODEL_FIELD_RENAME(activeTabItemName, active_tab_item_name)
|
||||
OX_MODEL_FIELD_RENAME(projectPath, project_path)
|
||||
OX_MODEL_FIELD_RENAME(openFiles, open_files)
|
||||
OX_MODEL_FIELD_RENAME(showProjectExplorer, show_project_explorer)
|
||||
OX_MODEL_END()
|
||||
|
||||
|
||||
struct StudioConfigV2 {
|
||||
static constexpr auto TypeName = "net.drinkingtea.studio.StudioConfig";
|
||||
static constexpr auto TypeVersion = 2;
|
||||
struct ProjectConfig {
|
||||
static constexpr auto TypeName = "net.drinkingtea.studio.ProjectConfig";
|
||||
static constexpr auto TypeVersion = 2;
|
||||
ox::String projectPath;
|
||||
ox::String activeTabItemName;
|
||||
ox::Vector<ox::String> openFiles;
|
||||
};
|
||||
ox::Vector<ProjectConfig> projects;
|
||||
bool showProjectExplorer = true;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr ProjectConfig const *project() const {
|
||||
return projects.empty() ? nullptr : &projects[0];
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr ProjectConfig *project() {
|
||||
return projects.empty() ? nullptr : &projects[0];
|
||||
}
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(StudioConfigV2::ProjectConfig)
|
||||
OX_MODEL_FIELD_RENAME(activeTabItemName, active_tab_item_name)
|
||||
OX_MODEL_FIELD_RENAME(projectPath, project_path)
|
||||
OX_MODEL_FIELD_RENAME(openFiles, open_files)
|
||||
OX_MODEL_END()
|
||||
|
||||
OX_MODEL_BEGIN(StudioConfigV2)
|
||||
OX_MODEL_FIELD(projects)
|
||||
OX_MODEL_FIELD_RENAME(showProjectExplorer, show_project_explorer)
|
||||
OX_MODEL_END()
|
||||
|
||||
static ox::Error convertStudioConfigV1ToStudioConfigV2(
|
||||
keel::Context&,
|
||||
StudioConfigV1 &src,
|
||||
StudioConfigV2 &dst) noexcept {
|
||||
dst.projects.emplace_back(StudioConfigV2::ProjectConfig{
|
||||
.projectPath = std::move(src.projectPath),
|
||||
.activeTabItemName = std::move(src.activeTabItemName),
|
||||
.openFiles = std::move(src.openFiles),
|
||||
});
|
||||
dst.showProjectExplorer = src.showProjectExplorer;
|
||||
return {};
|
||||
}
|
||||
|
||||
using StudioConfig = StudioConfigV2;
|
||||
|
||||
|
||||
StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexcept:
|
||||
m_sctx{*this, ctx},
|
||||
m_tctx{ctx},
|
||||
@ -87,6 +143,9 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
|
||||
// and hopefully it will change at some point.
|
||||
io.Fonts->AddFontFromMemoryTTF(const_cast<uint8_t*>(font.data()), static_cast<int>(font.size()), 13, &fontCfg);
|
||||
}
|
||||
auto &kctx = keelCtx(m_tctx);
|
||||
kctx.converters.emplace_back(keel::Converter::make<convertStudioConfigV1ToStudioConfigV2>());
|
||||
oxLogError(headerizeConfigFile<StudioConfigV1>(kctx));
|
||||
turbine::setApplicationData(m_tctx, &m_sctx);
|
||||
turbine::setShutdownHandler(m_tctx, shutdownHandler);
|
||||
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
|
||||
@ -102,21 +161,14 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
|
||||
m_newMenu.finished.connect(this, &StudioUI::openFile);
|
||||
m_closeAppConfirm.response.connect(this, &StudioUI::handleCloseAppResponse);
|
||||
m_closeFileConfirm.response.connect(this, &StudioUI::handleCloseFileResponse);
|
||||
m_removeRecentProject.dlg.response.connect(this, &StudioUI::handleRemoveRecentProjectResponse);
|
||||
loadModules();
|
||||
// open project and files
|
||||
auto const [config, err] = studio::readConfig<StudioConfig>(keelCtx(m_tctx));
|
||||
m_showProjectExplorer = config.showProjectExplorer;
|
||||
if (!err) {
|
||||
auto const openProjErr = openProjectPath(config.projectPath);
|
||||
if (!openProjErr) {
|
||||
for (auto const&f: config.openFiles) {
|
||||
auto const openFileErr = openFileActiveTab(f, config.activeTabItemName == f);
|
||||
if (openFileErr) {
|
||||
oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr));
|
||||
continue;
|
||||
}
|
||||
m_activeEditor = m_editors.back().value->get();
|
||||
}
|
||||
if (auto const pc = config.project()) {
|
||||
oxLogError(openProjectPath(pc->projectPath));
|
||||
}
|
||||
} else {
|
||||
if constexpr(!ox::defines::Debug) {
|
||||
@ -142,7 +194,7 @@ void StudioUI::navigateTo(ox::StringParam path, ox::StringParam navArgs) noexcep
|
||||
void StudioUI::draw() noexcept {
|
||||
glutils::clearScreen();
|
||||
drawMenu();
|
||||
auto const&viewport = *ImGui::GetMainViewport();
|
||||
auto const &viewport = *ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport.WorkPos);
|
||||
ImGui::SetNextWindowSize(viewport.WorkSize);
|
||||
ImGui::SetNextWindowViewport(viewport.ID);
|
||||
@ -194,6 +246,18 @@ void StudioUI::drawMenu() noexcept {
|
||||
if (ImGui::MenuItem("Open Project...", STUDIO_CTRL "+O")) {
|
||||
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProjectPath));
|
||||
}
|
||||
if (ImGui::BeginMenu("Recent Projects", m_recentProjects.size() > 1)) {
|
||||
for (size_t i = 1; i < m_recentProjects.size(); ++i) {
|
||||
auto const &p = m_recentProjects[i];
|
||||
if (ImGui::MenuItem(p.c_str())) {
|
||||
if (openProjectPath(p)) {
|
||||
m_removeRecentProject.idx = i;
|
||||
m_removeRecentProject.dlg.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::MenuItem(
|
||||
"Save",
|
||||
STUDIO_CTRL "+S",
|
||||
@ -270,7 +334,7 @@ void StudioUI::drawTabBar() noexcept {
|
||||
|
||||
void StudioUI::drawTabs() noexcept {
|
||||
for (auto it = m_editors.begin(); it != m_editors.end();) {
|
||||
auto const&e = *it;
|
||||
auto const &e = *it;
|
||||
auto open = true;
|
||||
auto const unsavedChanges = e->unsavedChanges() ? ImGuiTabItemFlags_UnsavedDocument : 0;
|
||||
auto const selected = m_activeEditorUpdatePending == e.get() ? ImGuiTabItemFlags_SetSelected : 0;
|
||||
@ -279,7 +343,9 @@ void StudioUI::drawTabs() noexcept {
|
||||
if (m_activeEditor != e.get()) [[unlikely]] {
|
||||
m_activeEditor = e.get();
|
||||
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&](StudioConfig &config) {
|
||||
config.activeTabItemName = m_activeEditor->itemPath();
|
||||
if (auto const pc = config.project()) {
|
||||
pc->activeTabItemName = m_activeEditor->itemPath();
|
||||
}
|
||||
});
|
||||
turbine::setRefreshWithin(m_tctx, 0);
|
||||
} else [[likely]] {
|
||||
@ -337,14 +403,14 @@ void StudioUI::drawTabs() noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::loadEditorMaker(EditorMaker const&editorMaker) noexcept {
|
||||
for (auto const&ext : editorMaker.fileTypes) {
|
||||
void StudioUI::loadEditorMaker(EditorMaker const &editorMaker) noexcept {
|
||||
for (auto const &ext : editorMaker.fileTypes) {
|
||||
m_editorMakers[ext] = editorMaker.make;
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::loadModule(Module const&mod) noexcept {
|
||||
for (auto const&editorMaker : mod.editors(m_sctx)) {
|
||||
void StudioUI::loadModule(Module const &mod) noexcept {
|
||||
for (auto const &editorMaker : mod.editors(m_sctx)) {
|
||||
loadEditorMaker(editorMaker);
|
||||
}
|
||||
for (auto &im : mod.itemMakers(m_sctx)) {
|
||||
@ -464,14 +530,16 @@ ox::Error StudioUI::renameFile(ox::StringViewCR path) noexcept {
|
||||
return m_renameFile.openPath(path);
|
||||
}
|
||||
|
||||
ox::Error StudioUI::handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const&) noexcept {
|
||||
ox::Error StudioUI::handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const &) noexcept {
|
||||
for (auto &f : m_openFiles) {
|
||||
if (f == oldPath) {
|
||||
f = newPath;
|
||||
editConfig<StudioConfig>(keelCtx(m_sctx), [&](StudioConfig &cfg) {
|
||||
auto p = find(cfg.openFiles.begin(), cfg.openFiles.end(), oldPath);
|
||||
*p = newPath;
|
||||
cfg.activeTabItemName = newPath;
|
||||
if (auto const pc = cfg.project()) {
|
||||
auto p = find(pc->openFiles.begin(), pc->openFiles.end(), oldPath);
|
||||
*p = newPath;
|
||||
pc->activeTabItemName = newPath;
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -546,8 +614,36 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
|
||||
m_openFiles.clear();
|
||||
m_editors.clear();
|
||||
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&](StudioConfig &config) {
|
||||
config.projectPath = ox::String(m_project->projectPath());
|
||||
config.openFiles.clear();
|
||||
auto const pcIt = std::find_if(
|
||||
config.projects.begin(), config.projects.end(),
|
||||
[this](StudioConfig::ProjectConfig const &pc) {
|
||||
return pc.projectPath == m_project->projectPath();
|
||||
});
|
||||
if (pcIt != config.projects.end()) {
|
||||
auto p = std::move(*pcIt);
|
||||
std::ignore = config.projects.erase(pcIt);
|
||||
auto &pc = *config.projects.emplace(0, std::move(p));
|
||||
for (auto const &f: pc.openFiles) {
|
||||
auto const openFileErr = openFileActiveTab(f, pc.activeTabItemName == f);
|
||||
if (openFileErr) {
|
||||
oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr));
|
||||
continue;
|
||||
}
|
||||
m_activeEditor = m_editors.back().value->get();
|
||||
}
|
||||
} else {
|
||||
config.projects.emplace(0, StudioConfig::ProjectConfig{
|
||||
.projectPath = ox::String{m_project->projectPath()},
|
||||
.activeTabItemName = {},
|
||||
.openFiles = {},
|
||||
});
|
||||
}
|
||||
config.projects.resize(ox::min<size_t>(10, config.projects.size()));
|
||||
m_recentProjects.clear();
|
||||
m_recentProjects.reserve(config.projects.size());
|
||||
for (auto const &p : config.projects) {
|
||||
m_recentProjects.emplace_back(p.projectPath);
|
||||
}
|
||||
});
|
||||
return m_projectExplorer.refreshProjectTreeModel();
|
||||
}
|
||||
@ -593,8 +689,10 @@ ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool const makeActi
|
||||
}
|
||||
// save to config
|
||||
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&path](StudioConfig &config) {
|
||||
if (!config.openFiles.contains(path)) {
|
||||
config.openFiles.emplace_back(path);
|
||||
if (auto const pc = config.project()) {
|
||||
if (!pc->openFiles.contains(path)) {
|
||||
pc->openFiles.emplace_back(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
return {};
|
||||
@ -618,6 +716,22 @@ ox::Error StudioUI::handleCloseFileResponse(ig::PopupResponse const response) no
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error StudioUI::handleRemoveRecentProjectResponse(ig::PopupResponse const response) noexcept {
|
||||
if (response == ig::PopupResponse::OK) {
|
||||
auto const p = std::move(m_recentProjects[m_removeRecentProject.idx]);
|
||||
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&p](StudioConfig &config) {
|
||||
std::ignore = config.projects.erase(
|
||||
std::remove_if(
|
||||
config.projects.begin(), config.projects.end(),
|
||||
[&p](StudioConfig::ProjectConfig const &pc) {
|
||||
return pc.projectPath == p;
|
||||
}));
|
||||
});
|
||||
return m_recentProjects.erase(m_removeRecentProject.idx).error;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error StudioUI::closeCurrentFile() noexcept {
|
||||
for (auto &e : m_editors) {
|
||||
if (m_activeEditor == e.get()) {
|
||||
@ -636,7 +750,9 @@ ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept {
|
||||
std::ignore = m_openFiles.erase(std::remove(m_openFiles.begin(), m_openFiles.end(), path));
|
||||
// save to config
|
||||
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&](StudioConfig &config) {
|
||||
std::ignore = config.openFiles.erase(std::remove(config.openFiles.begin(), config.openFiles.end(), path));
|
||||
if (auto const pc = config.project()) {
|
||||
std::ignore = pc->openFiles.erase(std::remove(pc->openFiles.begin(), pc->openFiles.end(), path));
|
||||
}
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ class StudioUI: public ox::SignalHandler {
|
||||
bool m_closeActiveTab{};
|
||||
ox::Vector<ox::Pair<ox::String>> m_queuedMoves;
|
||||
ox::Vector<ox::Pair<ox::String>> m_queuedDirMoves;
|
||||
ox::Vector<ox::String> m_recentProjects;
|
||||
NewMenu m_newMenu{keelCtx(m_tctx)};
|
||||
AboutPopup m_aboutPopup{m_tctx};
|
||||
DeleteConfirmation m_deleteConfirmation;
|
||||
@ -57,7 +58,11 @@ class StudioUI: public ox::SignalHandler {
|
||||
MakeCopyPopup m_copyFilePopup;
|
||||
RenameFile m_renameFile;
|
||||
NewProject m_newProject{m_projectDataDir};
|
||||
ox::Array<Widget*, 10> const m_widgets {
|
||||
struct {
|
||||
ig::QuestionPopup dlg{"Remove From Recents?", "Unable to load project. Remove from recent projects?"};
|
||||
size_t idx{};
|
||||
} m_removeRecentProject;
|
||||
ox::Array<Widget*, 11> const m_widgets {
|
||||
&m_closeFileConfirm,
|
||||
&m_closeAppConfirm,
|
||||
&m_copyFilePopup,
|
||||
@ -68,6 +73,7 @@ class StudioUI: public ox::SignalHandler {
|
||||
&m_newDirDialog,
|
||||
&m_renameFile,
|
||||
&m_messagePopup,
|
||||
&m_removeRecentProject.dlg,
|
||||
};
|
||||
bool m_showProjectExplorer = true;
|
||||
struct NavAction {
|
||||
@ -100,9 +106,9 @@ class StudioUI: public ox::SignalHandler {
|
||||
|
||||
void drawTabs() noexcept;
|
||||
|
||||
void loadEditorMaker(EditorMaker const&editorMaker) noexcept;
|
||||
void loadEditorMaker(EditorMaker const &editorMaker) noexcept;
|
||||
|
||||
void loadModule(Module const&mod) noexcept;
|
||||
void loadModule(Module const &mod) noexcept;
|
||||
|
||||
void loadModules() noexcept;
|
||||
|
||||
@ -124,7 +130,7 @@ class StudioUI: public ox::SignalHandler {
|
||||
|
||||
ox::Error renameFile(ox::StringViewCR path) noexcept;
|
||||
|
||||
ox::Error handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const&id) noexcept;
|
||||
ox::Error handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const &id) noexcept;
|
||||
|
||||
ox::Error handleDeleteDir(ox::StringViewCR path) noexcept;
|
||||
|
||||
@ -144,6 +150,8 @@ class StudioUI: public ox::SignalHandler {
|
||||
|
||||
ox::Error handleCloseFileResponse(ig::PopupResponse response) noexcept;
|
||||
|
||||
ox::Error handleRemoveRecentProjectResponse(ig::PopupResponse response) noexcept;
|
||||
|
||||
ox::Error closeCurrentFile() noexcept;
|
||||
|
||||
ox::Error closeFile(ox::StringViewCR path) noexcept;
|
||||
|
@ -4,21 +4,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/claw/claw.hpp>
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/model/typenamecatcher.hpp>
|
||||
#include <ox/oc/oc.hpp>
|
||||
#include <ox/std/buffer.hpp>
|
||||
#include <ox/std/defines.hpp>
|
||||
#include <ox/std/fmt.hpp>
|
||||
#include <ox/std/trace.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
#include <ox/std/trace.hpp>
|
||||
|
||||
#include <keel/context.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
namespace detail {
|
||||
inline ox::String slashesToPct(ox::StringView str) noexcept {
|
||||
inline ox::String slashesToPct(ox::StringViewCR str) noexcept {
|
||||
auto out = ox::String{str};
|
||||
for (auto&c: out) {
|
||||
if (c == '/' || c == '\\') {
|
||||
@ -30,78 +30,102 @@ inline ox::String slashesToPct(ox::StringView str) noexcept {
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::String configPath(keel::Context const&ctx) noexcept;
|
||||
ox::String configPath(keel::Context const&kctx) noexcept;
|
||||
|
||||
template<typename T>
|
||||
ox::Result<T> readConfig(keel::Context &ctx, ox::StringViewCR name) noexcept {
|
||||
ox::Result<T> readConfig(keel::Context &kctx, ox::StringViewCR name) noexcept {
|
||||
oxAssert(name != "", "Config type has no TypeName");
|
||||
auto const path = ox::sfmt("/{}.json", detail::slashesToPct(name));
|
||||
ox::PassThroughFS fs(configPath(ctx));
|
||||
ox::PassThroughFS fs(configPath(kctx));
|
||||
auto const [buff, err] = fs.read(path);
|
||||
if (err) {
|
||||
//oxErrf("Could not read config file: {} - {}\n", path, toStr(err));
|
||||
return err;
|
||||
}
|
||||
return ox::readOC<T>(buff);
|
||||
OX_REQUIRE(id, ox::readClawTypeId(buff));
|
||||
ox::Result<T> out;
|
||||
if (id != ox::ModelTypeId_v<T>) {
|
||||
out = keel::convert<T>(kctx, buff);
|
||||
} else {
|
||||
out = ox::readClaw<T>(buff);
|
||||
}
|
||||
OX_RETURN_ERROR(out);
|
||||
OX_RETURN_ERROR(keel::ensureValid(out.value));
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Result<T> readConfig(keel::Context &ctx) noexcept {
|
||||
ox::Result<T> readConfig(keel::Context &kctx) noexcept {
|
||||
constexpr auto TypeName = ox::requireModelTypeName<T>();
|
||||
return readConfig<T>(ctx, TypeName);
|
||||
return readConfig<T>(kctx, TypeName);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Error writeConfig(keel::Context &ctx, ox::StringViewCR name, T const&data) noexcept {
|
||||
ox::Error writeConfig(keel::Context &kctx, ox::StringViewCR name, T const&data) noexcept {
|
||||
oxAssert(name != "", "Config type has no TypeName");
|
||||
auto const path = ox::sfmt("/{}.json", detail::slashesToPct(name));
|
||||
ox::PassThroughFS fs(configPath(ctx));
|
||||
ox::PassThroughFS fs(configPath(kctx));
|
||||
if (auto const err = fs.mkdir("/", true)) {
|
||||
//oxErrf("Could not create config directory: {} - {}\n", path, toStr(err));
|
||||
return err;
|
||||
}
|
||||
OX_REQUIRE_M(buff, ox::writeOC(data));
|
||||
OX_REQUIRE_M(buff, ox::writeClaw(data, ox::ClawFormat::Organic));
|
||||
*buff.back().value = '\n';
|
||||
if (auto const err = fs.write(path, buff.data(), buff.size())) {
|
||||
//oxErrf("Could not read config file: {} - {}\n", path, toStr(err));
|
||||
return ox::Error(2, "Could not read config file");
|
||||
return ox::Error{2, "Could not read config file"};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Error writeConfig(keel::Context &ctx, T const&data) noexcept {
|
||||
ox::Error writeConfig(keel::Context &kctx, T const&data) noexcept {
|
||||
constexpr auto TypeName = ox::requireModelTypeName<T>();
|
||||
return writeConfig(ctx, TypeName, data);
|
||||
return writeConfig(kctx, TypeName, data);
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
void openConfig(keel::Context &ctx, ox::StringViewCR name, Func f) noexcept {
|
||||
void openConfig(keel::Context &kctx, ox::StringViewCR name, Func f) noexcept {
|
||||
oxAssert(name != "", "Config type has no TypeName");
|
||||
auto const [c, err] = readConfig<T>(ctx, name);
|
||||
auto const [c, err] = readConfig<T>(kctx, name);
|
||||
oxLogError(err);
|
||||
f(c);
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
void openConfig(keel::Context &ctx, Func f) noexcept {
|
||||
void openConfig(keel::Context &kctx, Func f) noexcept {
|
||||
constexpr auto TypeName = ox::requireModelTypeName<T>();
|
||||
openConfig<T>(ctx, TypeName, f);
|
||||
openConfig<T>(kctx, TypeName, f);
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
void editConfig(keel::Context &ctx, ox::StringViewCR name, Func f) noexcept {
|
||||
void editConfig(keel::Context &kctx, ox::StringViewCR name, Func f) noexcept {
|
||||
oxAssert(name != "", "Config type has no TypeName");
|
||||
auto [c, err] = readConfig<T>(ctx, name);
|
||||
auto [c, err] = readConfig<T>(kctx, name);
|
||||
oxLogError(err);
|
||||
f(c);
|
||||
oxLogError(writeConfig(ctx, name, c));
|
||||
oxLogError(writeConfig(kctx, name, c));
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
void editConfig(keel::Context &ctx, Func f) noexcept {
|
||||
void editConfig(keel::Context &kctx, Func f) noexcept {
|
||||
constexpr auto TypeName = ox::requireModelTypeName<T>();
|
||||
editConfig<T>(ctx, TypeName, f);
|
||||
editConfig<T>(kctx, TypeName, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Older config files didn't use ClawHeaders, so they can't
|
||||
* use the normal conversion system.
|
||||
* Functions like this shouldn't be necessary moving forward.
|
||||
*/
|
||||
template<typename T>
|
||||
ox::Error headerizeConfigFile(keel::Context &kctx, ox::StringViewCR name = ox::ModelTypeName_v<T>) noexcept {
|
||||
auto const path = ox::sfmt("/{}.json", name);
|
||||
ox::PassThroughFS fs(configPath(kctx));
|
||||
OX_REQUIRE_M(buff, fs.read(path));
|
||||
OX_REQUIRE_M(cv1, ox::readOC<T>(buff));
|
||||
OX_RETURN_ERROR(ox::writeClaw(cv1, ox::ClawFormat::Organic).moveTo(buff));
|
||||
return fs.write(path, buff);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user