[nostalgia] Update Studio to handle tabs and open directory dialog on Mac, Update core::init

This commit is contained in:
Gary Talent 2021-10-31 13:31:12 -05:00
parent e29f65f351
commit ad743565b2
28 changed files with 416 additions and 155 deletions

View File

@ -107,6 +107,22 @@ constexpr float bluef(Color16 c) noexcept {
}
[[nodiscard]]
constexpr float redf(Color32 c) noexcept {
return static_cast<float>(red32(c)) / 255.f;
}
[[nodiscard]]
constexpr float greenf(Color32 c) noexcept {
return static_cast<float>(green32(c)) / 255.f;
}
[[nodiscard]]
constexpr float bluef(Color32 c) noexcept {
return static_cast<float>(blue32(c)) / 255.f;
}
[[nodiscard]]
constexpr Color16 color16(uint8_t r, uint8_t g, uint8_t b) noexcept {
return r | (g << 5) | (b << 10);

View File

@ -10,6 +10,8 @@
#include <ox/fs/fs.hpp>
#include "assetmanager.hpp"
namespace nostalgia::core {
class Context;
@ -23,15 +25,19 @@ class Drawer {
// User Input Output
class Context {
public:
ox::FileSystem *rom = nullptr;
ox::UniquePtr<ox::FileSystem> rom;
ox::Vector<Drawer*, 5> drawers;
const char *appName = "Nostalgia";
#ifndef OX_BARE_METAL
AssetManager assetManager;
#endif
private:
void *m_customData = nullptr;
void *m_windowerData = nullptr;
void *m_rendererData = nullptr;
public:
constexpr Context() noexcept = default;
Context() noexcept = default;
Context(Context &other) noexcept = delete;
Context(const Context &other) noexcept = delete;

View File

@ -10,6 +10,7 @@
#include <ox/fs/fs.hpp>
#include "assetmanager.hpp"
#include "clipboard.hpp"
#include "consts.hpp"
#include "gfx.hpp"
@ -20,7 +21,7 @@ namespace nostalgia::core {
using event_handler = int(*)(Context*);
ox::Result<ox::UniquePtr<Context>> init(ox::FileSystem *fs) noexcept;
ox::Result<ox::UniquePtr<Context>> init(ox::UniquePtr<ox::FileSystem> fs, const char *appName = "Nostalgia") noexcept;
ox::Error run(Context *ctx) noexcept;

View File

@ -40,9 +40,10 @@ static void initTimer() noexcept {
REG_IE = REG_IE | Int_timer0;
}
ox::Result<ox::UniquePtr<Context>> init(ox::FileSystem *fs) noexcept {
ox::Result<ox::UniquePtr<Context>> init(ox::UniquePtr<ox::FileSystem> fs, const char *appName) noexcept {
auto ctx = ox::make_unique<Context>();
ctx->rom = fs;
ctx->rom = std::move(fs);
ctx->appName = std::move(appName);
oxReturnError(initGfx(ctx.get()));
initTimer();
initIrq();

View File

@ -162,7 +162,8 @@ ox::Error initConsole(Context *ctx) noexcept {
ctx = new (ox_alloca(sizeof(Context))) Context();
oxRequire(rom, loadRom());
ox::FileStore32 fs(rom, 32 * ox::units::MB);
ctx->rom = new (ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
auto romFs = new (ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
new (&ctx->rom) ox::UniquePtr<ox::FileSystem>(romFs);
}
return loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr);
}

View File

@ -32,8 +32,8 @@ static int eventHandler(core::Context *ctx) noexcept {
return 16;
}
ox::Error run(ox::FileSystem *fs) noexcept {
oxRequireM(ctx, core::init(fs));
ox::Error run(ox::UniquePtr<ox::FileSystem> fs) noexcept {
oxRequireM(ctx, core::init(std::move(fs)));
constexpr auto TileSheetAddr = "/TileSheets/Charset.ng";
constexpr auto PaletteAddr = "/Palettes/Charset.npal";
oxReturnError(core::loadSpriteTileSheet(ctx.get(), 0, TileSheetAddr, PaletteAddr));

View File

@ -6,4 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class ox::Error run(class ox::FileSystem *fs) noexcept;
#include <ox/std/memory.hpp>
class ox::Error run(ox::UniquePtr<class ox::FileSystem> fs) noexcept;

View File

@ -17,8 +17,8 @@ static ox::Error run(int argc, const char **argv) noexcept {
return OxError(1);
}
const auto path = argv[1];
oxRequire(fs, nostalgia::core::loadRomFs(path));
return run(fs.get());
oxRequireM(fs, nostalgia::core::loadRomFs(path));
return run(std::move(fs));
}
int main(int argc, const char **argv) {

View File

@ -13,6 +13,7 @@ target_link_libraries(
nostalgia-studio
OxClArgs
OxFS
NostalgiaCore-Studio
NostalgiaCore-Userspace
NostalgiaStudio
NostalgiaPack

View File

@ -0,0 +1,24 @@
/*
* Copyright 2016 - 2021 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <ox/std/memory.hpp>
#include <nostalgia/core/studio/module.hpp>
namespace nostalgia {
//[[maybe_unused]] // GCC warns about the existence of this "unused" constexpr list in a header file...
//constexpr auto BuiltinModules = {
// [] {
// return ox::make_unique<core::Module>();
// },
//};
}

View File

@ -11,15 +11,20 @@ add_library(
NostalgiaStudio
configio.cpp
editor.cpp
module.cpp
project.cpp
task.cpp
undostack.cpp
widget.cpp
window.cpp
filedialog_gtk.cpp
$<$<BOOL:${APPLE}>:filedialog.mm>
$<$<BOOL:${APPLE}>:filedialog_mac.mm>
)
if(NOT MSVC)
target_compile_options(NostalgiaStudio PUBLIC -Wsign-conversion)
endif()
install(TARGETS NostalgiaStudio
LIBRARY DESTINATION ${NOSTALGIA_DIST_LIB}/nostalgia)
@ -46,11 +51,15 @@ target_link_libraries(
install(
FILES
configio.hpp
editor.hpp
filedialog.hpp
filedialog.hpp
module.hpp
project.hpp
task.hpp
undostack.hpp
widget.hpp
window.hpp
${CMAKE_CURRENT_BINARY_DIR}/nostalgiastudio_export.h
DESTINATION
include/nostalgia/studio/lib

View File

@ -19,18 +19,20 @@
#include <ox/std/trace.hpp>
#include <ox/std/string.hpp>
#include <nostalgia/core/context.hpp>
namespace nostalgia::studio {
constexpr auto ConfigDir = [] {
switch (ox::defines::OS) {
case ox::defines::OS::Darwin:
return "{}/Library/Preferences/NostalgiaStudio";
return "{}/Library/Preferences/{}";
case ox::defines::OS::DragonFlyBSD:
case ox::defines::OS::FreeBSD:
case ox::defines::OS::Linux:
case ox::defines::OS::NetBSD:
case ox::defines::OS::OpenBSD:
return "{}/.config/NostalgiaStudio";
return "{}/.config/{}";
case ox::defines::OS::BareMetal:
case ox::defines::OS::Windows:
return "";
@ -38,13 +40,14 @@ constexpr auto ConfigDir = [] {
}();
template<typename T>
ox::Result<T> readConfig(const ox::String &name = ox::getModelTypeName<T>()) noexcept {
ox::Result<T> readConfig(core::Context *ctx, const ox::String &name = ox::getModelTypeName<T>()) noexcept {
oxAssert(name != "", "Config type has no TypeName");
const auto homeDir = std::getenv("HOME");
const auto configPath = ox::sfmt(ConfigDir, homeDir).toStdString();
const auto configPath = ox::sfmt(ConfigDir, homeDir, ctx->appName).toStdString();
const auto path = ox::sfmt("{}/{}.json", configPath, name).toStdString();
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file.good()) {
oxErrorf("Could not find config file: {}", path);
oxErrf("Could not find config file: {}\n", path);
return OxError(1, "Could not find config file");
}
try {
@ -54,15 +57,16 @@ ox::Result<T> readConfig(const ox::String &name = ox::getModelTypeName<T>()) noe
file.read(buff.data(), size);
return ox::readOC<T>(buff);
} catch (const std::ios_base::failure &e) {
oxErrorf("Could not read config file: {}", e.what());
oxErrf("Could not read config file: {}\n", e.what());
return OxError(2, "Could not read config file");
}
}
template<typename T>
ox::Error writeConfig(const ox::String &name, T *data) noexcept {
ox::Error writeConfig(core::Context *ctx, const ox::String &name, T *data) noexcept {
oxAssert(name != "", "Config type has no TypeName");
const auto homeDir = std::getenv("HOME");
const auto configPath = ox::sfmt(ConfigDir, homeDir).toStdString();
const auto configPath = ox::sfmt(ConfigDir, homeDir, ctx->appName).toStdString();
const auto path = ox::sfmt("{}/{}.json", configPath, name).toStdString();
std::error_code ec;
std::filesystem::create_directory(configPath, ec);
@ -71,7 +75,7 @@ ox::Error writeConfig(const ox::String &name, T *data) noexcept {
}
std::ofstream file(path, std::ios::binary | std::ios::ate);
if (!file.good()) {
oxErrorf("Could not find config file: {}", path);
oxErrf("Could not find config file: {}\n", path);
return OxError(1, "Could not find config file");
}
oxRequireM(buff, ox::writeOC(data));
@ -80,26 +84,42 @@ ox::Error writeConfig(const ox::String &name, T *data) noexcept {
file.write(buff.data(), static_cast<ox::Signed<decltype(buff.size())>>(buff.size()));
return OxError(0);
} catch (const std::ios_base::failure &e) {
oxErrorf("Could not read config file: {}", e.what());
oxErrf("Could not read config file: {}\n", e.what());
return OxError(2, "Could not read config file");
}
}
template<typename T>
ox::Error writeConfig(T *data) noexcept {
return writeConfig(ox::getModelTypeName<T>(), data);
ox::Error writeConfig(core::Context *ctx, T *data) noexcept {
const auto TypeName = ox::getModelTypeName<T>();
return writeConfig(ctx, TypeName, data);
}
template<typename T, typename Func>
ox::Error editConfig(const ox::String &name, Func f) noexcept {
auto c = readConfig<T>(name);
void openConfig(core::Context *ctx, const ox::String &name, Func f) noexcept {
oxAssert(name != "", "Config type has no TypeName");
const auto c = readConfig<T>(ctx, name);
f(&c.value);
return writeConfig(name, &c.value);
}
template<typename T, typename Func>
ox::Error editConfig(Func f) noexcept {
return editConfig<T>(ox::getModelTypeName<T>(), f);
void openConfig(core::Context *ctx, Func f) noexcept {
const auto TypeName = ox::getModelTypeName<T>();
openConfig<T>(ctx, TypeName, f);
}
template<typename T, typename Func>
void editConfig(core::Context *ctx, const ox::String &name, Func f) noexcept {
oxAssert(name != "", "Config type has no TypeName");
auto c = readConfig<T>(ctx, name);
f(&c.value);
oxLogError(writeConfig(ctx, name, &c.value));
}
template<typename T, typename Func>
void editConfig(core::Context *ctx, Func f) noexcept {
const auto TypeName = ox::getModelTypeName<T>();
editConfig<T>(ctx, TypeName, f);
}
}

View File

@ -12,6 +12,10 @@
namespace nostalgia::studio {
ox::String Editor::itemDisplayName() const {
return itemName();
}
void Editor::cut() {
}
@ -24,9 +28,13 @@ void Editor::paste() {
void Editor::exportFile() {
}
void Editor::close() {
this->closed.emit(itemName());
}
void Editor::save() {
saveItem();
setUnsavedChanges(false);
saveItem();
setUnsavedChanges(false);
}
void Editor::setUnsavedChanges(bool uc) {

View File

@ -11,12 +11,13 @@
#include <ox/event/signal.hpp>
#include "undostack.hpp"
#include "widget.hpp"
#include "nostalgiastudio_export.h"
namespace nostalgia::studio {
class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler {
class NOSTALGIASTUDIO_EXPORT Editor: public Widget {
private:
UndoStack m_cmdStack;
@ -35,6 +36,8 @@ class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler {
[[nodiscard]]
virtual ox::String itemName() const = 0;
virtual ox::String itemDisplayName() const;
virtual void cut();
virtual void copy();
@ -43,6 +46,8 @@ class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler {
virtual void exportFile();
void close();
/**
* Save changes to item being edited.
*/
@ -89,12 +94,14 @@ class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler {
*/
virtual void saveItem();
// slots
public:
ox::Signal<ox::Error(bool)> unsavedChangesChanged;
ox::Signal<ox::Error(bool)> exportableChanged;
ox::Signal<ox::Error(bool)> cutEnabledChanged;
ox::Signal<ox::Error(bool)> copyEnabledChanged;
ox::Signal<ox::Error(bool)> pasteEnabledChanged;
ox::Signal<ox::Error(const ox::String&)> closed;
};

View File

@ -6,20 +6,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "context.hpp"
#include "module.hpp"
namespace nostalgia::studio {
QVector<WizardMaker> Module::newWizards(const Context*) {
return {};
}
QVector<WizardMaker> Module::importWizards(const Context*) {
return {};
}
QVector<EditorMaker> Module::editors(const Context*) {
ox::Vector<EditorMaker> Module::editors(core::Context*) {
return {};
}

View File

@ -10,33 +10,25 @@
#include <functional>
#include <QVector>
#include <QWizardPage>
#include <ox/std/string.hpp>
#include <ox/std/vector.hpp>
#include "wizard.hpp"
#include <nostalgia/core/context.hpp>
namespace nostalgia::studio {
struct EditorMaker {
QStringList fileTypes;
std::function<class Editor*(QString)> make;
using Func = std::function<class Editor*(ox::String)>;
ox::Vector<ox::String> fileTypes;
Func make;
};
class Module {
public:
virtual ~Module() = default;
virtual ~Module() noexcept = default;
virtual QVector<WizardMaker> newWizards(const class Context *ctx);
virtual QVector<WizardMaker> importWizards(const Context *ctx);
virtual QVector<EditorMaker> editors(const class Context *ctx);
virtual ox::Vector<EditorMaker> editors(core::Context *ctx);
};
}
#define PluginInterface_iid "net.drinkingtea.nostalgia.studio.Module"
Q_DECLARE_INTERFACE(nostalgia::studio::Module, PluginInterface_iid)

View File

@ -21,7 +21,7 @@ ox::String filePathToName(const ox::String &path, const ox::String &prefix, cons
}
Project::Project(const ox::String &path) noexcept: m_fs(ox::make_unique<ox::PassThroughFS>(path.c_str())) {
Project::Project(ox::FileSystem *fs, const ox::String &path) noexcept: m_fs(fs) {
oxTracef("nostalgia::studio", "Project: {}", path);
m_path = path;
}
@ -33,7 +33,7 @@ ox::Error Project::create() noexcept {
}
ox::FileSystem *Project::romFs() noexcept {
return m_fs.get();
return m_fs;
}
ox::Error Project::mkdir(const ox::String &path) const noexcept {

View File

@ -39,10 +39,10 @@ ox::String filePathToName(const ox::String &path, const ox::String &prefix, cons
class NOSTALGIASTUDIO_EXPORT Project {
private:
ox::String m_path = "";
mutable ox::UniquePtr<ox::FileSystem> m_fs;
mutable ox::FileSystem *m_fs = nullptr;
public:
explicit Project(const ox::String &path) noexcept;
explicit Project(ox::FileSystem *fs, const ox::String &path) noexcept;
ox::Error create() noexcept;

View File

@ -6,16 +6,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <algorithm>
#include "task.hpp"
namespace nostalgia::studio {
void TaskRunner::update(core::Context *ctx) noexcept {
for (auto i = 0u; i < m_tasks.size(); ++i) {
if (m_tasks[i]->update(ctx) == studio::TaskState::Done) {
oxIgnoreError(m_tasks.erase(i--));
}
}
oxIgnoreError(m_tasks.erase(std::remove_if(m_tasks.begin(), m_tasks.end(), [&](auto &t) {
return t->update(ctx) == TaskState::Done;
})));
}
void TaskRunner::add(Task *task) noexcept {

View File

@ -21,24 +21,29 @@ class StudioUIDrawer: public core::Drawer {
explicit StudioUIDrawer(StudioUI *ui) noexcept: m_ui(ui) {
}
protected:
void draw(core::Context *ctx) noexcept final {
m_ui->draw(ctx);
void draw(core::Context*) noexcept final {
m_ui->draw();
}
};
static int eventHandler(core::Context *ctx) noexcept {
auto ui = ctx->customData<StudioUI>();
ui->update(ctx);
ui->update();
return 16;
}
static ox::Error run(ox::FileSystem *fs) noexcept {
oxRequireM(ctx, core::init(fs));
static ox::Error run(ox::UniquePtr<ox::FileSystem> fs) noexcept {
oxRequireM(ctx, core::init(std::move(fs), "NostalgiaStudio"));
core::setWindowTitle(ctx.get(), "Nostalgia Studio");
core::setEventHandler(ctx.get(), eventHandler);
StudioUI ui;
StudioUIDrawer drawer(&ui);
ctx->setCustomData(&ui);
ox::UniquePtr<StudioUI> ui;
try {
ui = ox::make_unique<StudioUI>(ctx.get());
} catch (ox::Exception &ex) {
return ex.toError();
}
StudioUIDrawer drawer(ui.get());
ctx->setCustomData(ui.get());
ctx->drawers.emplace_back(&drawer);
return core::run(ctx.get());
}
@ -47,10 +52,10 @@ static ox::Error run(int argc, const char **argv) noexcept {
ox::trace::init();
if (argc >= 2) {
const auto path = argv[1];
oxRequire(fs, core::loadRomFs(path));
return run(fs.get());
oxRequireM(fs, core::loadRomFs(path));
return run(std::move(fs));
} else {
return run(nullptr);
return run(ox::UniquePtr<ox::FileSystem>(nullptr));
}
}

View File

@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <algorithm>
#include <imgui.h>
#include <nostalgia/common/bounds.hpp>
@ -14,18 +16,41 @@
namespace nostalgia {
static ox::Result<ox::UniquePtr<ProjectTreeModel>>
buildProjectTreeModel(ProjectExplorer *explorer, const ox::String &name, const ox::String &path, ProjectTreeModel *parent) noexcept {
const auto fs = explorer->romFs();
oxRequire(stat, fs->stat(path.c_str()));
auto out = ox::make_unique<ProjectTreeModel>(explorer, name, parent);
if (stat.fileType == ox::FileType::Directory) {
oxRequireM(children, fs->ls(path));
std::sort(children.begin(), children.end());
ox::Vector<ox::UniquePtr<ProjectTreeModel>> outChildren;
for (const auto &childName : children) {
if (childName[0] != '.') {
const auto childPath = ox::sfmt("{}/{}", path, childName);
oxRequireM(child, buildProjectTreeModel(explorer, childName, childPath, out.get()));
outChildren.emplace_back(std::move(child));
}
}
out->setChildren(std::move(outChildren));
}
return out;
}
ProjectExplorer::ProjectExplorer(core::Context *ctx) noexcept {
m_ctx = ctx;
}
void ProjectExplorer::draw(core::Context *ctx) noexcept {
const auto viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + 71));
ImGui::SetNextWindowSize(ImVec2(313, viewport->Size.y - 71));
const auto flags = ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoSavedSettings;
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoSavedSettings;
ImGui::Begin("ProjectExplorer", nullptr, flags);
ImGui::PopStyleVar();
ImGui::SetNextTreeNodeOpen(true);
if (m_treeModel) {
m_treeModel->draw(ctx);
@ -37,5 +62,11 @@ void ProjectExplorer::setModel(ox::UniquePtr<ProjectTreeModel> model) noexcept {
m_treeModel = std::move(model);
}
ox::Error ProjectExplorer::refreshProjectTreeModel(const ox::String&) noexcept {
oxRequireM(model, buildProjectTreeModel(this, "Project", "/", nullptr));
setModel(std::move(model));
return OxError(0);
}
}

View File

@ -8,6 +8,7 @@
#pragma once
#include <ox/event/signal.hpp>
#include <ox/std/memory.hpp>
#include "lib/widget.hpp"
@ -18,10 +19,23 @@ namespace nostalgia {
class ProjectExplorer: public studio::Widget {
private:
ox::UniquePtr<ProjectTreeModel> m_treeModel;
core::Context *m_ctx = nullptr;
public:
explicit ProjectExplorer(core::Context *ctx) noexcept;
void draw(core::Context *ctx) noexcept override;
void setModel(ox::UniquePtr<ProjectTreeModel> model) noexcept;
ox::Error refreshProjectTreeModel(const ox::String& = {}) noexcept;
constexpr ox::FileSystem *romFs() noexcept {
return m_ctx->rom.get();
}
// slots
public:
ox::Signal<ox::Error(const ox::String&)> fileChosen;
};
}

View File

@ -8,14 +8,16 @@
#include <imgui.h>
#include "projectexplorer.hpp"
#include "projecttreemodel.hpp"
namespace nostalgia {
ProjectTreeModel::ProjectTreeModel(const ox::String &name,
ox::Vector<ox::UniquePtr<ProjectTreeModel>> children) noexcept {
ProjectTreeModel::ProjectTreeModel(ProjectExplorer *explorer, const ox::String &name,
ProjectTreeModel *parent) noexcept {
m_explorer = explorer;
m_name = name;
m_children = std::move(children);
m_parent = parent;
}
ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept {
@ -32,11 +34,23 @@ void ProjectTreeModel::draw(core::Context *ctx) noexcept {
}
ImGui::TreePop();
}
} else {
if (ImGui::TreeNodeEx(m_name.c_str(), ImGuiTreeNodeFlags_Leaf)) {
ImGui::TreePop();
} else if (auto path = fullPath(); ImGui::TreeNodeEx(ox::sfmt("{}##{}", m_name, path).c_str(), ImGuiTreeNodeFlags_Leaf)) {
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
m_explorer->fileChosen.emit(path);
}
ImGui::TreePop();
}
}
void ProjectTreeModel::setChildren(ox::Vector<ox::UniquePtr<ProjectTreeModel>> children) noexcept {
m_children = std::move(children);
}
ox::String ProjectTreeModel::fullPath() noexcept {
if (m_parent) {
return m_parent->fullPath() + "/" + m_name;
}
return "";
}
}

View File

@ -17,15 +17,23 @@ namespace nostalgia {
class ProjectTreeModel {
private:
class ProjectExplorer *m_explorer = nullptr;
ProjectTreeModel *m_parent = nullptr;
ox::String m_name;
ox::Vector <ox::UniquePtr<ProjectTreeModel>> m_children;
ox::Vector<ox::UniquePtr<ProjectTreeModel>> m_children;
public:
explicit ProjectTreeModel(const ox::String &name,
ox::Vector <ox::UniquePtr<ProjectTreeModel>> children) noexcept;
explicit ProjectTreeModel(class ProjectExplorer *explorer, const ox::String &name,
ProjectTreeModel *parent = nullptr) noexcept;
ProjectTreeModel(ProjectTreeModel &&other) noexcept;
void draw(core::Context *ctx) noexcept;
void setChildren(ox::Vector<ox::UniquePtr<ProjectTreeModel>> children) noexcept;
private:
[[nodiscard]]
ox::String fullPath() noexcept;
};
}

View File

@ -10,6 +10,8 @@
#include "lib/editor.hpp"
#include "lib/filedialog.hpp"
#include "lib/module.hpp"
#include "lib/project.hpp"
#include "lib/task.hpp"
#include "lib/undostack.hpp"
#include "lib/widget.hpp"

View File

@ -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);
}

View File

@ -12,6 +12,7 @@
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include "lib/module.hpp"
#include "lib/project.hpp"
#include "lib/task.hpp"
#include "projectexplorer.hpp"
@ -25,28 +26,41 @@ class StudioUI: public ox::SignalHandler {
private:
ox::UniquePtr<studio::Project> m_project;
studio::TaskRunner m_taskRunner;
ox::Vector<studio::Widget*> m_widgets;
ox::UniquePtr<ProjectTreeModel> m_treeModel;
ProjectExplorer m_projectExplorer;
core::Context *m_ctx = nullptr;
ox::Vector<ox::UniquePtr<studio::Editor>> m_editors;
ox::Vector<ox::UniquePtr<studio::Widget>> m_widgets;
ox::HashMap<ox::String, studio::EditorMaker::Func> m_editorMakers;
ProjectExplorer *m_projectExplorer = nullptr;
ox::Vector<ox::String> m_openFiles;
bool m_saveEnabled = false;
bool m_aboutEnabled = false;
public:
StudioUI() noexcept;
explicit StudioUI(core::Context *ctx) noexcept;
void update(core::Context *ctx) noexcept;
void update() noexcept;
protected:
void draw(core::Context *ctx) noexcept;
void draw() noexcept;
private:
void drawMenu(core::Context *ctx) noexcept;
void drawMenu() noexcept;
void drawToolbar(core::Context *ctx) noexcept;
void drawToolbar() noexcept;
void drawTabs() noexcept;
void loadEditorMaker(const studio::EditorMaker &editorMaker) noexcept;
void loadModule(studio::Module *module) noexcept;
void loadModules() noexcept;
ox::Error openProject(const ox::String &path) noexcept;
ox::Error refreshProjectTreeModel(const ox::String& = {}) noexcept;
ox::Error openFile(const ox::String &path) noexcept;
ox::Error closeFile(const ox::String &path) noexcept;
};
}