[nostalgia] Update Studio to handle tabs and open directory dialog on Mac, Update core::init
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include <nostalgia/core/core.hpp>
|
||||
|
||||
#include "lib/configio.hpp"
|
||||
#include "builtinmodules.hpp"
|
||||
#include "filedialogmanager.hpp"
|
||||
#include "studioapp.hpp"
|
||||
|
||||
@@ -20,58 +21,62 @@ struct StudioConfig {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.studio.StudioConfig";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
ox::String projectPath;
|
||||
ox::Vector<ox::String> openFiles;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
constexpr ox::Error model(T *h, StudioConfig *config) noexcept {
|
||||
h->template setTypeInfo<StudioConfig>();
|
||||
oxReturnError(h->field("project_path", &config->projectPath));
|
||||
oxReturnError(h->field("open_files", &config->openFiles));
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
StudioUI::StudioUI() noexcept {
|
||||
m_widgets.push_back(&m_projectExplorer);
|
||||
if (const auto [config, err] = studio::readConfig<StudioConfig>(); !err) {
|
||||
StudioUI::StudioUI(core::Context *ctx) noexcept {
|
||||
m_ctx = ctx;
|
||||
m_projectExplorer = new ProjectExplorer(m_ctx);
|
||||
m_widgets.emplace_back(m_projectExplorer);
|
||||
m_projectExplorer->fileChosen.connect(this, &StudioUI::openFile);
|
||||
loadModules();
|
||||
// open project and files
|
||||
const auto [config, err] = studio::readConfig<StudioConfig>(ctx);
|
||||
if (!err) {
|
||||
oxIgnoreError(openProject(config.projectPath));
|
||||
for (const auto &f : config.openFiles) {
|
||||
oxLogError(openFile(f));
|
||||
}
|
||||
} else {
|
||||
oxErrf("Could not open studio config file: {}\n", err.msg);
|
||||
}
|
||||
}
|
||||
|
||||
static ox::Result<ox::UniquePtr<ProjectTreeModel>>
|
||||
buildProjectTreeModel(const ox::String &name, const ox::String &path, ox::FileSystem *fs) noexcept {
|
||||
ox::Vector<ox::UniquePtr<ProjectTreeModel>> outChildren;
|
||||
oxRequire(stat, fs->stat(path.c_str()));
|
||||
if (stat.fileType == ox::FileType::Directory) {
|
||||
oxRequireM(children, fs->ls(path));
|
||||
//std::sort(children.begin(), children.end());
|
||||
for (const auto &childName : children) {
|
||||
if (childName[0] != '.') {
|
||||
const auto childPath = ox::sfmt("{}/{}", path, childName);
|
||||
oxRequireM(child, buildProjectTreeModel(childName, childPath, fs));
|
||||
outChildren.emplace_back(std::move(child));
|
||||
}
|
||||
if (err.msg) {
|
||||
oxErrf("Could not open studio config file: {}\n", err.msg);
|
||||
} else {
|
||||
oxErrf("Could not open studio config file: {} ({}:{})\n", err.errCode, err.file, err.line);
|
||||
}
|
||||
}
|
||||
return ox::make_unique<ProjectTreeModel>(name, std::move(outChildren));
|
||||
}
|
||||
|
||||
void StudioUI::update(core::Context *ctx) noexcept {
|
||||
m_taskRunner.update(ctx);
|
||||
void StudioUI::update() noexcept {
|
||||
m_taskRunner.update(m_ctx);
|
||||
}
|
||||
|
||||
void StudioUI::draw(core::Context *ctx) noexcept {
|
||||
drawMenu(ctx);
|
||||
drawToolbar(ctx);
|
||||
void StudioUI::draw() noexcept {
|
||||
drawMenu();
|
||||
drawToolbar();
|
||||
drawTabs();
|
||||
for (auto &w : m_widgets) {
|
||||
w->draw(ctx);
|
||||
w->draw(m_ctx);
|
||||
}
|
||||
constexpr auto aboutFlags = ImGuiWindowFlags_NoCollapse
|
||||
| ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_Modal;
|
||||
if (m_aboutEnabled &&
|
||||
ImGui::Begin("About", &m_aboutEnabled, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize)) {
|
||||
ImGui::Begin("About", &m_aboutEnabled, aboutFlags)) {
|
||||
ImGui::SetWindowSize(ImVec2(400, 250));
|
||||
ImGui::Text("Nostalgia Studio - dev build");
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::drawMenu(core::Context *ctx) noexcept {
|
||||
void StudioUI::drawMenu() noexcept {
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("New...", "Ctrl+N")) {
|
||||
@@ -82,7 +87,7 @@ void StudioUI::drawMenu(core::Context *ctx) noexcept {
|
||||
if (ImGui::MenuItem("Save", "Ctrl+S", false, m_saveEnabled)) {
|
||||
}
|
||||
if (ImGui::MenuItem("Quit", "Ctrl+Q")) {
|
||||
core::shutdown(ctx);
|
||||
core::shutdown(m_ctx);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
@@ -99,45 +104,134 @@ void StudioUI::drawMenu(core::Context *ctx) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::drawToolbar(core::Context*) noexcept {
|
||||
void StudioUI::drawToolbar() noexcept {
|
||||
constexpr auto BtnWidth = 96;
|
||||
constexpr auto BtnHeight = 32;
|
||||
const auto viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + 20));
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, 48));
|
||||
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + 1, viewport->Pos.y + 21));
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x - 1, 48));
|
||||
const auto flags = ImGuiWindowFlags_NoTitleBar
|
||||
| ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoMove
|
||||
| ImGuiWindowFlags_NoScrollbar
|
||||
| ImGuiWindowFlags_NoSavedSettings;
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
|
||||
ImGui::Begin("MainToolbar", nullptr, flags);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::Begin("MainToolbar##MainWindow", nullptr, flags);
|
||||
{
|
||||
ImGui::Button("New##MainToolbar", ImVec2(BtnWidth, BtnHeight));
|
||||
ImGui::Button("New##MainToolbar##MainWindow", ImVec2(BtnWidth, BtnHeight));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Open Project##MainToolbar", ImVec2(BtnWidth, BtnHeight))) {
|
||||
if (ImGui::Button("Open Project##MainToolbar##MainWindow", ImVec2(BtnWidth, BtnHeight))) {
|
||||
m_taskRunner.add(new FileDialogManager(this, &StudioUI::openProject));
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Button("Save##MainToolbar", ImVec2(BtnWidth, BtnHeight));
|
||||
ImGui::Button("Save##MainToolbar##MainWindow", ImVec2(BtnWidth, BtnHeight));
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
ox::Error StudioUI::openProject(const ox::String &path) noexcept {
|
||||
m_project = ox::make_unique<studio::Project>(path);
|
||||
m_project->fileAdded.connect(this, &StudioUI::refreshProjectTreeModel);
|
||||
m_project->fileDeleted.connect(this, &StudioUI::refreshProjectTreeModel);
|
||||
oxReturnError(studio::editConfig<StudioConfig>([path](StudioConfig *config) {
|
||||
config->projectPath = path;
|
||||
}));
|
||||
return refreshProjectTreeModel();
|
||||
void StudioUI::drawTabs() noexcept {
|
||||
const auto viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + 316, viewport->Pos.y + 71));
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x - 316, viewport->Size.y - 71));
|
||||
constexpr auto windowFlags = ImGuiWindowFlags_NoTitleBar
|
||||
| ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoMove
|
||||
| ImGuiWindowFlags_NoScrollbar
|
||||
| ImGuiWindowFlags_NoSavedSettings;
|
||||
ImGui::Begin("TabWindow##MainWindow", nullptr, windowFlags);
|
||||
constexpr auto tabBarFlags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_TabListPopupButton;
|
||||
if (ImGui::BeginTabBar("TabBar##TabWindow##MainWindow", tabBarFlags)) {
|
||||
for (auto &e : m_editors) {
|
||||
bool open = true;
|
||||
if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open)) {
|
||||
e->draw(m_ctx);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (!open) {
|
||||
e->close();
|
||||
try {
|
||||
oxThrowError(m_editors.erase(e));
|
||||
} catch (const std::exception &e) {
|
||||
oxErrf("Editor tab deletion failed: {}", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
ox::Error StudioUI::refreshProjectTreeModel(const ox::String&) noexcept {
|
||||
oxRequireM(model, buildProjectTreeModel("Project", "/", m_project->romFs()));
|
||||
m_projectExplorer.setModel(std::move(model));
|
||||
void StudioUI::loadEditorMaker(const studio::EditorMaker &editorMaker) noexcept {
|
||||
for (auto &ext : editorMaker.fileTypes) {
|
||||
m_editorMakers[ext] = editorMaker.make;
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::loadModule(studio::Module *module) noexcept {
|
||||
for (auto &editorMaker : module->editors(m_ctx)) {
|
||||
loadEditorMaker(editorMaker);
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::loadModules() noexcept {
|
||||
//for (auto &moduleMaker : BuiltinModules) {
|
||||
// const auto module = moduleMaker();
|
||||
// loadModule(module.get());
|
||||
//}
|
||||
}
|
||||
|
||||
ox::Error StudioUI::openProject(const ox::String &path) noexcept {
|
||||
oxRequireM(fs, core::loadRomFs(path.c_str()));
|
||||
m_ctx->rom = std::move(fs);
|
||||
core::setWindowTitle(m_ctx, ox::sfmt("Nostalgia Studio - {}", path).c_str());
|
||||
m_project = ox::make_unique<studio::Project>(m_ctx->rom.get(), path);
|
||||
m_project->fileAdded.connect(m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
|
||||
m_project->fileDeleted.connect(m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
|
||||
studio::editConfig<StudioConfig>(m_ctx, [&](StudioConfig *config) {
|
||||
config->projectPath = path;
|
||||
});
|
||||
return m_projectExplorer->refreshProjectTreeModel();
|
||||
}
|
||||
|
||||
ox::Error StudioUI::openFile(const ox::String &path) noexcept {
|
||||
if (m_openFiles.contains(path)) {
|
||||
return OxError(0);
|
||||
}
|
||||
// find file extension
|
||||
const auto extStart = std::find(path.crbegin(), path.crend(), '.').offset();
|
||||
if (!extStart) {
|
||||
return OxError(1, "Cannot open a file without valid extension.");
|
||||
}
|
||||
const auto ext = path.substr(extStart + 1);
|
||||
// create Editor
|
||||
if (!m_editorMakers.contains(ext)) {
|
||||
return OxError(1, "There is no editor for this file extension");
|
||||
}
|
||||
try {
|
||||
auto editor = m_editorMakers[ext](path);
|
||||
editor->closed.connect(this, &StudioUI::closeFile);
|
||||
m_editors.emplace_back(editor);
|
||||
} catch (const ox::Exception &ex) {
|
||||
return ex.toError();
|
||||
}
|
||||
m_openFiles.emplace_back(path);
|
||||
// save to config
|
||||
studio::editConfig<StudioConfig>(m_ctx, [&](StudioConfig *config) {
|
||||
if (!config->openFiles.contains((path))) {
|
||||
config->openFiles.emplace_back(path);
|
||||
}
|
||||
});
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
ox::Error StudioUI::closeFile(const ox::String &path) noexcept {
|
||||
if (!m_openFiles.contains(path)) {
|
||||
return OxError(0);
|
||||
}
|
||||
oxIgnoreError(m_openFiles.erase(std::remove(m_openFiles.begin(), m_openFiles.end(), path)));
|
||||
// save to config
|
||||
studio::editConfig<StudioConfig>(m_ctx, [&](StudioConfig *config) {
|
||||
oxIgnoreError(config->openFiles.erase(std::remove(config->openFiles.begin(), config->openFiles.end(), path)));
|
||||
});
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user