[olympic] Move keel, turbine, and studio to olympic

This commit is contained in:
2023-12-11 22:48:08 -06:00
parent a60765b338
commit e2545a956b
96 changed files with 32 additions and 24 deletions

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

View 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;
};
}

View 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;
};
}

View 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;
}

View 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;
}

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

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

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

View 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 {};
}
}

View 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>

View 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;
};
}

View 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;
};
}

View 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;
};
}