[nostalgia] Update Studio to handle tabs and open directory dialog on Mac, Update core::init
This commit is contained in:
parent
e29f65f351
commit
ad743565b2
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -13,6 +13,7 @@ target_link_libraries(
|
||||
nostalgia-studio
|
||||
OxClArgs
|
||||
OxFS
|
||||
NostalgiaCore-Studio
|
||||
NostalgiaCore-Userspace
|
||||
NostalgiaStudio
|
||||
NostalgiaPack
|
||||
|
24
src/nostalgia/studio/builtinmodules.hpp
Normal file
24
src/nostalgia/studio/builtinmodules.hpp
Normal 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>();
|
||||
// },
|
||||
//};
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -6,20 +6,20 @@
|
||||
* 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 {
|
||||
m_tasks.emplace_back(task);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -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 "";
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user