[olympic] Move keel, turbine, and studio to olympic
This commit is contained in:
95
src/olympic/studio/modlib/include/studio/configio.hpp
Normal file
95
src/olympic/studio/modlib/include/studio/configio.hpp
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/model/typenamecatcher.hpp>
|
||||
#include <ox/oc/oc.hpp>
|
||||
#include <ox/std/buffer.hpp>
|
||||
#include <ox/std/defines.hpp>
|
||||
#include <ox/std/fmt.hpp>
|
||||
#include <ox/std/trace.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <keel/context.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
[[nodiscard]]
|
||||
ox::String configPath(keel::Context const&ctx) noexcept;
|
||||
|
||||
template<typename T>
|
||||
ox::Result<T> readConfig(keel::Context &ctx, ox::CRStringView name) noexcept {
|
||||
oxAssert(name != "", "Config type has no TypeName");
|
||||
const auto path = ox::sfmt("/{}.json", name);
|
||||
ox::PassThroughFS fs(configPath(ctx));
|
||||
const auto [buff, err] = fs.read(path);
|
||||
if (err) {
|
||||
oxErrf("Could not read config file: {}\n", toStr(err));
|
||||
return err;
|
||||
}
|
||||
return ox::readOC<T>(buff);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Result<T> readConfig(keel::Context &ctx) noexcept {
|
||||
constexpr auto TypeName = ox::requireModelTypeName<T>();
|
||||
return readConfig<T>(ctx, TypeName);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Error writeConfig(keel::Context &ctx, ox::CRStringView name, T *data) noexcept {
|
||||
oxAssert(name != "", "Config type has no TypeName");
|
||||
const auto path = ox::sfmt("/{}.json", name);
|
||||
ox::PassThroughFS fs(configPath(ctx));
|
||||
if (const auto err = fs.mkdir("/", true)) {
|
||||
oxErrf("Could not create config directory: {}\n", toStr(err));
|
||||
return err;
|
||||
}
|
||||
oxRequireM(buff, ox::writeOC(*data));
|
||||
*buff.back().value = '\n';
|
||||
if (const auto err = fs.write(path, buff.data(), buff.size())) {
|
||||
oxErrf("Could not read config file: {}\n", toStr(err));
|
||||
return OxError(2, "Could not read config file");
|
||||
}
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Error writeConfig(keel::Context &ctx, T *data) noexcept {
|
||||
constexpr auto TypeName = ox::requireModelTypeName<T>();
|
||||
return writeConfig(ctx, TypeName, data);
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
void openConfig(keel::Context &ctx, ox::CRStringView name, Func f) noexcept {
|
||||
oxAssert(name != "", "Config type has no TypeName");
|
||||
const auto [c, err] = readConfig<T>(ctx, name);
|
||||
oxLogError(err);
|
||||
f(&c);
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
void openConfig(keel::Context &ctx, Func f) noexcept {
|
||||
constexpr auto TypeName = ox::requireModelTypeName<T>();
|
||||
openConfig<T>(ctx, TypeName, f);
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
void editConfig(keel::Context &ctx, ox::CRStringView name, Func f) noexcept {
|
||||
oxAssert(name != "", "Config type has no TypeName");
|
||||
auto [c, err] = readConfig<T>(ctx, name);
|
||||
oxLogError(err);
|
||||
f(&c);
|
||||
oxLogError(writeConfig(ctx, name, &c));
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
void editConfig(keel::Context &ctx, Func f) noexcept {
|
||||
constexpr auto TypeName = ox::requireModelTypeName<T>();
|
||||
editConfig<T>(ctx, TypeName, f);
|
||||
}
|
||||
|
||||
}
|
18
src/olympic/studio/modlib/include/studio/context.hpp
Normal file
18
src/olympic/studio/modlib/include/studio/context.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
|
||||
#include "project.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
struct StudioContext {
|
||||
ox::SignalHandler *ui = nullptr;
|
||||
Project *project = nullptr;
|
||||
};
|
||||
|
||||
}
|
136
src/olympic/studio/modlib/include/studio/editor.hpp
Normal file
136
src/olympic/studio/modlib/include/studio/editor.hpp
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
|
||||
#include "undostack.hpp"
|
||||
#include "widget.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
class StudioUI;
|
||||
|
||||
class BaseEditor: public Widget {
|
||||
|
||||
friend StudioUI;
|
||||
|
||||
private:
|
||||
bool m_unsavedChanges = false;
|
||||
bool m_exportable = false;
|
||||
bool m_cutEnabled = false;
|
||||
bool m_copyEnabled = false;
|
||||
bool m_pasteEnabled = false;
|
||||
bool m_requiresConstantRefresh = false;
|
||||
|
||||
public:
|
||||
~BaseEditor() override = default;
|
||||
|
||||
/**
|
||||
* Returns the name of item being edited.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
virtual ox::CStringView itemName() const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual ox::CStringView itemDisplayName() const noexcept;
|
||||
|
||||
virtual void cut();
|
||||
|
||||
virtual void copy();
|
||||
|
||||
virtual void paste();
|
||||
|
||||
virtual void exportFile();
|
||||
|
||||
virtual void keyStateChanged(turbine::Key key, bool down);
|
||||
|
||||
virtual void onActivated() noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
bool requiresConstantRefresh() const noexcept;
|
||||
|
||||
void close() const;
|
||||
|
||||
/**
|
||||
* Save changes to item being edited.
|
||||
*/
|
||||
void save() noexcept;
|
||||
|
||||
/**
|
||||
* Sets indication of item being edited has unsaved changes. Also emits
|
||||
* unsavedChangesChanged signal.
|
||||
*/
|
||||
void setUnsavedChanges(bool);
|
||||
|
||||
[[nodiscard]]
|
||||
bool unsavedChanges() const noexcept;
|
||||
|
||||
void setExportable(bool);
|
||||
|
||||
[[nodiscard]]
|
||||
bool exportable() const;
|
||||
|
||||
void setCutEnabled(bool);
|
||||
|
||||
[[nodiscard]]
|
||||
bool cutEnabled() const;
|
||||
|
||||
void setCopyEnabled(bool);
|
||||
|
||||
[[nodiscard]]
|
||||
bool copyEnabled() const;
|
||||
|
||||
void setPasteEnabled(bool);
|
||||
|
||||
[[nodiscard]]
|
||||
bool pasteEnabled() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Save changes to item being edited.
|
||||
*/
|
||||
virtual ox::Error saveItem() noexcept;
|
||||
|
||||
/**
|
||||
* Returns the undo stack holding changes to the item being edited.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
virtual UndoStack *undoStack() noexcept {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static ox::StringView pathToItemName(ox::CRStringView path) noexcept;
|
||||
|
||||
void setRequiresConstantRefresh(bool value) noexcept;
|
||||
|
||||
// signals
|
||||
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(ox::StringView)> closed;
|
||||
|
||||
};
|
||||
|
||||
class Editor: public studio::BaseEditor {
|
||||
private:
|
||||
studio::UndoStack m_undoStack;
|
||||
|
||||
public:
|
||||
Editor() noexcept;
|
||||
|
||||
UndoStack *undoStack() noexcept final {
|
||||
return &m_undoStack;
|
||||
}
|
||||
|
||||
private:
|
||||
ox::Error markUnsavedChanges(const UndoCommand*) noexcept;
|
||||
};
|
||||
|
||||
|
||||
}
|
27
src/olympic/studio/modlib/include/studio/filedialog.hpp
Normal file
27
src/olympic/studio/modlib/include/studio/filedialog.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/defines.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
struct FDFilterItem {
|
||||
#ifdef OX_OS_Windows
|
||||
using String = ox::Vector<wchar_t>;
|
||||
#else
|
||||
using String = ox::Vector<char>;
|
||||
#endif
|
||||
String name{};
|
||||
String spec{};
|
||||
constexpr FDFilterItem() noexcept = default;
|
||||
FDFilterItem(ox::CRStringView pName, ox::CRStringView pSpec) noexcept;
|
||||
};
|
||||
|
||||
ox::Result<ox::String> saveFile(ox::Vector<FDFilterItem> const&exts) noexcept;
|
||||
|
||||
ox::Result<ox::String> chooseDirectory() noexcept;
|
||||
|
||||
}
|
13
src/olympic/studio/modlib/include/studio/imguiuitl.hpp
Normal file
13
src/olympic/studio/modlib/include/studio/imguiuitl.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
namespace studio::ig {
|
||||
|
||||
void centerNextWindow(turbine::Context &ctx) noexcept;
|
||||
|
||||
}
|
72
src/olympic/studio/modlib/include/studio/itemmaker.hpp
Normal file
72
src/olympic/studio/modlib/include/studio/itemmaker.hpp
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/claw/claw.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
class ItemMaker {
|
||||
public:
|
||||
const ox::String name;
|
||||
const ox::String parentDir;
|
||||
const ox::String fileExt;
|
||||
constexpr explicit ItemMaker(ox::StringView pName, ox::StringView pParentDir, ox::CRStringView pFileExt) noexcept:
|
||||
name(pName),
|
||||
parentDir(pParentDir),
|
||||
fileExt(pFileExt) {
|
||||
}
|
||||
virtual ~ItemMaker() noexcept = default;
|
||||
virtual ox::Error write(turbine::Context &ctx, ox::CRStringView pName) const noexcept = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class ItemMakerT: public ItemMaker {
|
||||
private:
|
||||
const T item;
|
||||
const ox::ClawFormat fmt;
|
||||
public:
|
||||
constexpr ItemMakerT(
|
||||
ox::StringView pDisplayName,
|
||||
ox::StringView pParentDir,
|
||||
ox::StringView fileExt,
|
||||
ox::ClawFormat pFmt = ox::ClawFormat::Metal) noexcept:
|
||||
ItemMaker(pDisplayName, pParentDir, fileExt),
|
||||
fmt(pFmt) {
|
||||
}
|
||||
constexpr ItemMakerT(
|
||||
ox::StringView pDisplayName,
|
||||
ox::StringView pParentDir,
|
||||
ox::StringView fileExt,
|
||||
T pItem,
|
||||
ox::ClawFormat pFmt) noexcept:
|
||||
ItemMaker(pDisplayName, pParentDir, fileExt),
|
||||
item(pItem),
|
||||
fmt(pFmt) {
|
||||
}
|
||||
constexpr ItemMakerT(
|
||||
ox::StringView pDisplayName,
|
||||
ox::StringView pParentDir,
|
||||
ox::StringView fileExt,
|
||||
T &&pItem,
|
||||
ox::ClawFormat pFmt) noexcept:
|
||||
ItemMaker(pDisplayName, pParentDir, fileExt),
|
||||
item(std::move(pItem)),
|
||||
fmt(pFmt) {
|
||||
}
|
||||
ox::Error write(turbine::Context &ctx, ox::CRStringView pName) const noexcept override {
|
||||
const auto path = ox::sfmt("/{}/{}.{}", parentDir, pName, fileExt);
|
||||
auto sctx = turbine::applicationData<studio::StudioContext>(ctx);
|
||||
keel::createUuidMapping(keelCtx(ctx), path, ox::UUID::generate().unwrap());
|
||||
return sctx->project->writeObj(path, item, fmt);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
47
src/olympic/studio/modlib/include/studio/module.hpp
Normal file
47
src/olympic/studio/modlib/include/studio/module.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <ox/std/string.hpp>
|
||||
#include <ox/std/vector.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include <studio/itemmaker.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
class ItemMaker;
|
||||
|
||||
struct EditorMaker {
|
||||
using Func = std::function<ox::Result<class BaseEditor*>(ox::CRStringView)>;
|
||||
ox::Vector<ox::String> fileTypes;
|
||||
Func make;
|
||||
};
|
||||
|
||||
class Module {
|
||||
public:
|
||||
virtual ~Module() noexcept = default;
|
||||
|
||||
virtual ox::Vector<EditorMaker> editors(turbine::Context &ctx) const;
|
||||
|
||||
virtual ox::Vector<ox::UPtr<ItemMaker>> itemMakers(turbine::Context&) const;
|
||||
|
||||
};
|
||||
|
||||
template<typename Editor>
|
||||
[[nodiscard]]
|
||||
studio::EditorMaker editorMaker(turbine::Context &ctx, ox::CRStringView ext) noexcept {
|
||||
return {
|
||||
{ox::String(ext)},
|
||||
[&ctx](ox::CRStringView path) -> ox::Result<studio::BaseEditor*> {
|
||||
return ox::makeCatch<Editor>(ctx, path);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
55
src/olympic/studio/modlib/include/studio/popup.hpp
Normal file
55
src/olympic/studio/modlib/include/studio/popup.hpp
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
#include <ox/std/vec.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
|
||||
class Popup {
|
||||
private:
|
||||
ox::Vec2 m_size;
|
||||
ox::String m_title;
|
||||
public:
|
||||
// emits path parameter
|
||||
ox::Signal<ox::Error(const ox::String&)> finished;
|
||||
|
||||
virtual ~Popup() noexcept = default;
|
||||
|
||||
virtual void open() noexcept = 0;
|
||||
|
||||
virtual void close() noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual bool isOpen() const noexcept = 0;
|
||||
|
||||
virtual void draw(turbine::Context &ctx) noexcept = 0;
|
||||
|
||||
protected:
|
||||
constexpr void setSize(ox::Size sz) noexcept {
|
||||
m_size = {static_cast<float>(sz.width), static_cast<float>(sz.height)};
|
||||
}
|
||||
|
||||
constexpr void setTitle(ox::String title) noexcept {
|
||||
m_title = std::move(title);
|
||||
}
|
||||
|
||||
constexpr const ox::String &title() const noexcept {
|
||||
return m_title;
|
||||
}
|
||||
|
||||
void drawWindow(turbine::Context &ctx, bool *open, std::function<void()> const&drawContents);
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
169
src/olympic/studio/modlib/include/studio/project.hpp
Normal file
169
src/olympic/studio/modlib/include/studio/project.hpp
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/claw/read.hpp>
|
||||
#include <ox/claw/write.hpp>
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/mc/mc.hpp>
|
||||
#include <ox/model/descwrite.hpp>
|
||||
#include <ox/std/hashmap.hpp>
|
||||
|
||||
#include <keel/typestore.hpp>
|
||||
#include <keel/media.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
enum class ProjectEvent {
|
||||
None,
|
||||
FileAdded,
|
||||
// FileRecognized is triggered for all matching files upon a new
|
||||
// subscription to a section of the project and upon the addition of a file.
|
||||
FileRecognized,
|
||||
FileDeleted,
|
||||
FileUpdated,
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr ox::Result<ox::StringView> fileExt(ox::CRStringView path) noexcept {
|
||||
const auto extStart = ox::find(path.crbegin(), path.crend(), '.').offset();
|
||||
if (!extStart) {
|
||||
return OxError(1, "Cannot open a file without valid extension.");
|
||||
}
|
||||
return substr(path, extStart + 1);
|
||||
}
|
||||
|
||||
class Project {
|
||||
private:
|
||||
keel::Context &m_ctx;
|
||||
ox::String m_path;
|
||||
ox::String m_projectDataDir;
|
||||
mutable keel::TypeStore m_typeStore;
|
||||
ox::FileSystem &m_fs;
|
||||
ox::HashMap<ox::String, ox::Vector<ox::String>> m_fileExtFileMap;
|
||||
|
||||
public:
|
||||
explicit Project(keel::Context &ctx, ox::String path, ox::CRStringView projectDataDir) noexcept;
|
||||
|
||||
ox::Error create() noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::FileSystem *romFs() noexcept;
|
||||
|
||||
ox::Error mkdir(ox::CRStringView path) const noexcept;
|
||||
|
||||
/**
|
||||
* Writes a MetalClaw object to the project at the given path.
|
||||
*/
|
||||
template<typename T>
|
||||
ox::Error writeObj(
|
||||
ox::CRStringView path,
|
||||
T const&obj,
|
||||
ox::ClawFormat fmt = ox::ClawFormat::Metal) noexcept;
|
||||
|
||||
template<typename T>
|
||||
ox::Result<T> loadObj(ox::CRStringView path) const noexcept;
|
||||
|
||||
ox::Result<ox::FileStat> stat(ox::CRStringView path) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
bool exists(ox::CRStringView path) const noexcept;
|
||||
|
||||
template<typename Functor>
|
||||
ox::Error subscribe(ProjectEvent e, ox::SignalHandler *tgt, Functor &&slot) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
const ox::Vector<ox::String> &fileList(ox::CRStringView ext) noexcept;
|
||||
|
||||
private:
|
||||
void buildFileIndex() noexcept;
|
||||
|
||||
void indexFile(ox::CRStringView path) noexcept;
|
||||
|
||||
ox::Error writeBuff(ox::CRStringView path, ox::Buffer const&buff) noexcept;
|
||||
|
||||
ox::Result<ox::Buffer> loadBuff(ox::CRStringView path) const noexcept;
|
||||
|
||||
ox::Error lsProcDir(ox::Vector<ox::String> *paths, ox::CRStringView path) const noexcept;
|
||||
|
||||
ox::Result<ox::Vector<ox::String>> listFiles(ox::CRStringView path = "") const noexcept;
|
||||
|
||||
// signals
|
||||
public:
|
||||
ox::Signal<ox::Error(ProjectEvent, ox::CRStringView)> fileEvent;
|
||||
ox::Signal<ox::Error(ox::CRStringView)> fileAdded;
|
||||
// FileRecognized is triggered for all matching files upon a new
|
||||
// subscription to a section of the project and upon the addition of a
|
||||
// file.
|
||||
ox::Signal<ox::Error(ox::CRStringView)> fileRecognized;
|
||||
ox::Signal<ox::Error(ox::CRStringView)> fileDeleted;
|
||||
ox::Signal<ox::Error(ox::CRStringView)> fileUpdated;
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
ox::Error Project::writeObj(ox::CRStringView path, T const&obj, ox::ClawFormat fmt) noexcept {
|
||||
oxRequireM(buff, ox::writeClaw(obj, fmt));
|
||||
// write to FS
|
||||
oxReturnError(writeBuff(path, buff));
|
||||
// write type descriptor
|
||||
if (m_typeStore.get<T>().error) {
|
||||
oxReturnError(ox::buildTypeDef(&m_typeStore, &obj));
|
||||
}
|
||||
// write out type store
|
||||
const auto descPath = ox::sfmt("/{}/type_descriptors", m_projectDataDir);
|
||||
oxReturnError(mkdir(descPath));
|
||||
for (auto const&t : m_typeStore.typeList()) {
|
||||
oxRequireM(typeOut, ox::writeClaw(*t, ox::ClawFormat::Organic));
|
||||
// replace garbage last character with new line
|
||||
*typeOut.back().value = '\n';
|
||||
// write to FS
|
||||
const auto typePath = ox::sfmt("/{}/{}", descPath, buildTypeId(*t));
|
||||
oxReturnError(writeBuff(typePath, typeOut));
|
||||
}
|
||||
oxReturnError(keel::setAsset(m_ctx, path, obj));
|
||||
fileUpdated.emit(path);
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Result<T> Project::loadObj(ox::CRStringView path) const noexcept {
|
||||
oxRequire(buff, loadBuff(path));
|
||||
if constexpr(ox::is_same_v<T, ox::ModelObject>) {
|
||||
return keel::readAsset(&m_typeStore, buff);
|
||||
} else {
|
||||
return keel::readAsset<T>(buff);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Functor>
|
||||
ox::Error Project::subscribe(ProjectEvent e, ox::SignalHandler *tgt, Functor &&slot) const noexcept {
|
||||
switch (e) {
|
||||
case ProjectEvent::None:
|
||||
break;
|
||||
case ProjectEvent::FileAdded:
|
||||
connect(this, &Project::fileAdded, tgt, slot);
|
||||
break;
|
||||
case ProjectEvent::FileRecognized:
|
||||
{
|
||||
oxRequire(files, listFiles());
|
||||
for (auto const&f : files) {
|
||||
slot(f);
|
||||
}
|
||||
connect(this, &Project::fileRecognized, tgt, slot);
|
||||
break;
|
||||
}
|
||||
case ProjectEvent::FileDeleted:
|
||||
connect(this, &Project::fileDeleted, tgt, slot);
|
||||
break;
|
||||
case ProjectEvent::FileUpdated:
|
||||
connect(this, &Project::fileUpdated, tgt, slot);
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
17
src/olympic/studio/modlib/include/studio/studio.hpp
Normal file
17
src/olympic/studio/modlib/include/studio/studio.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/context.hpp>
|
||||
#include <studio/editor.hpp>
|
||||
#include <studio/filedialog.hpp>
|
||||
#include <studio/imguiuitl.hpp>
|
||||
#include <studio/module.hpp>
|
||||
#include <studio/itemmaker.hpp>
|
||||
#include <studio/popup.hpp>
|
||||
#include <studio/project.hpp>
|
||||
#include <studio/task.hpp>
|
||||
#include <studio/undostack.hpp>
|
||||
#include <studio/widget.hpp>
|
33
src/olympic/studio/modlib/include/studio/task.hpp
Normal file
33
src/olympic/studio/modlib/include/studio/task.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
enum class TaskState {
|
||||
Running,
|
||||
Done
|
||||
};
|
||||
|
||||
class Task: public ox::SignalHandler {
|
||||
public:
|
||||
ox::Signal<ox::Error()> finished;
|
||||
~Task() noexcept override = default;
|
||||
virtual TaskState update(turbine::Context &ctx) noexcept = 0;
|
||||
};
|
||||
|
||||
class TaskRunner {
|
||||
private:
|
||||
ox::Vector<ox::UniquePtr<studio::Task>> m_tasks;
|
||||
public:
|
||||
void update(turbine::Context &ctx) noexcept;
|
||||
void add(Task &task) noexcept;
|
||||
};
|
||||
|
||||
}
|
51
src/olympic/studio/modlib/include/studio/undostack.hpp
Normal file
51
src/olympic/studio/modlib/include/studio/undostack.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/std/error.hpp>
|
||||
#include <ox/std/memory.hpp>
|
||||
#include <ox/std/vector.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
class UndoCommand {
|
||||
public:
|
||||
virtual ~UndoCommand() noexcept = default;
|
||||
virtual void redo() noexcept = 0;
|
||||
virtual void undo() noexcept = 0;
|
||||
[[nodiscard]]
|
||||
virtual int commandId() const noexcept = 0;
|
||||
virtual bool mergeWith(const UndoCommand *cmd) noexcept;
|
||||
};
|
||||
|
||||
class UndoStack {
|
||||
private:
|
||||
ox::Vector<ox::UPtr<UndoCommand>> m_stack;
|
||||
std::size_t m_stackIdx = 0;
|
||||
|
||||
public:
|
||||
void push(ox::UPtr<UndoCommand> &&cmd) noexcept;
|
||||
|
||||
void redo() noexcept;
|
||||
|
||||
void undo() noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool canRedo() const noexcept {
|
||||
return m_stackIdx < m_stack.size();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool canUndo() const noexcept {
|
||||
return m_stackIdx;
|
||||
}
|
||||
|
||||
ox::Signal<ox::Error(const studio::UndoCommand*)> redoTriggered;
|
||||
ox::Signal<ox::Error(const studio::UndoCommand*)> undoTriggered;
|
||||
ox::Signal<ox::Error(const studio::UndoCommand*)> changeTriggered;
|
||||
};
|
||||
|
||||
}
|
19
src/olympic/studio/modlib/include/studio/widget.hpp
Normal file
19
src/olympic/studio/modlib/include/studio/widget.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
class Widget: public ox::SignalHandler {
|
||||
public:
|
||||
~Widget() noexcept override = default;
|
||||
virtual void draw(turbine::Context&) noexcept = 0;
|
||||
};
|
||||
|
||||
}
|
Reference in New Issue
Block a user