[olympic/studio] Add new project menu, make file creation open file
Some checks failed
Build / build (push) Failing after 2m7s

This commit is contained in:
Gary Talent 2023-12-28 23:44:25 -06:00
parent ffbdb09c31
commit 1df4e78084
9 changed files with 228 additions and 27 deletions

View File

@ -5,6 +5,7 @@ add_library(
filedialogmanager.cpp
main.cpp
newmenu.cpp
newproject.cpp
projectexplorer.cpp
projecttreemodel.cpp
studioapp.cpp

View File

@ -115,12 +115,12 @@ void NewMenu::drawLastPageButtons(turbine::Context &ctx) noexcept {
}
void NewMenu::finish(turbine::Context &ctx) noexcept {
auto const err = m_types[static_cast<std::size_t>(m_selectedType)]->write(ctx, m_itemName);
auto const [path, err] = m_types[static_cast<std::size_t>(m_selectedType)]->write(ctx, m_itemName);
if (err) {
oxLogError(err);
return;
}
finished.emit("");
finished.emit(path);
m_stage = Stage::Closed;
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <imgui.h>
#include <studio/imguiuitl.hpp>
#include <utility>
#include "filedialogmanager.hpp"
#include "newproject.hpp"
namespace studio {
NewProject::NewProject(ox::String projectDatadir) noexcept: m_projectDataDir(std::move(projectDatadir)) {
setTitle(ox::String("New Project"));
setSize({230, 140});
}
void NewProject::open() noexcept {
m_stage = Stage::Opening;
m_projectPath = "";
m_projectName = "";
}
void NewProject::close() noexcept {
m_stage = Stage::Closed;
m_open = false;
}
bool NewProject::isOpen() const noexcept {
return m_open;
}
void NewProject::draw(turbine::Context &ctx) noexcept {
switch (m_stage) {
case Stage::Opening:
ImGui::OpenPopup(title().c_str());
m_stage = Stage::NewItemName;
m_open = true;
[[fallthrough]];
case Stage::NewItemName:
drawNewProjectName(ctx);
break;
case Stage::Closed:
m_open = false;
break;
}
}
void NewProject::drawNewProjectName(turbine::Context &ctx) noexcept {
drawWindow(ctx, &m_open, [this, &ctx] {
ImGui::InputText("Name", m_projectName.data(), m_projectName.cap());
ImGui::Text("Path: %s", m_projectPath.c_str());
if (ImGui::Button("Browse")) {
oxLogError(studio::chooseDirectory().moveTo(m_projectPath));
}
drawLastPageButtons(ctx);
});
}
void NewProject::drawFirstPageButtons() noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 130);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 20);
auto const btnSz = ImVec2(60, 20);
if (ImGui::Button("Next", btnSz)) {
m_stage = Stage::NewItemName;
}
ImGui::SameLine();
if (ImGui::Button("Cancel", btnSz)) {
ImGui::CloseCurrentPopup();
m_stage = Stage::Closed;
}
}
void NewProject::drawLastPageButtons(turbine::Context&) noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 95);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 20);
if (ImGui::Button("Finish")) {
finish();
}
ImGui::SameLine();
if (ImGui::Button("Quit")) {
ImGui::CloseCurrentPopup();
m_stage = Stage::Closed;
}
}
void NewProject::finish() noexcept {
auto projectPath = ox::sfmt("{}/{}", m_projectPath, m_projectName);
finished.emit(projectPath);
m_stage = Stage::Closed;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/claw/claw.hpp>
#include <ox/event/signal.hpp>
#include <ox/std/string.hpp>
#include <studio/itemmaker.hpp>
#include <studio/popup.hpp>
namespace studio {
class NewProject: public studio::Popup {
public:
enum class Stage {
Closed,
Opening,
NewItemName,
};
// emits path parameter
ox::Signal<ox::Error(ox::StringView)> finished;
private:
Stage m_stage = Stage::Closed;
ox::String const m_projectDataDir;
ox::String m_projectPath;
ox::BString<255> m_projectName;
ox::Vector<ox::UniquePtr<studio::ItemMaker>> m_types;
bool m_open = false;
public:
NewProject(ox::String projectDatadir) noexcept;
void open() noexcept override;
void close() noexcept override;
[[nodiscard]]
bool isOpen() const noexcept override;
void draw(turbine::Context &ctx) noexcept override;
private:
void drawNewProjectName(turbine::Context &ctx) noexcept;
void drawFirstPageButtons() noexcept;
void drawLastPageButtons(turbine::Context &ctx) noexcept;
void finish() noexcept;
};
}

View File

@ -2,6 +2,8 @@
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <filesystem>
#include <imgui.h>
#include <keel/media.hpp>
@ -42,15 +44,18 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringView projectDataDir) noexcep
m_ctx(ctx),
m_projectDataDir(projectDataDir),
m_projectExplorer(m_ctx),
m_newProject(ox::String(projectDataDir)),
m_aboutPopup(m_ctx) {
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
m_newProject.finished.connect(this, &StudioUI::createOpenProject);
m_newMenu.finished.connect(this, &StudioUI::openFile);
ImGui::GetIO().IniFilename = nullptr;
loadModules();
// open project and files
auto const [config, err] = studio::readConfig<StudioConfig>(keelCtx(m_ctx));
m_showProjectExplorer = config.showProjectExplorer;
if (!err) {
auto const openProjErr = openProject(config.projectPath);
auto const openProjErr = openProjectPath(config.projectPath);
if (!openProjErr) {
for (auto const&f: config.openFiles) {
auto openFileErr = openFileActiveTab(f, config.activeTabItemName == f);
@ -95,10 +100,14 @@ void StudioUI::handleKeyEvent(turbine::Key key, bool down) noexcept {
}
break;
case turbine::Key::Alpha_N:
m_newMenu.open();
if (turbine::buttonDown(m_ctx, turbine::Key::Mod_Shift)) {
m_newProject.open();
} else {
m_newMenu.open();
}
break;
case turbine::Key::Alpha_O:
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProject));
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProjectPath));
break;
case turbine::Key::Alpha_Q:
turbine::requestShutdown(m_ctx);
@ -168,8 +177,11 @@ void StudioUI::drawMenu() noexcept {
if (ImGui::MenuItem("New...", "Ctrl+N")) {
m_newMenu.open();
}
if (ImGui::MenuItem("New Project...", "Ctrl+Shift+N")) {
m_newProject.open();
}
if (ImGui::MenuItem("Open Project...", "Ctrl+O")) {
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProject));
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProjectPath));
}
if (ImGui::MenuItem("Save", "Ctrl+S", false, m_activeEditor && m_activeEditor->unsavedChanges())) {
m_activeEditor->save();
@ -317,19 +329,27 @@ void StudioUI::save() noexcept {
}
}
ox::Error StudioUI::openProject(ox::CRStringView path) noexcept {
ox::Error StudioUI::createOpenProject(ox::CRStringView path) noexcept {
std::error_code ec;
std::filesystem::create_directories(toStdStringView(path), ec);
oxReturnError(OxError(ec.value() != 0, "Could not create project directory"));
oxReturnError(openProjectPath(path));
return m_project->writeAllTypeDescriptors();
}
ox::Error StudioUI::openProjectPath(ox::CRStringView path) noexcept {
oxRequireM(fs, keel::loadRomFs(path));
oxReturnError(keel::setRomFs(keelCtx(m_ctx), std::move(fs)));
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);
auto sctx = applicationData<studio::StudioContext>(m_ctx);
auto const sctx = applicationData<studio::StudioContext>(m_ctx);
sctx->project = m_project.get();
turbine::setWindowTitle(m_ctx, ox::sfmt("{} - {}", keelCtx(m_ctx).appName, m_project->projectPath()));
m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
m_project->fileDeleted.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
m_openFiles.clear();
m_editors.clear();
studio::editConfig<StudioConfig>(keelCtx(m_ctx), [&](StudioConfig *config) {
config->projectPath = ox::String(path);
config->projectPath = ox::String(m_project->projectPath());
config->openFiles.clear();
});
return m_projectExplorer.refreshProjectTreeModel();

View File

@ -14,6 +14,7 @@
#include <studio/task.hpp>
#include "aboutpopup.hpp"
#include "newmenu.hpp"
#include "newproject.hpp"
#include "projectexplorer.hpp"
#include "projecttreemodel.hpp"
@ -36,9 +37,11 @@ class StudioUI: public ox::SignalHandler {
studio::BaseEditor *m_activeEditor = nullptr;
studio::BaseEditor *m_activeEditorUpdatePending = nullptr;
NewMenu m_newMenu;
NewProject m_newProject;
AboutPopup m_aboutPopup;
ox::Array<studio::Popup*, 2> const m_popups = {
&m_newMenu,
&m_newProject,
&m_aboutPopup
};
bool m_showProjectExplorer = true;
@ -79,7 +82,9 @@ class StudioUI: public ox::SignalHandler {
void save() noexcept;
ox::Error openProject(ox::CRStringView path) noexcept;
ox::Error createOpenProject(ox::CRStringView path) noexcept;
ox::Error openProjectPath(ox::CRStringView path) noexcept;
ox::Error openFile(ox::CRStringView path) noexcept;

View File

@ -24,7 +24,14 @@ class ItemMaker {
fileExt(pFileExt) {
}
virtual ~ItemMaker() noexcept = default;
virtual ox::Error write(turbine::Context &ctx, ox::CRStringView pName) const noexcept = 0;
/**
* Returns path of the file created.
* @param ctx
* @param pName
* @return path of file or error in Result
*/
virtual ox::Result<ox::String> write(turbine::Context &ctx, ox::CRStringView pName) const noexcept = 0;
};
template<typename T>
@ -61,11 +68,12 @@ class ItemMakerT: public ItemMaker {
m_item(std::move(pItem)),
m_fmt(pFmt) {
}
ox::Error write(turbine::Context &ctx, ox::CRStringView pName) const noexcept override {
ox::Result<ox::String> write(turbine::Context &ctx, ox::CRStringView pName) const noexcept override {
auto const path = ox::sfmt("/{}/{}.{}", parentDir, pName, fileExt);
auto sctx = turbine::applicationData<studio::StudioContext>(ctx);
keel::createUuidMapping(keelCtx(ctx), path, ox::UUID::generate().unwrap());
return sctx->project->writeObj(path, m_item, m_fmt);
oxReturnError(sctx->project->writeObj(path, m_item, m_fmt));
return path;
}
};

View File

@ -50,6 +50,7 @@ class Project {
keel::Context &m_ctx;
ox::String m_path;
ox::String m_projectDataDir;
ox::String m_typeDescPath;
mutable keel::TypeStore m_typeStore;
ox::FileSystem &m_fs;
ox::HashMap<ox::String, ox::Vector<ox::String>> m_fileExtFileMap;
@ -59,6 +60,9 @@ class Project {
ox::Error create() noexcept;
[[nodiscard]]
ox::String const&projectPath() const noexcept;
[[nodiscard]]
ox::FileSystem *romFs() noexcept;
@ -87,6 +91,8 @@ class Project {
[[nodiscard]]
ox::Vector<ox::String> const&fileList(ox::CRStringView ext) noexcept;
ox::Error writeAllTypeDescriptors() noexcept;
private:
void buildFileIndex() noexcept;
@ -123,21 +129,10 @@ ox::Error Project::writeObj(ox::CRStringView path, T const&obj, ox::ClawFormat f
if (m_typeStore.get<T>().error) {
oxReturnError(ox::buildTypeDef(&m_typeStore, &obj));
}
// write out type store
auto const descPath = ox::sfmt("/{}/type_descriptors", m_projectDataDir);
oxRequire(desc, m_typeStore.get<T>());
auto const descExists = m_fs.stat(ox::sfmt("{}/{}", descPath, buildTypeId(*desc))).error != 0;
auto const descExists = m_fs.stat(ox::sfmt("{}/{}", m_typeDescPath, buildTypeId(*desc))).error != 0;
if (!descExists || ox::defines::Debug) {
// write all descriptors because we don't know which types T depends on
oxReturnError(mkdir(descPath));
for (auto const &t: m_typeStore.typeList()) {
oxRequireM(typeOut, ox::writeClaw(*t, ox::ClawFormat::Organic));
// replace garbage last character with new line
*typeOut.back().value = '\n';
// write to FS
auto const typePath = ox::sfmt("/{}/{}", descPath, buildTypeId(*t));
oxReturnError(writeBuff(typePath, typeOut));
}
oxReturnError(writeAllTypeDescriptors());
}
oxReturnError(keel::setAsset(m_ctx, path, obj));
fileUpdated.emit(path);

View File

@ -30,6 +30,7 @@ Project::Project(keel::Context &ctx, ox::String path, ox::CRStringView projectDa
m_ctx(ctx),
m_path(std::move(path)),
m_projectDataDir(projectDataDir),
m_typeDescPath(ox::sfmt("/{}/type_descriptors", m_projectDataDir)),
m_typeStore(*m_ctx.rom, ox::sfmt("/{}/type_descriptors", projectDataDir)),
m_fs(*m_ctx.rom) {
oxTracef("studio", "Project: {}", m_path);
@ -43,6 +44,10 @@ ox::Error Project::create() noexcept {
return OxError(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: mkdir failed");
}
ox::String const&Project::projectPath() const noexcept {
return m_path;
}
ox::FileSystem *Project::romFs() noexcept {
return &m_fs;
}
@ -65,6 +70,20 @@ ox::Vector<ox::String> const&Project::fileList(ox::CRStringView ext) noexcept {
return m_fileExtFileMap[ext];
}
ox::Error Project::writeAllTypeDescriptors() noexcept {
// write all descriptors because we don't know which types T depends on
oxReturnError(mkdir(m_typeDescPath));
for (auto const &t: m_typeStore.typeList()) {
oxRequireM(typeOut, ox::writeClaw(*t, ox::ClawFormat::Organic));
// replace garbage last character with new line
*typeOut.back().value = '\n';
// write to FS
auto const typePath = ox::sfmt("{}/{}", m_typeDescPath, buildTypeId(*t));
oxReturnError(writeBuff(typePath, typeOut));
}
return {};
}
void Project::buildFileIndex() noexcept {
auto [files, err] = listFiles();
if (err) {