|
|
|
@@ -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,9 +343,11 @@ 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);
|
|
|
|
|
turbine::requireRefreshWithin(m_tctx, 0);
|
|
|
|
|
} else [[likely]] {
|
|
|
|
|
if (m_activeEditorUpdatePending == e.get()) [[unlikely]] {
|
|
|
|
|
m_activeEditorUpdatePending = nullptr;
|
|
|
|
@@ -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 {};
|
|
|
|
|
}
|
|
|
|
|