[olympic] Move keel, turbine, and studio to olympic
This commit is contained in:
1
src/olympic/studio/modlib/CMakeLists.txt
Normal file
1
src/olympic/studio/modlib/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
add_subdirectory(src)
|
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;
|
||||
};
|
||||
|
||||
}
|
46
src/olympic/studio/modlib/src/CMakeLists.txt
Normal file
46
src/olympic/studio/modlib/src/CMakeLists.txt
Normal file
@ -0,0 +1,46 @@
|
||||
add_library(
|
||||
Studio
|
||||
configio.cpp
|
||||
editor.cpp
|
||||
imguiutil.cpp
|
||||
module.cpp
|
||||
popup.cpp
|
||||
project.cpp
|
||||
task.cpp
|
||||
undostack.cpp
|
||||
widget.cpp
|
||||
filedialog_nfd.cpp
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
Studio PUBLIC
|
||||
../include
|
||||
)
|
||||
|
||||
include_directories(
|
||||
SYSTEM
|
||||
${GTK3_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
Studio PUBLIC
|
||||
nfd
|
||||
OxEvent
|
||||
GlUtils
|
||||
Turbine
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
Studio
|
||||
DESTINATION
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY
|
||||
../include/studio
|
||||
DESTINATION
|
||||
include/studio/
|
||||
)
|
31
src/olympic/studio/modlib/src/configio.cpp
Normal file
31
src/olympic/studio/modlib/src/configio.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <studio/configio.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
constexpr auto ConfigDir = [] {
|
||||
switch (ox::defines::OS) {
|
||||
case ox::OS::Darwin:
|
||||
return "{}/Library/Preferences/{}";
|
||||
case ox::OS::DragonFlyBSD:
|
||||
case ox::OS::FreeBSD:
|
||||
case ox::OS::Linux:
|
||||
case ox::OS::NetBSD:
|
||||
case ox::OS::OpenBSD:
|
||||
return "{}/.config/{}";
|
||||
case ox::OS::Windows:
|
||||
return R"({}/AppData/Local/{})";
|
||||
case ox::OS::BareMetal:
|
||||
return "";
|
||||
}
|
||||
}();
|
||||
|
||||
ox::String configPath(const keel::Context &ctx) noexcept {
|
||||
const auto homeDir = std::getenv(ox::defines::OS == ox::OS::Windows ? "USERPROFILE" : "HOME");
|
||||
return ox::sfmt(ConfigDir, homeDir, ctx.appName);
|
||||
}
|
||||
|
||||
}
|
124
src/olympic/studio/modlib/src/editor.cpp
Normal file
124
src/olympic/studio/modlib/src/editor.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <studio/editor.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
ox::CStringView BaseEditor::itemDisplayName() const noexcept {
|
||||
return itemName();
|
||||
}
|
||||
|
||||
void BaseEditor::cut() {
|
||||
}
|
||||
|
||||
void BaseEditor::copy() {
|
||||
}
|
||||
|
||||
void BaseEditor::paste() {
|
||||
}
|
||||
|
||||
void BaseEditor::exportFile() {
|
||||
}
|
||||
|
||||
void BaseEditor::keyStateChanged(turbine::Key, bool) {
|
||||
}
|
||||
|
||||
void BaseEditor::onActivated() noexcept {
|
||||
}
|
||||
|
||||
bool BaseEditor::requiresConstantRefresh() const noexcept {
|
||||
return m_requiresConstantRefresh;
|
||||
}
|
||||
|
||||
void BaseEditor::close() const {
|
||||
this->closed.emit(itemName());
|
||||
}
|
||||
|
||||
void BaseEditor::save() noexcept {
|
||||
const auto err = saveItem();
|
||||
if (!err) {
|
||||
setUnsavedChanges(false);
|
||||
} else {
|
||||
if constexpr(ox::defines::Debug) {
|
||||
oxErrorf("Could not save file {}: {} ({}:{})", itemName(), toStr(err), err.file, err.line);
|
||||
} else {
|
||||
oxErrorf("Could not save file {}: {}", itemName(), toStr(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BaseEditor::setUnsavedChanges(bool uc) {
|
||||
m_unsavedChanges = uc;
|
||||
unsavedChangesChanged.emit(uc);
|
||||
}
|
||||
|
||||
bool BaseEditor::unsavedChanges() const noexcept {
|
||||
return m_unsavedChanges;
|
||||
}
|
||||
|
||||
void BaseEditor::setExportable(bool exportable) {
|
||||
m_exportable = exportable;
|
||||
exportableChanged.emit(exportable);
|
||||
}
|
||||
|
||||
bool BaseEditor::exportable() const {
|
||||
return m_exportable;
|
||||
}
|
||||
|
||||
void BaseEditor::setCutEnabled(bool v) {
|
||||
m_cutEnabled = v;
|
||||
cutEnabledChanged.emit(v);
|
||||
}
|
||||
|
||||
bool BaseEditor::cutEnabled() const {
|
||||
return m_cutEnabled;
|
||||
}
|
||||
|
||||
void BaseEditor::setCopyEnabled(bool v) {
|
||||
m_copyEnabled = v;
|
||||
copyEnabledChanged.emit(v);
|
||||
}
|
||||
|
||||
bool BaseEditor::copyEnabled() const {
|
||||
return m_copyEnabled;
|
||||
}
|
||||
|
||||
void BaseEditor::setPasteEnabled(bool v) {
|
||||
m_pasteEnabled = v;
|
||||
pasteEnabledChanged.emit(v);
|
||||
}
|
||||
|
||||
bool BaseEditor::pasteEnabled() const {
|
||||
return m_pasteEnabled;
|
||||
}
|
||||
|
||||
ox::Error BaseEditor::saveItem() noexcept {
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
ox::StringView BaseEditor::pathToItemName(ox::CRStringView path) noexcept {
|
||||
const auto lastSlash = std::find(path.rbegin(), path.rend(), '/').offset();
|
||||
return substr(path, lastSlash + 1);
|
||||
}
|
||||
|
||||
void BaseEditor::setRequiresConstantRefresh(bool value) noexcept {
|
||||
m_requiresConstantRefresh = value;
|
||||
}
|
||||
|
||||
|
||||
Editor::Editor() noexcept {
|
||||
m_undoStack.changeTriggered.connect(this, &Editor::markUnsavedChanges);
|
||||
}
|
||||
|
||||
ox::Error Editor::markUnsavedChanges(const UndoCommand*) noexcept {
|
||||
setUnsavedChanges(true);
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
}
|
57
src/olympic/studio/modlib/src/filedialog_nfd.cpp
Normal file
57
src/olympic/studio/modlib/src/filedialog_nfd.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nfd.hpp>
|
||||
|
||||
#include <ox/std/error.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <studio/filedialog.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
FDFilterItem::FDFilterItem(ox::CRStringView pName, ox::CRStringView pSpec) noexcept {
|
||||
name.resize(pName.len() + 1);
|
||||
ox_strncpy(name.data(), pName.data(), pName.len());
|
||||
spec.resize(pSpec.len() + 1);
|
||||
ox_strncpy(spec.data(), pSpec.data(), pSpec.len());
|
||||
}
|
||||
|
||||
static ox::Result<ox::String> toResult(nfdresult_t r, NFD::UniquePathN const&path) noexcept {
|
||||
switch (r) {
|
||||
case NFD_OKAY: {
|
||||
ox::String out;
|
||||
for (auto i = 0u; path.get()[i]; ++i) {
|
||||
const auto c = static_cast<char>(path.get()[i]);
|
||||
oxIgnoreError(out.append(&c, 1));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
case NFD_CANCEL:
|
||||
return OxError(1, "Operation cancelled");
|
||||
default:
|
||||
return OxError(2, NFD::GetError());
|
||||
}
|
||||
}
|
||||
|
||||
ox::Result<ox::String> saveFile(ox::Vector<FDFilterItem> const&filters) noexcept {
|
||||
NFD::Guard const guard;
|
||||
NFD::UniquePathN path;
|
||||
ox::Vector<nfdnfilteritem_t, 5> filterItems(filters.size());
|
||||
for (auto i = 0u; const auto &f : filters) {
|
||||
filterItems[i].name = f.name.data();
|
||||
filterItems[i].spec = f.spec.data();
|
||||
++i;
|
||||
}
|
||||
auto const filterItemsCnt = static_cast<nfdfiltersize_t>(filterItems.size());
|
||||
return toResult(NFD::SaveDialog(path, filterItems.data(), filterItemsCnt), path);
|
||||
}
|
||||
|
||||
ox::Result<ox::String> chooseDirectory() noexcept {
|
||||
const NFD::Guard guard;
|
||||
NFD::UniquePathN path;
|
||||
return toResult(NFD::PickFolder(path), path);
|
||||
}
|
||||
|
||||
}
|
19
src/olympic/studio/modlib/src/imguiutil.cpp
Normal file
19
src/olympic/studio/modlib/src/imguiutil.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <turbine/gfx.hpp>
|
||||
|
||||
namespace studio::ig {
|
||||
|
||||
void centerNextWindow(turbine::Context &ctx) noexcept {
|
||||
const auto sz = turbine::getScreenSize(ctx);
|
||||
const auto screenW = static_cast<float>(sz.width);
|
||||
const auto screenH = static_cast<float>(sz.height);
|
||||
const auto mod = ImGui::GetWindowDpiScale() * 2;
|
||||
ImGui::SetNextWindowPos(ImVec2(screenW / mod, screenH / mod), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
}
|
||||
|
||||
}
|
17
src/olympic/studio/modlib/src/module.cpp
Normal file
17
src/olympic/studio/modlib/src/module.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <studio/module.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
ox::Vector<EditorMaker> Module::editors(turbine::Context&) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Vector<ox::UPtr<ItemMaker>> Module::itemMakers(turbine::Context&) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
20
src/olympic/studio/modlib/src/popup.cpp
Normal file
20
src/olympic/studio/modlib/src/popup.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <studio/imguiuitl.hpp>
|
||||
#include <studio/popup.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
void Popup::drawWindow(turbine::Context &ctx, bool *open, std::function<void()> const&drawContents) {
|
||||
studio::ig::centerNextWindow(ctx);
|
||||
ImGui::SetNextWindowSize(static_cast<ImVec2>(m_size));
|
||||
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
||||
if (ImGui::BeginPopupModal(m_title.c_str(), open, modalFlags)) {
|
||||
drawContents();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
136
src/olympic/studio/modlib/src/project.cpp
Normal file
136
src/olympic/studio/modlib/src/project.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
#include <ox/std/std.hpp>
|
||||
|
||||
#include <keel/module.hpp>
|
||||
|
||||
#include <studio/project.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
static void generateTypes(ox::TypeStore &ts) noexcept {
|
||||
for (const auto mod : keel::modules()) {
|
||||
for (auto gen : mod->types()) {
|
||||
oxLogError(gen(ts));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Project::Project(keel::Context &ctx, ox::String path, ox::CRStringView projectDataDir) noexcept:
|
||||
m_ctx(ctx),
|
||||
m_path(std::move(path)),
|
||||
m_projectDataDir(projectDataDir),
|
||||
m_typeStore(*m_ctx.rom, ox::sfmt("/{}/type_descriptors", projectDataDir)),
|
||||
m_fs(*m_ctx.rom) {
|
||||
oxTracef("studio", "Project: {}", m_path);
|
||||
generateTypes(m_typeStore);
|
||||
buildFileIndex();
|
||||
}
|
||||
|
||||
ox::Error Project::create() noexcept {
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directory(m_path.toStdString(), ec);
|
||||
return OxError(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: mkdir failed");
|
||||
}
|
||||
|
||||
ox::FileSystem *Project::romFs() noexcept {
|
||||
return &m_fs;
|
||||
}
|
||||
|
||||
ox::Error Project::mkdir(ox::CRStringView path) const noexcept {
|
||||
oxReturnError(m_fs.mkdir(path, true));
|
||||
fileUpdated.emit(path);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Result<ox::FileStat> Project::stat(ox::CRStringView path) const noexcept {
|
||||
return m_fs.stat(path);
|
||||
}
|
||||
|
||||
bool Project::exists(ox::CRStringView path) const noexcept {
|
||||
return m_fs.stat(path).error == 0;
|
||||
}
|
||||
|
||||
const ox::Vector<ox::String> &Project::fileList(ox::CRStringView ext) noexcept {
|
||||
return m_fileExtFileMap[ext];
|
||||
}
|
||||
|
||||
void Project::buildFileIndex() noexcept {
|
||||
auto [files, err] = listFiles();
|
||||
if (err) {
|
||||
oxLogError(err);
|
||||
return;
|
||||
}
|
||||
m_fileExtFileMap.clear();
|
||||
std::sort(files.begin(), files.end());
|
||||
for (const auto &file : files) {
|
||||
if (!beginsWith(file, ox::sfmt("/.{}/", m_projectDataDir))) {
|
||||
indexFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Project::indexFile(ox::CRStringView path) noexcept {
|
||||
const auto [ext, err] = fileExt(path);
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
m_fileExtFileMap[ext].emplace_back(path);
|
||||
}
|
||||
|
||||
ox::Error Project::writeBuff(ox::CRStringView path, ox::Buffer const&buff) noexcept {
|
||||
constexpr auto HdrSz = 40;
|
||||
ox::Buffer outBuff;
|
||||
outBuff.reserve(buff.size() + HdrSz);
|
||||
ox::BufferWriter writer(&outBuff);
|
||||
const auto [uuid, err] = m_ctx.pathToUuid.at(path);
|
||||
if (!err) {
|
||||
oxReturnError(keel::writeUuidHeader(writer, *uuid));
|
||||
}
|
||||
oxReturnError(writer.write(buff.data(), buff.size()));
|
||||
const auto newFile = m_fs.stat(path).error != 0;
|
||||
oxReturnError(m_fs.write(path, outBuff.data(), outBuff.size(), ox::FileType::NormalFile));
|
||||
if (newFile) {
|
||||
fileAdded.emit(path);
|
||||
indexFile(path);
|
||||
} else {
|
||||
fileUpdated.emit(path);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Result<ox::Buffer> Project::loadBuff(ox::CRStringView path) const noexcept {
|
||||
return m_fs.read(path);
|
||||
}
|
||||
|
||||
ox::Error Project::lsProcDir(ox::Vector<ox::String> *paths, ox::CRStringView path) const noexcept {
|
||||
oxRequire(files, m_fs.ls(path));
|
||||
for (const auto &name : files) {
|
||||
auto fullPath = ox::sfmt("{}/{}", path, name);
|
||||
oxRequire(stat, m_fs.stat(ox::StringView(fullPath)));
|
||||
switch (stat.fileType) {
|
||||
case ox::FileType::NormalFile:
|
||||
paths->emplace_back(std::move(fullPath));
|
||||
break;
|
||||
case ox::FileType::Directory:
|
||||
oxReturnError(lsProcDir(paths, fullPath));
|
||||
break;
|
||||
case ox::FileType::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Result<ox::Vector<ox::String>> Project::listFiles(ox::CRStringView path) const noexcept {
|
||||
ox::Vector<ox::String> paths;
|
||||
oxReturnError(lsProcDir(&paths, path));
|
||||
return paths;
|
||||
}
|
||||
|
||||
}
|
25
src/olympic/studio/modlib/src/task.cpp
Normal file
25
src/olympic/studio/modlib/src/task.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <studio/task.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
void TaskRunner::update(turbine::Context &ctx) noexcept {
|
||||
oxIgnoreError(m_tasks.erase(std::remove_if(m_tasks.begin(), m_tasks.end(), [&](ox::UPtr<studio::Task> &t) {
|
||||
const auto done = t->update(ctx) == TaskState::Done;
|
||||
if (done) {
|
||||
t->finished.emit();
|
||||
}
|
||||
return done;
|
||||
})));
|
||||
}
|
||||
|
||||
void TaskRunner::add(Task &task) noexcept {
|
||||
m_tasks.emplace_back(&task);
|
||||
}
|
||||
|
||||
}
|
44
src/olympic/studio/modlib/src/undostack.cpp
Normal file
44
src/olympic/studio/modlib/src/undostack.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <studio/undostack.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
bool UndoCommand::mergeWith(const UndoCommand*) noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
void UndoStack::push(ox::UPtr<UndoCommand> &&cmd) noexcept {
|
||||
for (const auto i = m_stackIdx; i < m_stack.size();) {
|
||||
oxIgnoreError(m_stack.erase(i));
|
||||
}
|
||||
cmd->redo();
|
||||
redoTriggered.emit(cmd.get());
|
||||
changeTriggered.emit(cmd.get());
|
||||
if (m_stack.empty() || !(*m_stack.back().value)->mergeWith(cmd.get())) {
|
||||
m_stack.emplace_back(std::move(cmd));
|
||||
++m_stackIdx;
|
||||
}
|
||||
}
|
||||
|
||||
void UndoStack::redo() noexcept {
|
||||
if (m_stackIdx < m_stack.size()) {
|
||||
auto &c = m_stack[m_stackIdx++];
|
||||
c->redo();
|
||||
redoTriggered.emit(c.get());
|
||||
changeTriggered.emit(c.get());
|
||||
}
|
||||
}
|
||||
|
||||
void UndoStack::undo() noexcept {
|
||||
if (m_stackIdx) {
|
||||
auto &c = m_stack[--m_stackIdx];
|
||||
c->undo();
|
||||
undoTriggered.emit(c.get());
|
||||
changeTriggered.emit(c.get());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
9
src/olympic/studio/modlib/src/widget.cpp
Normal file
9
src/olympic/studio/modlib/src/widget.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <studio/widget.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
}
|
Reference in New Issue
Block a user