[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]] [[nodiscard]]
constexpr Color16 color16(uint8_t r, uint8_t g, uint8_t b) noexcept { constexpr Color16 color16(uint8_t r, uint8_t g, uint8_t b) noexcept {
return r | (g << 5) | (b << 10); return r | (g << 5) | (b << 10);

View File

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

View File

@ -10,6 +10,7 @@
#include <ox/fs/fs.hpp> #include <ox/fs/fs.hpp>
#include "assetmanager.hpp"
#include "clipboard.hpp" #include "clipboard.hpp"
#include "consts.hpp" #include "consts.hpp"
#include "gfx.hpp" #include "gfx.hpp"
@ -20,7 +21,7 @@ namespace nostalgia::core {
using event_handler = int(*)(Context*); 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; ox::Error run(Context *ctx) noexcept;

View File

@ -40,9 +40,10 @@ static void initTimer() noexcept {
REG_IE = REG_IE | Int_timer0; 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>(); auto ctx = ox::make_unique<Context>();
ctx->rom = fs; ctx->rom = std::move(fs);
ctx->appName = std::move(appName);
oxReturnError(initGfx(ctx.get())); oxReturnError(initGfx(ctx.get()));
initTimer(); initTimer();
initIrq(); initIrq();

View File

@ -162,7 +162,8 @@ ox::Error initConsole(Context *ctx) noexcept {
ctx = new (ox_alloca(sizeof(Context))) Context(); ctx = new (ox_alloca(sizeof(Context))) Context();
oxRequire(rom, loadRom()); oxRequire(rom, loadRom());
ox::FileStore32 fs(rom, 32 * ox::units::MB); 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); return loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr);
} }

View File

@ -32,8 +32,8 @@ static int eventHandler(core::Context *ctx) noexcept {
return 16; return 16;
} }
ox::Error run(ox::FileSystem *fs) noexcept { ox::Error run(ox::UniquePtr<ox::FileSystem> fs) noexcept {
oxRequireM(ctx, core::init(fs)); oxRequireM(ctx, core::init(std::move(fs)));
constexpr auto TileSheetAddr = "/TileSheets/Charset.ng"; constexpr auto TileSheetAddr = "/TileSheets/Charset.ng";
constexpr auto PaletteAddr = "/Palettes/Charset.npal"; constexpr auto PaletteAddr = "/Palettes/Charset.npal";
oxReturnError(core::loadSpriteTileSheet(ctx.get(), 0, TileSheetAddr, PaletteAddr)); 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/. * 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); return OxError(1);
} }
const auto path = argv[1]; const auto path = argv[1];
oxRequire(fs, nostalgia::core::loadRomFs(path)); oxRequireM(fs, nostalgia::core::loadRomFs(path));
return run(fs.get()); return run(std::move(fs));
} }
int main(int argc, const char **argv) { int main(int argc, const char **argv) {

View File

@ -13,6 +13,7 @@ target_link_libraries(
nostalgia-studio nostalgia-studio
OxClArgs OxClArgs
OxFS OxFS
NostalgiaCore-Studio
NostalgiaCore-Userspace NostalgiaCore-Userspace
NostalgiaStudio NostalgiaStudio
NostalgiaPack 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 NostalgiaStudio
configio.cpp configio.cpp
editor.cpp editor.cpp
module.cpp
project.cpp project.cpp
task.cpp task.cpp
undostack.cpp undostack.cpp
widget.cpp widget.cpp
window.cpp window.cpp
filedialog_gtk.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 install(TARGETS NostalgiaStudio
LIBRARY DESTINATION ${NOSTALGIA_DIST_LIB}/nostalgia) LIBRARY DESTINATION ${NOSTALGIA_DIST_LIB}/nostalgia)
@ -46,11 +51,15 @@ target_link_libraries(
install( install(
FILES FILES
configio.hpp
editor.hpp editor.hpp
filedialog.hpp filedialog.hpp
module.hpp
project.hpp project.hpp
task.hpp task.hpp
undostack.hpp undostack.hpp
widget.hpp
window.hpp
${CMAKE_CURRENT_BINARY_DIR}/nostalgiastudio_export.h ${CMAKE_CURRENT_BINARY_DIR}/nostalgiastudio_export.h
DESTINATION DESTINATION
include/nostalgia/studio/lib include/nostalgia/studio/lib

View File

@ -19,18 +19,20 @@
#include <ox/std/trace.hpp> #include <ox/std/trace.hpp>
#include <ox/std/string.hpp> #include <ox/std/string.hpp>
#include <nostalgia/core/context.hpp>
namespace nostalgia::studio { namespace nostalgia::studio {
constexpr auto ConfigDir = [] { constexpr auto ConfigDir = [] {
switch (ox::defines::OS) { switch (ox::defines::OS) {
case ox::defines::OS::Darwin: case ox::defines::OS::Darwin:
return "{}/Library/Preferences/NostalgiaStudio"; return "{}/Library/Preferences/{}";
case ox::defines::OS::DragonFlyBSD: case ox::defines::OS::DragonFlyBSD:
case ox::defines::OS::FreeBSD: case ox::defines::OS::FreeBSD:
case ox::defines::OS::Linux: case ox::defines::OS::Linux:
case ox::defines::OS::NetBSD: case ox::defines::OS::NetBSD:
case ox::defines::OS::OpenBSD: case ox::defines::OS::OpenBSD:
return "{}/.config/NostalgiaStudio"; return "{}/.config/{}";
case ox::defines::OS::BareMetal: case ox::defines::OS::BareMetal:
case ox::defines::OS::Windows: case ox::defines::OS::Windows:
return ""; return "";
@ -38,13 +40,14 @@ constexpr auto ConfigDir = [] {
}(); }();
template<typename T> 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 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(); const auto path = ox::sfmt("{}/{}.json", configPath, name).toStdString();
std::ifstream file(path, std::ios::binary | std::ios::ate); std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file.good()) { 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"); return OxError(1, "Could not find config file");
} }
try { try {
@ -54,15 +57,16 @@ ox::Result<T> readConfig(const ox::String &name = ox::getModelTypeName<T>()) noe
file.read(buff.data(), size); file.read(buff.data(), size);
return ox::readOC<T>(buff); return ox::readOC<T>(buff);
} catch (const std::ios_base::failure &e) { } 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"); return OxError(2, "Could not read config file");
} }
} }
template<typename T> 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 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(); const auto path = ox::sfmt("{}/{}.json", configPath, name).toStdString();
std::error_code ec; std::error_code ec;
std::filesystem::create_directory(configPath, 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); std::ofstream file(path, std::ios::binary | std::ios::ate);
if (!file.good()) { 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"); return OxError(1, "Could not find config file");
} }
oxRequireM(buff, ox::writeOC(data)); 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())); file.write(buff.data(), static_cast<ox::Signed<decltype(buff.size())>>(buff.size()));
return OxError(0); return OxError(0);
} catch (const std::ios_base::failure &e) { } 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"); return OxError(2, "Could not read config file");
} }
} }
template<typename T> template<typename T>
ox::Error writeConfig(T *data) noexcept { ox::Error writeConfig(core::Context *ctx, T *data) noexcept {
return writeConfig(ox::getModelTypeName<T>(), data); const auto TypeName = ox::getModelTypeName<T>();
return writeConfig(ctx, TypeName, data);
} }
template<typename T, typename Func> template<typename T, typename Func>
ox::Error editConfig(const ox::String &name, Func f) noexcept { void openConfig(core::Context *ctx, const ox::String &name, Func f) noexcept {
auto c = readConfig<T>(name); oxAssert(name != "", "Config type has no TypeName");
const auto c = readConfig<T>(ctx, name);
f(&c.value); f(&c.value);
return writeConfig(name, &c.value);
} }
template<typename T, typename Func> template<typename T, typename Func>
ox::Error editConfig(Func f) noexcept { void openConfig(core::Context *ctx, Func f) noexcept {
return editConfig<T>(ox::getModelTypeName<T>(), f); 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 { namespace nostalgia::studio {
ox::String Editor::itemDisplayName() const {
return itemName();
}
void Editor::cut() { void Editor::cut() {
} }
@ -24,6 +28,10 @@ void Editor::paste() {
void Editor::exportFile() { void Editor::exportFile() {
} }
void Editor::close() {
this->closed.emit(itemName());
}
void Editor::save() { void Editor::save() {
saveItem(); saveItem();
setUnsavedChanges(false); setUnsavedChanges(false);

View File

@ -11,12 +11,13 @@
#include <ox/event/signal.hpp> #include <ox/event/signal.hpp>
#include "undostack.hpp" #include "undostack.hpp"
#include "widget.hpp"
#include "nostalgiastudio_export.h" #include "nostalgiastudio_export.h"
namespace nostalgia::studio { namespace nostalgia::studio {
class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler { class NOSTALGIASTUDIO_EXPORT Editor: public Widget {
private: private:
UndoStack m_cmdStack; UndoStack m_cmdStack;
@ -35,6 +36,8 @@ class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler {
[[nodiscard]] [[nodiscard]]
virtual ox::String itemName() const = 0; virtual ox::String itemName() const = 0;
virtual ox::String itemDisplayName() const;
virtual void cut(); virtual void cut();
virtual void copy(); virtual void copy();
@ -43,6 +46,8 @@ class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler {
virtual void exportFile(); virtual void exportFile();
void close();
/** /**
* Save changes to item being edited. * Save changes to item being edited.
*/ */
@ -89,12 +94,14 @@ class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler {
*/ */
virtual void saveItem(); virtual void saveItem();
// slots
public: public:
ox::Signal<ox::Error(bool)> unsavedChangesChanged; ox::Signal<ox::Error(bool)> unsavedChangesChanged;
ox::Signal<ox::Error(bool)> exportableChanged; ox::Signal<ox::Error(bool)> exportableChanged;
ox::Signal<ox::Error(bool)> cutEnabledChanged; ox::Signal<ox::Error(bool)> cutEnabledChanged;
ox::Signal<ox::Error(bool)> copyEnabledChanged; ox::Signal<ox::Error(bool)> copyEnabledChanged;
ox::Signal<ox::Error(bool)> pasteEnabledChanged; 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
#include "context.hpp"
#include "module.hpp" #include "module.hpp"
namespace nostalgia::studio { namespace nostalgia::studio {
QVector<WizardMaker> Module::newWizards(const Context*) { ox::Vector<EditorMaker> Module::editors(core::Context*) {
return {};
}
QVector<WizardMaker> Module::importWizards(const Context*) {
return {};
}
QVector<EditorMaker> Module::editors(const Context*) {
return {}; return {};
} }

View File

@ -10,33 +10,25 @@
#include <functional> #include <functional>
#include <QVector> #include <ox/std/string.hpp>
#include <QWizardPage> #include <ox/std/vector.hpp>
#include "wizard.hpp" #include <nostalgia/core/context.hpp>
namespace nostalgia::studio { namespace nostalgia::studio {
struct EditorMaker { struct EditorMaker {
QStringList fileTypes; using Func = std::function<class Editor*(ox::String)>;
std::function<class Editor*(QString)> make; ox::Vector<ox::String> fileTypes;
Func make;
}; };
class Module { class Module {
public: public:
virtual ~Module() = default; virtual ~Module() noexcept = default;
virtual QVector<WizardMaker> newWizards(const class Context *ctx); virtual ox::Vector<EditorMaker> editors(core::Context *ctx);
virtual QVector<WizardMaker> importWizards(const Context *ctx);
virtual QVector<EditorMaker> editors(const class 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); oxTracef("nostalgia::studio", "Project: {}", path);
m_path = path; m_path = path;
} }
@ -33,7 +33,7 @@ ox::Error Project::create() noexcept {
} }
ox::FileSystem *Project::romFs() noexcept { ox::FileSystem *Project::romFs() noexcept {
return m_fs.get(); return m_fs;
} }
ox::Error Project::mkdir(const ox::String &path) const noexcept { 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 { class NOSTALGIASTUDIO_EXPORT Project {
private: private:
ox::String m_path = ""; ox::String m_path = "";
mutable ox::UniquePtr<ox::FileSystem> m_fs; mutable ox::FileSystem *m_fs = nullptr;
public: public:
explicit Project(const ox::String &path) noexcept; explicit Project(ox::FileSystem *fs, const ox::String &path) noexcept;
ox::Error create() noexcept; ox::Error create() noexcept;

View File

@ -6,16 +6,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
#include <algorithm>
#include "task.hpp" #include "task.hpp"
namespace nostalgia::studio { namespace nostalgia::studio {
void TaskRunner::update(core::Context *ctx) noexcept { void TaskRunner::update(core::Context *ctx) noexcept {
for (auto i = 0u; i < m_tasks.size(); ++i) { oxIgnoreError(m_tasks.erase(std::remove_if(m_tasks.begin(), m_tasks.end(), [&](auto &t) {
if (m_tasks[i]->update(ctx) == studio::TaskState::Done) { return t->update(ctx) == TaskState::Done;
oxIgnoreError(m_tasks.erase(i--)); })));
}
}
} }
void TaskRunner::add(Task *task) noexcept { 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) { explicit StudioUIDrawer(StudioUI *ui) noexcept: m_ui(ui) {
} }
protected: protected:
void draw(core::Context *ctx) noexcept final { void draw(core::Context*) noexcept final {
m_ui->draw(ctx); m_ui->draw();
} }
}; };
static int eventHandler(core::Context *ctx) noexcept { static int eventHandler(core::Context *ctx) noexcept {
auto ui = ctx->customData<StudioUI>(); auto ui = ctx->customData<StudioUI>();
ui->update(ctx); ui->update();
return 16; return 16;
} }
static ox::Error run(ox::FileSystem *fs) noexcept { static ox::Error run(ox::UniquePtr<ox::FileSystem> fs) noexcept {
oxRequireM(ctx, core::init(fs)); oxRequireM(ctx, core::init(std::move(fs), "NostalgiaStudio"));
core::setWindowTitle(ctx.get(), "Nostalgia Studio"); core::setWindowTitle(ctx.get(), "Nostalgia Studio");
core::setEventHandler(ctx.get(), eventHandler); core::setEventHandler(ctx.get(), eventHandler);
StudioUI ui; ox::UniquePtr<StudioUI> ui;
StudioUIDrawer drawer(&ui); try {
ctx->setCustomData(&ui); 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); ctx->drawers.emplace_back(&drawer);
return core::run(ctx.get()); return core::run(ctx.get());
} }
@ -47,10 +52,10 @@ static ox::Error run(int argc, const char **argv) noexcept {
ox::trace::init(); ox::trace::init();
if (argc >= 2) { if (argc >= 2) {
const auto path = argv[1]; const auto path = argv[1];
oxRequire(fs, core::loadRomFs(path)); oxRequireM(fs, core::loadRomFs(path));
return run(fs.get()); return run(std::move(fs));
} else { } 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
#include <algorithm>
#include <imgui.h> #include <imgui.h>
#include <nostalgia/common/bounds.hpp> #include <nostalgia/common/bounds.hpp>
@ -14,6 +16,31 @@
namespace nostalgia { 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 { void ProjectExplorer::draw(core::Context *ctx) noexcept {
const auto viewport = ImGui::GetMainViewport(); const auto viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + 71)); ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + 71));
@ -23,9 +50,7 @@ void ProjectExplorer::draw(core::Context *ctx) noexcept {
| ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoSavedSettings; | ImGuiWindowFlags_NoSavedSettings;
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
ImGui::Begin("ProjectExplorer", nullptr, flags); ImGui::Begin("ProjectExplorer", nullptr, flags);
ImGui::PopStyleVar();
ImGui::SetNextTreeNodeOpen(true); ImGui::SetNextTreeNodeOpen(true);
if (m_treeModel) { if (m_treeModel) {
m_treeModel->draw(ctx); m_treeModel->draw(ctx);
@ -37,5 +62,11 @@ void ProjectExplorer::setModel(ox::UniquePtr<ProjectTreeModel> model) noexcept {
m_treeModel = std::move(model); 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 #pragma once
#include <ox/event/signal.hpp>
#include <ox/std/memory.hpp> #include <ox/std/memory.hpp>
#include "lib/widget.hpp" #include "lib/widget.hpp"
@ -18,10 +19,23 @@ namespace nostalgia {
class ProjectExplorer: public studio::Widget { class ProjectExplorer: public studio::Widget {
private: private:
ox::UniquePtr<ProjectTreeModel> m_treeModel; ox::UniquePtr<ProjectTreeModel> m_treeModel;
core::Context *m_ctx = nullptr;
public: public:
explicit ProjectExplorer(core::Context *ctx) noexcept;
void draw(core::Context *ctx) noexcept override; void draw(core::Context *ctx) noexcept override;
void setModel(ox::UniquePtr<ProjectTreeModel> model) noexcept; 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 <imgui.h>
#include "projectexplorer.hpp"
#include "projecttreemodel.hpp" #include "projecttreemodel.hpp"
namespace nostalgia { namespace nostalgia {
ProjectTreeModel::ProjectTreeModel(const ox::String &name, ProjectTreeModel::ProjectTreeModel(ProjectExplorer *explorer, const ox::String &name,
ox::Vector<ox::UniquePtr<ProjectTreeModel>> children) noexcept { ProjectTreeModel *parent) noexcept {
m_explorer = explorer;
m_name = name; m_name = name;
m_children = std::move(children); m_parent = parent;
} }
ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept { ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept {
@ -32,11 +34,23 @@ void ProjectTreeModel::draw(core::Context *ctx) noexcept {
} }
ImGui::TreePop(); ImGui::TreePop();
} }
} else { } else if (auto path = fullPath(); ImGui::TreeNodeEx(ox::sfmt("{}##{}", m_name, path).c_str(), ImGuiTreeNodeFlags_Leaf)) {
if (ImGui::TreeNodeEx(m_name.c_str(), ImGuiTreeNodeFlags_Leaf)) { if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
ImGui::TreePop(); 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 { class ProjectTreeModel {
private: private:
class ProjectExplorer *m_explorer = nullptr;
ProjectTreeModel *m_parent = nullptr;
ox::String m_name; ox::String m_name;
ox::Vector <ox::UniquePtr<ProjectTreeModel>> m_children; ox::Vector<ox::UniquePtr<ProjectTreeModel>> m_children;
public: public:
explicit ProjectTreeModel(const ox::String &name, explicit ProjectTreeModel(class ProjectExplorer *explorer, const ox::String &name,
ox::Vector <ox::UniquePtr<ProjectTreeModel>> children) noexcept; ProjectTreeModel *parent = nullptr) noexcept;
ProjectTreeModel(ProjectTreeModel &&other) noexcept; ProjectTreeModel(ProjectTreeModel &&other) noexcept;
void draw(core::Context *ctx) 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/editor.hpp"
#include "lib/filedialog.hpp" #include "lib/filedialog.hpp"
#include "lib/module.hpp"
#include "lib/project.hpp" #include "lib/project.hpp"
#include "lib/task.hpp" #include "lib/task.hpp"
#include "lib/undostack.hpp" #include "lib/undostack.hpp"
#include "lib/widget.hpp"

View File

@ -11,6 +11,7 @@
#include <nostalgia/core/core.hpp> #include <nostalgia/core/core.hpp>
#include "lib/configio.hpp" #include "lib/configio.hpp"
#include "builtinmodules.hpp"
#include "filedialogmanager.hpp" #include "filedialogmanager.hpp"
#include "studioapp.hpp" #include "studioapp.hpp"
@ -20,58 +21,62 @@ struct StudioConfig {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.studio.StudioConfig"; static constexpr auto TypeName = "net.drinkingtea.nostalgia.studio.StudioConfig";
static constexpr auto TypeVersion = 1; static constexpr auto TypeVersion = 1;
ox::String projectPath; ox::String projectPath;
ox::Vector<ox::String> openFiles;
}; };
template<typename T> template<typename T>
constexpr ox::Error model(T *h, StudioConfig *config) noexcept { constexpr ox::Error model(T *h, StudioConfig *config) noexcept {
h->template setTypeInfo<StudioConfig>();
oxReturnError(h->field("project_path", &config->projectPath)); oxReturnError(h->field("project_path", &config->projectPath));
oxReturnError(h->field("open_files", &config->openFiles));
return OxError(0); return OxError(0);
} }
StudioUI::StudioUI() noexcept { StudioUI::StudioUI(core::Context *ctx) noexcept {
m_widgets.push_back(&m_projectExplorer); m_ctx = ctx;
if (const auto [config, err] = studio::readConfig<StudioConfig>(); !err) { 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)); oxIgnoreError(openProject(config.projectPath));
for (const auto &f : config.openFiles) {
oxLogError(openFile(f));
}
} else { } else {
if (err.msg) {
oxErrf("Could not open studio config file: {}\n", 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);
}
} }
} }
static ox::Result<ox::UniquePtr<ProjectTreeModel>> void StudioUI::update() noexcept {
buildProjectTreeModel(const ox::String &name, const ox::String &path, ox::FileSystem *fs) noexcept { m_taskRunner.update(m_ctx);
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));
}
}
}
return ox::make_unique<ProjectTreeModel>(name, std::move(outChildren));
} }
void StudioUI::update(core::Context *ctx) noexcept { void StudioUI::draw() noexcept {
m_taskRunner.update(ctx); drawMenu();
} drawToolbar();
drawTabs();
void StudioUI::draw(core::Context *ctx) noexcept {
drawMenu(ctx);
drawToolbar(ctx);
for (auto &w : m_widgets) { for (auto &w : m_widgets) {
w->draw(ctx); w->draw(m_ctx);
} }
constexpr auto aboutFlags = ImGuiWindowFlags_NoCollapse
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_Modal;
if (m_aboutEnabled && 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(); ImGui::End();
} }
} }
void StudioUI::drawMenu(core::Context *ctx) noexcept { void StudioUI::drawMenu() noexcept {
if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("File")) { if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("New...", "Ctrl+N")) { 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("Save", "Ctrl+S", false, m_saveEnabled)) {
} }
if (ImGui::MenuItem("Quit", "Ctrl+Q")) { if (ImGui::MenuItem("Quit", "Ctrl+Q")) {
core::shutdown(ctx); core::shutdown(m_ctx);
} }
ImGui::EndMenu(); 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 BtnWidth = 96;
constexpr auto BtnHeight = 32; constexpr auto BtnHeight = 32;
const auto viewport = ImGui::GetMainViewport(); const auto viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + 20)); ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + 1, viewport->Pos.y + 21));
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, 48)); ImGui::SetNextWindowSize(ImVec2(viewport->Size.x - 1, 48));
const auto flags = ImGuiWindowFlags_NoTitleBar const auto flags = ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoSavedSettings; | ImGuiWindowFlags_NoSavedSettings;
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); ImGui::Begin("MainToolbar##MainWindow", nullptr, flags);
ImGui::Begin("MainToolbar", nullptr, flags);
ImGui::PopStyleVar();
{ {
ImGui::Button("New##MainToolbar", ImVec2(BtnWidth, BtnHeight)); ImGui::Button("New##MainToolbar##MainWindow", ImVec2(BtnWidth, BtnHeight));
ImGui::SameLine(); 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)); m_taskRunner.add(new FileDialogManager(this, &StudioUI::openProject));
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::Button("Save##MainToolbar", ImVec2(BtnWidth, BtnHeight)); ImGui::Button("Save##MainToolbar##MainWindow", ImVec2(BtnWidth, BtnHeight));
} }
ImGui::End(); ImGui::End();
} }
ox::Error StudioUI::openProject(const ox::String &path) noexcept { void StudioUI::drawTabs() noexcept {
m_project = ox::make_unique<studio::Project>(path); const auto viewport = ImGui::GetMainViewport();
m_project->fileAdded.connect(this, &StudioUI::refreshProjectTreeModel); ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + 316, viewport->Pos.y + 71));
m_project->fileDeleted.connect(this, &StudioUI::refreshProjectTreeModel); ImGui::SetNextWindowSize(ImVec2(viewport->Size.x - 316, viewport->Size.y - 71));
oxReturnError(studio::editConfig<StudioConfig>([path](StudioConfig *config) { constexpr auto windowFlags = ImGuiWindowFlags_NoTitleBar
config->projectPath = path; | ImGuiWindowFlags_NoResize
})); | ImGuiWindowFlags_NoMove
return refreshProjectTreeModel(); | 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 { void StudioUI::loadEditorMaker(const studio::EditorMaker &editorMaker) noexcept {
oxRequireM(model, buildProjectTreeModel("Project", "/", m_project->romFs())); for (auto &ext : editorMaker.fileTypes) {
m_projectExplorer.setModel(std::move(model)); 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); return OxError(0);
} }

View File

@ -12,6 +12,7 @@
#include <ox/std/memory.hpp> #include <ox/std/memory.hpp>
#include <ox/std/string.hpp> #include <ox/std/string.hpp>
#include "lib/module.hpp"
#include "lib/project.hpp" #include "lib/project.hpp"
#include "lib/task.hpp" #include "lib/task.hpp"
#include "projectexplorer.hpp" #include "projectexplorer.hpp"
@ -25,28 +26,41 @@ class StudioUI: public ox::SignalHandler {
private: private:
ox::UniquePtr<studio::Project> m_project; ox::UniquePtr<studio::Project> m_project;
studio::TaskRunner m_taskRunner; studio::TaskRunner m_taskRunner;
ox::Vector<studio::Widget*> m_widgets; core::Context *m_ctx = nullptr;
ox::UniquePtr<ProjectTreeModel> m_treeModel; ox::Vector<ox::UniquePtr<studio::Editor>> m_editors;
ProjectExplorer m_projectExplorer; 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_saveEnabled = false;
bool m_aboutEnabled = false; bool m_aboutEnabled = false;
public: public:
StudioUI() noexcept; explicit StudioUI(core::Context *ctx) noexcept;
void update(core::Context *ctx) noexcept; void update() noexcept;
protected: protected:
void draw(core::Context *ctx) noexcept; void draw() noexcept;
private: 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 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;
}; };
} }