Merge commit '587dd924142c959b812ca340eab52af35ac4096c' as 'deps/nostalgia'
This commit is contained in:
21
deps/nostalgia/src/olympic/CMakeLists.txt
vendored
Normal file
21
deps/nostalgia/src/olympic/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
if(BUILDCORE_TARGET STREQUAL "gba")
|
||||
project(Olympic ASM CXX)
|
||||
else()
|
||||
project(Olympic CXX)
|
||||
endif()
|
||||
|
||||
set(OLYMPIC_BUILD_STUDIO ON CACHE BOOL "Build Studio")
|
||||
|
||||
if(BUILDCORE_TARGET STREQUAL "gba")
|
||||
set(OLYMPIC_BUILD_STUDIO OFF)
|
||||
set(TURBINE_BUILD_TYPE "GBA")
|
||||
else()
|
||||
set(TURBINE_BUILD_TYPE "Native")
|
||||
endif()
|
||||
|
||||
add_subdirectory(applib)
|
||||
add_subdirectory(keel)
|
||||
add_subdirectory(turbine)
|
||||
if(${OLYMPIC_BUILD_STUDIO})
|
||||
add_subdirectory(studio)
|
||||
endif()
|
9
deps/nostalgia/src/olympic/applib/CMakeLists.txt
vendored
Normal file
9
deps/nostalgia/src/olympic/applib/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
add_library(
|
||||
OlympicApplib INTERFACE
|
||||
)
|
||||
|
||||
target_sources(
|
||||
OlympicApplib INTERFACE
|
||||
applib.cpp
|
||||
)
|
68
deps/nostalgia/src/olympic/applib/applib.cpp
vendored
Normal file
68
deps/nostalgia/src/olympic/applib/applib.cpp
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/logconn/def.hpp>
|
||||
#include <ox/logconn/logconn.hpp>
|
||||
|
||||
#ifndef OLYMPIC_PROJECT_NAME
|
||||
#define OLYMPIC_PROJECT_NAME "OlympicProject"
|
||||
#endif
|
||||
|
||||
#ifndef OLYMPIC_APP_NAME
|
||||
#define OLYMPIC_APP_NAME "App"
|
||||
#endif
|
||||
|
||||
#ifndef OLYMPIC_PROJECT_NAMESPACE
|
||||
#define OLYMPIC_PROJECT_NAMESPACE project
|
||||
#endif
|
||||
|
||||
#ifndef OLYMPIC_PROJECT_DATADIR
|
||||
#define OLYMPIC_PROJECT_DATADIR ".keel"
|
||||
#endif
|
||||
|
||||
#ifndef OLYMPIC_LOAD_KEEL_MODULES
|
||||
#define OLYMPIC_LOAD_KEEL_MODULES 1
|
||||
#endif
|
||||
|
||||
#ifndef OLYMPIC_LOAD_STUDIO_MODULES
|
||||
#define OLYMPIC_LOAD_STUDIO_MODULES 0
|
||||
#endif
|
||||
|
||||
namespace olympic {
|
||||
|
||||
ox::Error run(
|
||||
ox::StringView project,
|
||||
ox::StringView appName,
|
||||
ox::StringView projectDataDir,
|
||||
int argc,
|
||||
char const**argv) noexcept;
|
||||
|
||||
}
|
||||
|
||||
namespace OLYMPIC_PROJECT_NAMESPACE {
|
||||
void registerKeelModules() noexcept;
|
||||
void registerStudioModules() noexcept;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int WinMain() {
|
||||
auto const argc = __argc;
|
||||
auto const argv = const_cast<const char**>(__argv);
|
||||
#else
|
||||
int main(int argc, char const**argv) {
|
||||
#endif
|
||||
OX_INIT_DEBUG_LOGGER(loggerConn, OLYMPIC_PROJECT_NAME " " OLYMPIC_APP_NAME)
|
||||
#if OLYMPIC_LOAD_KEEL_MODULES
|
||||
OLYMPIC_PROJECT_NAMESPACE::registerKeelModules();
|
||||
#endif
|
||||
#if OLYMPIC_LOAD_STUDIO_MODULES
|
||||
OLYMPIC_PROJECT_NAMESPACE::registerStudioModules();
|
||||
#endif
|
||||
auto const err = olympic::run(OLYMPIC_PROJECT_NAME, OLYMPIC_APP_NAME, OLYMPIC_PROJECT_DATADIR, argc, argv);
|
||||
oxAssert(err, "Something went wrong...");
|
||||
if (err) {
|
||||
oxErrf("Failure: {}\n", toStr(err));
|
||||
}
|
||||
return static_cast<int>(err);
|
||||
}
|
5
deps/nostalgia/src/olympic/keel/CMakeLists.txt
vendored
Normal file
5
deps/nostalgia/src/olympic/keel/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
add_subdirectory(src)
|
||||
|
||||
if(TURBINE_BUILD_TYPE STREQUAL "Native")
|
||||
add_subdirectory(test)
|
||||
endif()
|
45
deps/nostalgia/src/olympic/keel/include/keel/asset.hpp
vendored
Normal file
45
deps/nostalgia/src/olympic/keel/include/keel/asset.hpp
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/claw/claw.hpp>
|
||||
#include <ox/fs/fs.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
constexpr auto K1HdrSz = 40;
|
||||
|
||||
ox::Result<ox::UUID> readUuidHeader(ox::Buffer const&buff) noexcept;
|
||||
|
||||
ox::Result<ox::UUID> readUuidHeader(const char *buff, std::size_t buffLen) noexcept;
|
||||
|
||||
ox::Error writeUuidHeader(ox::Writer_c auto &writer, ox::UUID const&uuid) noexcept {
|
||||
oxReturnError(write(writer, "K1;"));
|
||||
oxReturnError(uuid.toString(writer));
|
||||
return writer.put(';');
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Result<T> readAsset(ox::Buffer const&buff) noexcept {
|
||||
std::size_t offset = 0;
|
||||
const auto err = readUuidHeader(buff).error;
|
||||
if (!err) {
|
||||
offset = K1HdrSz; // the size of K1 headers
|
||||
}
|
||||
return ox::readClaw<T>(buff.data() + offset, buff.size() - offset);
|
||||
}
|
||||
|
||||
ox::Result<ox::ModelObject> readAsset(ox::TypeStore &ts, ox::Buffer const&buff) noexcept;
|
||||
|
||||
struct AssetHdr {
|
||||
ox::UUID uuid;
|
||||
ox::ClawHeader clawHdr;
|
||||
};
|
||||
|
||||
ox::Result<AssetHdr> readAssetHeader(char const*buff, std::size_t buffLen) noexcept;
|
||||
|
||||
ox::Result<AssetHdr> readAssetHeader(ox::Buffer const&buff) noexcept;
|
||||
|
||||
}
|
301
deps/nostalgia/src/olympic/keel/include/keel/assetmanager.hpp
vendored
Normal file
301
deps/nostalgia/src/olympic/keel/include/keel/assetmanager.hpp
vendored
Normal file
@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/model/typenamecatcher.hpp>
|
||||
#include <ox/std/hashmap.hpp>
|
||||
#include <ox/std/utility.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
class AssetManager;
|
||||
|
||||
template<typename T>
|
||||
class AssetRef;
|
||||
|
||||
#ifndef OX_BARE_METAL
|
||||
template<typename T>
|
||||
class AssetContainer {
|
||||
|
||||
friend AssetManager;
|
||||
friend AssetRef<T>;
|
||||
|
||||
protected:
|
||||
ox::Signal<ox::Error()> updated;
|
||||
|
||||
private:
|
||||
T m_obj;
|
||||
mutable int m_references = 0;
|
||||
|
||||
public:
|
||||
template<class... Args>
|
||||
explicit constexpr AssetContainer(Args&&... args): m_obj(ox::forward<Args>(args)...) {
|
||||
}
|
||||
|
||||
AssetContainer(AssetContainer const&) = delete;
|
||||
AssetContainer(AssetContainer&&) = delete;
|
||||
AssetContainer &operator=(AssetContainer const&) = delete;
|
||||
AssetContainer &operator=(AssetContainer&&) = delete;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr T *get() noexcept {
|
||||
return &m_obj;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr T const*get() const noexcept {
|
||||
return &m_obj;
|
||||
}
|
||||
|
||||
constexpr void set(T &&val) {
|
||||
m_obj = std::move(val);
|
||||
}
|
||||
|
||||
constexpr void set(T const&val) {
|
||||
m_obj = val;
|
||||
}
|
||||
|
||||
protected:
|
||||
constexpr void incRefs() const noexcept {
|
||||
++m_references;
|
||||
}
|
||||
|
||||
constexpr void decRefs() const noexcept {
|
||||
--m_references;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr int references() const noexcept {
|
||||
return m_references;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class AssetRef: public ox::SignalHandler {
|
||||
private:
|
||||
AssetContainer<T> const*m_ctr = nullptr;
|
||||
|
||||
public:
|
||||
ox::Signal<ox::Error()> updated;
|
||||
|
||||
explicit constexpr AssetRef(AssetContainer<T> const*c = nullptr) noexcept;
|
||||
|
||||
constexpr AssetRef(AssetRef const&h) noexcept;
|
||||
|
||||
constexpr AssetRef(AssetRef &&h) noexcept;
|
||||
|
||||
~AssetRef() noexcept override {
|
||||
if (m_ctr) {
|
||||
m_ctr->decRefs();
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr T const*get() const noexcept {
|
||||
if (m_ctr) {
|
||||
return m_ctr->get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
constexpr T const&operator*() const noexcept {
|
||||
return *m_ctr->get();
|
||||
}
|
||||
|
||||
constexpr T const*operator->() const noexcept {
|
||||
return m_ctr->get();
|
||||
}
|
||||
|
||||
AssetRef &operator=(AssetRef const&h) noexcept {
|
||||
if (this == &h) {
|
||||
return *this;
|
||||
}
|
||||
if (m_ctr) {
|
||||
m_ctr->decRefs();
|
||||
oxIgnoreError(m_ctr->updated.disconnectObject(this));
|
||||
}
|
||||
m_ctr = h.m_ctr;
|
||||
if (m_ctr) {
|
||||
m_ctr->updated.connect(&updated, &ox::Signal<ox::Error()>::emitCheckError);
|
||||
m_ctr->incRefs();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
AssetRef &operator=(AssetRef &&h) noexcept {
|
||||
if (this == &h) {
|
||||
return *this;
|
||||
}
|
||||
if (m_ctr) {
|
||||
m_ctr->decRefs();
|
||||
oxIgnoreError(m_ctr->updated.disconnectObject(this));
|
||||
}
|
||||
m_ctr = h.m_ctr;
|
||||
if (m_ctr) {
|
||||
oxIgnoreError(m_ctr->updated.disconnectObject(&h));
|
||||
m_ctr->updated.connect(this, &AssetRef::emitUpdated);
|
||||
}
|
||||
h.m_ctr = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit constexpr operator bool() const noexcept {
|
||||
return m_ctr;
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr ox::Error emitUpdated() const noexcept {
|
||||
updated.emit();
|
||||
return OxError(0);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
constexpr AssetRef<T>::AssetRef(AssetContainer<T> const*c) noexcept: m_ctr(c) {
|
||||
if (m_ctr) {
|
||||
m_ctr->updated.connect(this, &AssetRef::emitUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr AssetRef<T>::AssetRef(AssetRef const&h) noexcept: m_ctr(h.m_ctr) {
|
||||
if (m_ctr) {
|
||||
m_ctr->updated.connect(this, &AssetRef::emitUpdated);
|
||||
m_ctr->incRefs();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr AssetRef<T>::AssetRef(AssetRef &&h) noexcept: m_ctr(h.m_ctr) {
|
||||
if (m_ctr) {
|
||||
m_ctr->updated.connect(this, &AssetRef::emitUpdated);
|
||||
}
|
||||
h.m_ctr = nullptr;
|
||||
}
|
||||
|
||||
class AssetManager {
|
||||
private:
|
||||
class AssetTypeManagerBase {
|
||||
public:
|
||||
virtual ~AssetTypeManagerBase() = default;
|
||||
|
||||
virtual void gc() noexcept = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class AssetTypeManager: public AssetTypeManagerBase {
|
||||
private:
|
||||
ox::HashMap<ox::String, ox::UPtr<AssetContainer<T>>> m_cache;
|
||||
|
||||
public:
|
||||
ox::Result<AssetRef<T>> getAsset(ox::StringView const&assetId) const noexcept {
|
||||
auto out = m_cache.at(assetId);
|
||||
oxReturnError(out);
|
||||
return AssetRef<T>(out.value->get());
|
||||
}
|
||||
|
||||
ox::Result<AssetRef<T>> setAsset(ox::StringView const&assetId, T const&obj) noexcept {
|
||||
auto &p = m_cache[assetId];
|
||||
if (!p) {
|
||||
p = ox::make_unique<AssetContainer<T>>(obj);
|
||||
} else {
|
||||
p->set(obj);
|
||||
p->updated.emit();
|
||||
}
|
||||
return AssetRef<T>(p.get());
|
||||
}
|
||||
|
||||
ox::Result<AssetRef<T>> setAsset(ox::StringView const&assetId, T &&obj) noexcept {
|
||||
auto &p = m_cache[assetId];
|
||||
if (!p) {
|
||||
p = ox::make_unique<AssetContainer<T>>(obj);
|
||||
} else {
|
||||
p->set(std::move(obj));
|
||||
p->updated.emit();
|
||||
}
|
||||
return AssetRef<T>(p.get());
|
||||
}
|
||||
|
||||
void gc() noexcept final {
|
||||
for (auto const&ack : m_cache.keys()) {
|
||||
auto &ac = m_cache[ack];
|
||||
if (!ac->references()) {
|
||||
m_cache.erase(ack);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ox::HashMap<ox::String, ox::UPtr<AssetTypeManagerBase>> m_assetTypeManagers;
|
||||
|
||||
template<typename T>
|
||||
AssetTypeManager<T> *getTypeManager() noexcept {
|
||||
constexpr auto typeName = ox::requireModelTypeName<T>();
|
||||
static_assert(ox::StringView(typeName) != "", "Types must have TypeName to use AssetManager");
|
||||
auto &am = m_assetTypeManagers[typeName];
|
||||
if (!am) {
|
||||
am = ox::make_unique<AssetTypeManager<T>>();
|
||||
}
|
||||
return dynamic_cast<AssetTypeManager<T>*>(am.get());
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
ox::Result<AssetRef<T>> getAsset(ox::CRStringView assetId) noexcept {
|
||||
auto m = getTypeManager<T>();
|
||||
return m->getAsset(assetId);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Result<AssetRef<T>> setAsset(ox::CRStringView assetId, T const&obj) noexcept {
|
||||
auto m = getTypeManager<T>();
|
||||
return m->setAsset(assetId, obj);
|
||||
}
|
||||
|
||||
void gc() noexcept {
|
||||
for (auto const&amk : m_assetTypeManagers.keys()) {
|
||||
auto &am = m_assetTypeManagers[amk];
|
||||
am->gc();
|
||||
}
|
||||
}
|
||||
};
|
||||
#else
|
||||
template<typename T>
|
||||
class AssetRef {
|
||||
private:
|
||||
T const*const m_obj = nullptr;
|
||||
|
||||
public:
|
||||
constexpr AssetRef() noexcept = default;
|
||||
|
||||
explicit constexpr AssetRef(T const*obj) noexcept: m_obj(obj) {
|
||||
}
|
||||
|
||||
constexpr T const*get() const noexcept {
|
||||
return m_obj;
|
||||
}
|
||||
|
||||
constexpr T const&operator*() const & noexcept {
|
||||
return *m_obj;
|
||||
}
|
||||
|
||||
constexpr T const&&operator*() const && noexcept {
|
||||
return *m_obj;
|
||||
}
|
||||
|
||||
constexpr T const*operator->() const noexcept {
|
||||
return m_obj;
|
||||
}
|
||||
|
||||
explicit constexpr operator bool() const noexcept {
|
||||
return m_obj;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
}
|
39
deps/nostalgia/src/olympic/keel/include/keel/context.hpp
vendored
Normal file
39
deps/nostalgia/src/olympic/keel/include/keel/context.hpp
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/std/memory.hpp>
|
||||
|
||||
#include "assetmanager.hpp"
|
||||
|
||||
namespace keel {
|
||||
|
||||
class Context;
|
||||
using PackTransform = ox::Error(*)(Context&, ox::Buffer &clawData);
|
||||
|
||||
class Context {
|
||||
public:
|
||||
ox::UPtr<ox::FileSystem> rom;
|
||||
ox::BasicString<32> appName{"Keel App"};
|
||||
#ifndef OX_BARE_METAL
|
||||
AssetManager assetManager;
|
||||
ox::HashMap<ox::String, ox::UUID> pathToUuid;
|
||||
ox::HashMap<ox::UUIDStr, ox::String> uuidToPath;
|
||||
ox::Vector<class BaseConverter const*> converters;
|
||||
ox::Vector<PackTransform> packTransforms;
|
||||
#else
|
||||
std::size_t preloadSectionOffset = 0;
|
||||
#endif
|
||||
|
||||
constexpr Context() noexcept = default;
|
||||
Context(Context const&) noexcept = delete;
|
||||
Context(Context&&) noexcept = delete;
|
||||
Context &operator=(Context const&) noexcept = delete;
|
||||
Context &operator=(Context&&) noexcept = delete;
|
||||
constexpr virtual ~Context() noexcept = default;
|
||||
};
|
||||
|
||||
}
|
26
deps/nostalgia/src/olympic/keel/include/keel/keel.hpp
vendored
Normal file
26
deps/nostalgia/src/olympic/keel/include/keel/keel.hpp
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/memory.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
#include "media.hpp"
|
||||
#include "module.hpp"
|
||||
#include "pack.hpp"
|
||||
#include "typestore.hpp"
|
||||
|
||||
namespace keel {
|
||||
|
||||
ox::Error init(
|
||||
keel::Context &ctx,
|
||||
ox::UPtr<ox::FileSystem> &&fs,
|
||||
ox::CRStringView appName) noexcept;
|
||||
|
||||
ox::Result<ox::UPtr<Context>> init(
|
||||
ox::UPtr<ox::FileSystem> &&fs,
|
||||
ox::CRStringView appName) noexcept;
|
||||
|
||||
}
|
169
deps/nostalgia/src/olympic/keel/include/keel/media.hpp
vendored
Normal file
169
deps/nostalgia/src/olympic/keel/include/keel/media.hpp
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/defines.hpp>
|
||||
|
||||
#include <ox/claw/claw.hpp>
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/model/metadata.hpp>
|
||||
|
||||
|
||||
#include "asset.hpp"
|
||||
#include "context.hpp"
|
||||
#include "typeconv.hpp"
|
||||
|
||||
namespace keel {
|
||||
|
||||
// Pointer to preloaded data that can be stored in FS in place of the actual
|
||||
// data.
|
||||
struct PreloadPtr {
|
||||
static constexpr auto TypeName = "net.drinkingtea.keel.PreloadPtr";
|
||||
static constexpr auto TypeVersion = 2;
|
||||
uint64_t preloadAddr = 0;
|
||||
};
|
||||
|
||||
oxModelBegin(PreloadPtr)
|
||||
oxModelField(preloadAddr)
|
||||
oxModelEnd()
|
||||
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&file) noexcept;
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::CRStringView file) noexcept;
|
||||
|
||||
#ifndef OX_BARE_METAL
|
||||
|
||||
template<typename T>
|
||||
ox::Result<keel::AssetRef<T>> readObjFile(
|
||||
keel::Context &ctx,
|
||||
ox::StringView assetId,
|
||||
bool forceLoad) noexcept {
|
||||
constexpr auto readConvert = [](Context &ctx, const ox::Buffer &buff) -> ox::Result<T> {
|
||||
auto [obj, err] = readAsset<T>(buff);
|
||||
if (err) {
|
||||
if (err != ox::Error_ClawTypeVersionMismatch && err != ox::Error_ClawTypeMismatch) {
|
||||
return err;
|
||||
}
|
||||
oxReturnError(convert<T>(ctx, buff, &obj));
|
||||
}
|
||||
return std::move(obj);
|
||||
};
|
||||
ox::StringView path;
|
||||
if (beginsWith(assetId, "uuid://")) {
|
||||
assetId = substr(assetId, 7);
|
||||
oxRequire(p, ctx.uuidToPath.at(assetId));
|
||||
path = *p;
|
||||
} else {
|
||||
path = assetId;
|
||||
}
|
||||
if (forceLoad) {
|
||||
oxRequire(buff, ctx.rom->read(path));
|
||||
oxRequire(obj, readConvert(ctx, buff));
|
||||
oxRequire(cached, ctx.assetManager.setAsset(assetId, obj));
|
||||
return cached;
|
||||
} else {
|
||||
auto [cached, err] = ctx.assetManager.getAsset<T>(assetId);
|
||||
if (err) {
|
||||
oxRequire(buff, ctx.rom->read(path));
|
||||
oxRequire(obj, readConvert(ctx, buff));
|
||||
oxReturnError(ctx.assetManager.setAsset(assetId, obj).moveTo(cached));
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template<typename T>
|
||||
ox::Result<keel::AssetRef<T>> readObjNoCache(
|
||||
keel::Context &ctx,
|
||||
ox::CRStringView assetId) noexcept {
|
||||
if constexpr(ox::preloadable<T>::value) {
|
||||
oxRequire(addr, getPreloadAddr(ctx, assetId));
|
||||
return keel::AssetRef<T>(std::bit_cast<T const*>(uintptr_t{addr}));
|
||||
} else {
|
||||
return OxError(1);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void createUuidMapping(Context &ctx, ox::StringView filePath, ox::UUID const&uuid) noexcept;
|
||||
|
||||
ox::Error buildUuidMap(Context &ctx) noexcept;
|
||||
|
||||
ox::Result<ox::String> uuidToPath(Context &ctx, ox::CRStringView uuid) noexcept;
|
||||
|
||||
ox::Result<ox::String> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept;
|
||||
|
||||
ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept;
|
||||
|
||||
template<typename T>
|
||||
ox::Result<AssetRef<T>> setAsset(keel::Context &ctx, ox::StringView assetId, T const&asset) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
if (assetId.len() == 0) {
|
||||
return OxError(1, "Invalid asset ID");
|
||||
}
|
||||
ox::UUIDStr idStr;
|
||||
if (assetId[0] == '/') {
|
||||
auto const [id, err] = ctx.pathToUuid.at(assetId);
|
||||
oxReturnError(err);
|
||||
idStr = id->toString();
|
||||
assetId = idStr;
|
||||
}
|
||||
return ctx.assetManager.setAsset(assetId, asset);
|
||||
#else
|
||||
return OxError(1, "Not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Result<keel::AssetRef<T>> readObj(
|
||||
keel::Context &ctx,
|
||||
ox::CRStringView assetId,
|
||||
[[maybe_unused]] bool forceLoad = false) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
return readObjFile<T>(ctx, assetId, forceLoad);
|
||||
#else
|
||||
return readObjNoCache<T>(ctx, assetId);
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Result<keel::AssetRef<T>> readObj(
|
||||
keel::Context &ctx,
|
||||
ox::FileAddress const&file,
|
||||
[[maybe_unused]] bool forceLoad = false) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
oxRequire(assetId, file.getPath());
|
||||
return readObj<T>(ctx, ox::StringView(assetId), forceLoad);
|
||||
#else
|
||||
if constexpr(ox::preloadable<T>::value) {
|
||||
oxRequire(addr, getPreloadAddr(ctx, file));
|
||||
return keel::AssetRef<T>(std::bit_cast<T const*>(uintptr_t{addr}));
|
||||
} else {
|
||||
return OxError(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Error writeObj(
|
||||
keel::Context &ctx,
|
||||
ox::FileAddress const&file,
|
||||
T const&obj,
|
||||
ox::ClawFormat fmt = ox::ClawFormat::Metal) noexcept {
|
||||
oxRequire(objBuff, ox::writeClaw(obj, fmt));
|
||||
return ctx.rom->write(file, objBuff.data(), objBuff.size());
|
||||
}
|
||||
|
||||
ox::Error setRomFs(Context &ctx, ox::UPtr<ox::FileSystem> &&fs) noexcept;
|
||||
|
||||
ox::Result<ox::UniquePtr<ox::FileSystem>> loadRomFs(ox::CRStringView path) noexcept;
|
||||
|
||||
ox::Result<char*> loadRom(ox::CRStringView path = "") noexcept;
|
||||
|
||||
void unloadRom(char*) noexcept;
|
||||
|
||||
}
|
45
deps/nostalgia/src/olympic/keel/include/keel/module.hpp
vendored
Normal file
45
deps/nostalgia/src/olympic/keel/include/keel/module.hpp
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/vector.hpp>
|
||||
#include <ox/model/descwrite.hpp>
|
||||
|
||||
#include "typeconv.hpp"
|
||||
|
||||
namespace keel {
|
||||
|
||||
using TypeDescGenerator = ox::Error(*)(ox::TypeStore&);
|
||||
|
||||
template<typename T>
|
||||
ox::Error generateTypeDesc(ox::TypeStore &ts) noexcept {
|
||||
return ox::buildTypeDef<T>(&ts).error;
|
||||
}
|
||||
|
||||
class Module {
|
||||
public:
|
||||
constexpr Module() noexcept = default;
|
||||
Module(Module const&) noexcept = delete;
|
||||
Module(Module&&) noexcept = delete;
|
||||
Module &operator=(Module const&) noexcept = delete;
|
||||
Module &operator=(Module&&) noexcept = delete;
|
||||
constexpr virtual ~Module() noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual ox::String id() const noexcept = 0;
|
||||
[[nodiscard]]
|
||||
virtual ox::Vector<TypeDescGenerator> types() const noexcept;
|
||||
[[nodiscard]]
|
||||
virtual ox::Vector<const keel::BaseConverter*> converters() const noexcept;
|
||||
[[nodiscard]]
|
||||
virtual ox::Vector<PackTransform> packTransforms() const noexcept;
|
||||
};
|
||||
|
||||
void registerModule(Module const*mod) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<keel::Module const*> const&modules() noexcept;
|
||||
|
||||
}
|
166
deps/nostalgia/src/olympic/keel/include/keel/pack.hpp
vendored
Normal file
166
deps/nostalgia/src/olympic/keel/include/keel/pack.hpp
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/preloader/preloader.hpp>
|
||||
|
||||
#include "asset.hpp"
|
||||
#include "media.hpp"
|
||||
|
||||
namespace keel {
|
||||
|
||||
class Context;
|
||||
|
||||
struct GbaPlatSpec {
|
||||
using PtrType = uint32_t;
|
||||
using size_t = uint32_t;
|
||||
|
||||
static constexpr PtrType RomStart = 0x08000000;
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr std::size_t alignOf(const bool) noexcept {
|
||||
return 1;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr std::size_t alignOf(const uint8_t) noexcept {
|
||||
return 1;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr std::size_t alignOf(const uint16_t) noexcept {
|
||||
return 2;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr std::size_t alignOf(const uint32_t) noexcept {
|
||||
return 4;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr std::size_t alignOf(const uint64_t) noexcept {
|
||||
return 8;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr std::size_t alignOf(const int8_t) noexcept {
|
||||
return 1;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr std::size_t alignOf(const int16_t) noexcept {
|
||||
return 2;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr std::size_t alignOf(const int32_t) noexcept {
|
||||
return 4;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr std::size_t alignOf(const int64_t) noexcept {
|
||||
return 8;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr std::size_t alignOf(auto const*) noexcept {
|
||||
return 4;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static constexpr auto correctEndianness(auto v) noexcept {
|
||||
return ox::toLittleEndian(v);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
using GbaPreloader = ox::Preloader<GbaPlatSpec>;
|
||||
|
||||
namespace detail {
|
||||
// transformations need to be done after the copy to the new FS is complete
|
||||
template<typename PlatSpec>
|
||||
ox::Error preloadObj(
|
||||
ox::TypeStore &ts,
|
||||
ox::FileSystem &romFs,
|
||||
ox::Preloader<PlatSpec> &pl,
|
||||
ox::CRStringView path) noexcept {
|
||||
// load file
|
||||
oxRequireM(buff, romFs.read(path));
|
||||
oxRequireM(obj, keel::readAsset(ts, buff));
|
||||
if (obj.type()->preloadable) {
|
||||
oxOutf("preloading {}\n", path);
|
||||
// preload
|
||||
oxRequire(a, pl.startAlloc(ox::sizeOf<GbaPlatSpec>(&obj)));
|
||||
auto const err = ox::preload<GbaPlatSpec, decltype(obj)>(&pl, &obj);
|
||||
oxReturnError(pl.endAlloc());
|
||||
oxReturnError(err);
|
||||
keel::PreloadPtr const p{.preloadAddr = static_cast<uint32_t>(a)};
|
||||
oxReturnError(ox::writeMC(p).moveTo(buff));
|
||||
} else {
|
||||
// strip the Claw header (it is not needed after preloading) and write back out to dest fs
|
||||
oxReturnError(ox::writeMC(obj).moveTo(buff));
|
||||
}
|
||||
oxReturnError(romFs.write(path, buff.data(), buff.size()));
|
||||
return {};
|
||||
}
|
||||
|
||||
// claw file transformations are broken out because path to inode
|
||||
// transformations need to be done after the copy to the new FS is complete
|
||||
template<typename PlatSpec>
|
||||
ox::Error preloadDir(
|
||||
ox::TypeStore &ts,
|
||||
ox::FileSystem &romFs,
|
||||
ox::Preloader<PlatSpec> &pl,
|
||||
ox::CRStringView path) noexcept {
|
||||
// copy
|
||||
oxTracef("pack.preload", "path: {}", path);
|
||||
oxRequire(fileList, romFs.ls(path));
|
||||
for (auto const&name : fileList) {
|
||||
auto const filePath = ox::sfmt("{}{}", path, name);
|
||||
oxRequire(stat, romFs.stat(filePath));
|
||||
if (stat.fileType == ox::FileType::Directory) {
|
||||
auto const dir = ox::sfmt("{}{}/", path, name);
|
||||
oxReturnError(preloadDir(ts, romFs, pl, dir));
|
||||
} else {
|
||||
oxReturnError(preloadObj(ts, romFs, pl, filePath));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename PlatSpec>
|
||||
ox::Error appendBinary(ox::Buffer &binBuff, ox::SpanView<char> const&fsBuff, ox::Preloader<PlatSpec> &pl) noexcept {
|
||||
constexpr auto padbin = [](ox::BufferWriter &w, unsigned factor) noexcept -> ox::Error {
|
||||
return w.write(nullptr, factor - w.buff().size() % factor);
|
||||
};
|
||||
constexpr ox::StringView mediaHdr = "KEEL_MEDIA_HEADER_______________";
|
||||
constexpr ox::StringView preloadHdr = "KEEL_PRELOAD_HEADER_____________";
|
||||
constexpr auto hdrSize = 32u;
|
||||
static_assert(mediaHdr.bytes() == hdrSize);
|
||||
static_assert(preloadHdr.bytes() == hdrSize);
|
||||
ox::BufferWriter w(&binBuff);
|
||||
oxReturnError(padbin(w, hdrSize));
|
||||
oxReturnError(w.write(mediaHdr.data(), mediaHdr.bytes()));
|
||||
oxReturnError(w.write(fsBuff.data(), fsBuff.size()));
|
||||
oxReturnError(padbin(w, hdrSize));
|
||||
oxReturnError(w.write(preloadHdr.data(), preloadHdr.bytes()));
|
||||
oxReturnError(pl.offsetPtrs(binBuff.size()));
|
||||
auto const&plBuff = pl.buff();
|
||||
oxReturnError(w.write(plBuff.data(), plBuff.size()));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename PlatSpec>
|
||||
ox::Error preload(ox::TypeStore &ts, ox::FileSystem &src, ox::Preloader<PlatSpec> &pl) noexcept {
|
||||
oxOut("Preloading\n");
|
||||
return detail::preloadDir(ts, src, pl, "/");
|
||||
}
|
||||
|
||||
ox::Error pack(keel::Context &ctx, ox::TypeStore &ts, ox::FileSystem &dest) noexcept;
|
||||
|
||||
}
|
170
deps/nostalgia/src/olympic/keel/include/keel/typeconv.hpp
vendored
Normal file
170
deps/nostalgia/src/olympic/keel/include/keel/typeconv.hpp
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/def.hpp>
|
||||
#include <ox/std/error.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
#include <ox/claw/read.hpp>
|
||||
#include <ox/claw/write.hpp>
|
||||
|
||||
#include "asset.hpp"
|
||||
#include "context.hpp"
|
||||
|
||||
namespace keel {
|
||||
|
||||
class Wrap {
|
||||
public:
|
||||
virtual ~Wrap() = default;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class WrapInline: public Wrap {
|
||||
private:
|
||||
T m_obj;
|
||||
|
||||
public:
|
||||
constexpr WrapInline() = default;
|
||||
|
||||
template<typename... Args>
|
||||
constexpr explicit WrapInline(Args &&...args): m_obj(ox::forward<Args>(args)...) {
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr T &obj() noexcept {
|
||||
return m_obj;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T, typename... Args>
|
||||
constexpr auto makeWrap(Args &&...args) noexcept {
|
||||
return ox::make_unique<WrapInline<T>>(ox::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr T &wrapCast(Wrap &ptr) noexcept {
|
||||
return static_cast<WrapInline<T>&>(ptr).obj();
|
||||
}
|
||||
|
||||
class BaseConverter {
|
||||
public:
|
||||
virtual ~BaseConverter() noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual ox::StringView srcTypeName() const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual int srcTypeVersion() const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual bool srcMatches(ox::CRStringView pSrcTypeName, int pSrcTypeVersion) const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual bool dstMatches(ox::CRStringView dstTypeName, int dstTypeVersion) const noexcept = 0;
|
||||
|
||||
virtual ox::Result<ox::UniquePtr<Wrap>> convertPtrToPtr(keel::Context &ctx, Wrap &src) const noexcept = 0;
|
||||
|
||||
virtual ox::Result<ox::UniquePtr<Wrap>> convertBuffToPtr(keel::Context &ctx, ox::Buffer const&srcBuff) const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
inline bool matches(
|
||||
ox::CRStringView srcTypeName, int srcTypeVersion,
|
||||
ox::CRStringView dstTypeName, int dstTypeVersion) const noexcept {
|
||||
return srcMatches(srcTypeName, srcTypeVersion)
|
||||
&& dstMatches(dstTypeName, dstTypeVersion);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename SrcType, typename DstType>
|
||||
class Converter: public BaseConverter {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
ox::StringView srcTypeName() const noexcept final {
|
||||
return ox::requireModelTypeName<SrcType>();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
int srcTypeVersion() const noexcept final {
|
||||
return ox::requireModelTypeVersion<SrcType>();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool srcMatches(ox::CRStringView pSrcTypeName, int pSrcTypeVersion) const noexcept final {
|
||||
static const auto SrcTypeName = ox::requireModelTypeName<SrcType>();
|
||||
static const auto SrcTypeVersion = ox::requireModelTypeVersion<SrcType>();
|
||||
return pSrcTypeName == SrcTypeName
|
||||
&& pSrcTypeVersion == SrcTypeVersion;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool dstMatches(ox::CRStringView dstTypeName, int dstTypeVersion) const noexcept final {
|
||||
static const auto DstTypeName = ox::StringView(ox::requireModelTypeName<DstType>());
|
||||
static const auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
|
||||
return dstTypeName == DstTypeName
|
||||
&& dstTypeVersion == DstTypeVersion;
|
||||
}
|
||||
|
||||
ox::Result<ox::UniquePtr<Wrap>> convertPtrToPtr(keel::Context &ctx, Wrap &src) const noexcept final {
|
||||
auto dst = makeWrap<DstType>();
|
||||
oxReturnError(convert(ctx, wrapCast<SrcType>(src), wrapCast<DstType>(*dst)));
|
||||
return ox::Result<ox::UniquePtr<Wrap>>(std::move(dst));
|
||||
}
|
||||
|
||||
ox::Result<ox::UniquePtr<Wrap>> convertBuffToPtr(keel::Context &ctx, ox::Buffer const&srcBuff) const noexcept final {
|
||||
oxRequireM(src, readAsset<SrcType>(srcBuff));
|
||||
auto dst = makeWrap<DstType>();
|
||||
oxReturnError(convert(ctx, src, wrapCast<DstType>(*dst)));
|
||||
return ox::Result<ox::UniquePtr<Wrap>>(std::move(dst));
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ox::Error convert(keel::Context &ctx, SrcType&, DstType&) const noexcept = 0;
|
||||
|
||||
};
|
||||
|
||||
ox::Result<ox::UniquePtr<Wrap>> convert(
|
||||
keel::Context &ctx, ox::Buffer const&srcBuffer,
|
||||
ox::CRStringView dstTypeName, int dstTypeVersion) noexcept;
|
||||
|
||||
template<typename DstType>
|
||||
ox::Result<DstType> convert(keel::Context &ctx, ox::Buffer const&srcBuffer) noexcept {
|
||||
static constexpr auto DstTypeName = ox::requireModelTypeName<DstType>();
|
||||
static constexpr auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
|
||||
oxRequire(out, convert(ctx, srcBuffer, DstTypeName, DstTypeVersion));
|
||||
return std::move(wrapCast<DstType>(out));
|
||||
}
|
||||
|
||||
template<typename DstType>
|
||||
ox::Error convert(keel::Context &ctx, ox::Buffer const&buff, DstType *outObj) noexcept {
|
||||
static constexpr auto DstTypeName = ox::requireModelTypeName<DstType>();
|
||||
static constexpr auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
|
||||
oxRequire(outPtr, convert(ctx, buff, DstTypeName, DstTypeVersion));
|
||||
*outObj = std::move(wrapCast<DstType>(*outPtr));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename DstType>
|
||||
ox::Result<ox::Buffer> convertBuffToBuff(keel::Context &ctx, const ox::Buffer &srcBuffer, ox::ClawFormat fmt) noexcept {
|
||||
static constexpr auto DstTypeName = ox::requireModelTypeName<DstType>();
|
||||
static constexpr auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
|
||||
oxRequire(out, convert(ctx, srcBuffer, DstTypeName, DstTypeVersion));
|
||||
return ox::writeClaw<DstType>(wrapCast<DstType>(*out), fmt);
|
||||
}
|
||||
|
||||
template<typename From, typename To, ox::ClawFormat fmt = ox::ClawFormat::Metal>
|
||||
auto transformRule(keel::Context &ctx, ox::Buffer &buff) noexcept -> ox::Error {
|
||||
oxRequire(hdr, readAssetHeader(buff));
|
||||
const auto typeId = ox::buildTypeId(
|
||||
hdr.clawHdr.typeName, hdr.clawHdr.typeVersion, hdr.clawHdr.typeParams);
|
||||
if (typeId == ox::buildTypeId<From>()) {
|
||||
oxReturnError(keel::convertBuffToBuff<To>(ctx, buff, fmt).moveTo(buff));
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
|
||||
}
|
25
deps/nostalgia/src/olympic/keel/include/keel/typestore.hpp
vendored
Normal file
25
deps/nostalgia/src/olympic/keel/include/keel/typestore.hpp
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/claw/claw.hpp>
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/model/typestore.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
class TypeStore: public ox::TypeStore {
|
||||
private:
|
||||
ox::FileSystem &m_fs;
|
||||
ox::String m_descPath;
|
||||
|
||||
public:
|
||||
explicit TypeStore(ox::FileSystem &fs, ox::StringView descPath) noexcept;
|
||||
|
||||
protected:
|
||||
ox::Result<ox::UPtr<ox::DescriptorType>> loadDescriptor(ox::CRStringView typeId) noexcept override;
|
||||
};
|
||||
|
||||
}
|
63
deps/nostalgia/src/olympic/keel/src/CMakeLists.txt
vendored
Normal file
63
deps/nostalgia/src/olympic/keel/src/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
|
||||
add_library(
|
||||
Keel
|
||||
asset.cpp
|
||||
keel.cpp
|
||||
media.cpp
|
||||
module.cpp
|
||||
pack.cpp
|
||||
typeconv.cpp
|
||||
typestore.cpp
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
Keel PUBLIC
|
||||
../include
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
Keel PUBLIC
|
||||
OxClaw
|
||||
OxEvent
|
||||
OxFS
|
||||
OxModel
|
||||
OxPreloader
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY
|
||||
../include/keel
|
||||
DESTINATION
|
||||
include/keel
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
Keel
|
||||
DESTINATION
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
||||
|
||||
if(TURBINE_BUILD_TYPE STREQUAL "Native")
|
||||
add_library(
|
||||
KeelPack-AppLib
|
||||
pack-applib.cpp
|
||||
)
|
||||
target_include_directories(
|
||||
KeelPack-AppLib PUBLIC
|
||||
../include
|
||||
)
|
||||
target_compile_definitions(
|
||||
KeelPack-AppLib PUBLIC
|
||||
OLYMPIC_LOAD_KEEL_MODULES=1
|
||||
OLYMPIC_APP_NAME="Keel Pack"
|
||||
)
|
||||
target_link_libraries(
|
||||
KeelPack-AppLib
|
||||
Keel
|
||||
OxClArgs
|
||||
OxClaw
|
||||
OxLogConn
|
||||
)
|
||||
endif()
|
46
deps/nostalgia/src/olympic/keel/src/asset.cpp
vendored
Normal file
46
deps/nostalgia/src/olympic/keel/src/asset.cpp
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <keel/asset.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
ox::Result<ox::UUID> readUuidHeader(ox::Buffer const&buff) noexcept {
|
||||
return readUuidHeader(buff.data(), buff.size());
|
||||
}
|
||||
|
||||
ox::Result<ox::UUID> readUuidHeader(char const*buff, std::size_t buffLen) noexcept {
|
||||
if (buffLen < K1HdrSz) {
|
||||
return OxError(1, "Insufficient data to contain complete Keel header");
|
||||
}
|
||||
constexpr ox::StringView k1Hdr = "K1;";
|
||||
if (k1Hdr != ox::StringView(buff, k1Hdr.bytes())) {
|
||||
return OxError(2, "No Keel asset header data");
|
||||
}
|
||||
return ox::UUID::fromString(ox::StringView(buff + k1Hdr.bytes(), 36));
|
||||
}
|
||||
|
||||
ox::Result<ox::ModelObject> readAsset(ox::TypeStore &ts, ox::Buffer const&buff) noexcept {
|
||||
std::size_t offset = 0;
|
||||
if (!readUuidHeader(buff).error) {
|
||||
offset = K1HdrSz;
|
||||
}
|
||||
return ox::readClaw(ts, buff.data() + offset, buff.size() - offset);
|
||||
}
|
||||
|
||||
ox::Result<AssetHdr> readAssetHeader(char const*buff, std::size_t buffLen) noexcept {
|
||||
AssetHdr out;
|
||||
const auto err = readUuidHeader(buff, buffLen).moveTo(out.uuid);
|
||||
const auto offset = err ? 0u : K1HdrSz;
|
||||
buff = buff + offset;
|
||||
buffLen = buffLen - offset;
|
||||
oxReturnError(ox::readClawHeader(buff, buffLen).moveTo(out.clawHdr));
|
||||
return out;
|
||||
}
|
||||
|
||||
ox::Result<AssetHdr> readAssetHeader(ox::Buffer const&buff) noexcept {
|
||||
return readAssetHeader(buff.data(), buff.size());
|
||||
}
|
||||
|
||||
}
|
37
deps/nostalgia/src/olympic/keel/src/keel.cpp
vendored
Normal file
37
deps/nostalgia/src/olympic/keel/src/keel.cpp
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <keel/keel.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
ox::Error init(
|
||||
keel::Context &ctx,
|
||||
ox::UPtr<ox::FileSystem> &&fs,
|
||||
ox::CRStringView appName) noexcept {
|
||||
ctx.appName = appName;
|
||||
oxIgnoreError(setRomFs(ctx, std::move(fs)));
|
||||
#ifndef OX_BARE_METAL
|
||||
auto const&mods = modules();
|
||||
for (auto &mod : mods) {
|
||||
// register type converters
|
||||
for (auto c : mod->converters()) {
|
||||
ctx.converters.emplace_back(c);
|
||||
}
|
||||
// register pack transforms
|
||||
for (auto c : mod->packTransforms()) {
|
||||
ctx.packTransforms.emplace_back(c);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Result<ox::UPtr<Context>> init(ox::UPtr<ox::FileSystem> &&fs, ox::CRStringView appName) noexcept {
|
||||
auto ctx = ox::make_unique<Context>();
|
||||
oxReturnError(keel::init(*ctx, std::move(fs), appName));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
}
|
194
deps/nostalgia/src/olympic/keel/src/media.cpp
vendored
Normal file
194
deps/nostalgia/src/olympic/keel/src/media.cpp
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#ifndef OX_BARE_METAL
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <ox/std/trace.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
ox::Result<char*> loadRom(ox::CRStringView path) noexcept {
|
||||
std::ifstream file(std::string(toStdStringView(path)), std::ios::binary | std::ios::ate);
|
||||
if (!file.good()) {
|
||||
oxErrorf("Could not find ROM file: {}", path);
|
||||
return OxError(1, "Could not find ROM file");
|
||||
}
|
||||
try {
|
||||
auto const size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
auto buff = new char[static_cast<std::size_t>(size)];
|
||||
file.read(buff, size);
|
||||
return buff;
|
||||
} catch (std::ios_base::failure const&e) {
|
||||
oxErrorf("Could not read ROM file due to file IO failure: {}", e.what());
|
||||
return OxError(2, "Could not read ROM file");
|
||||
} catch (std::bad_alloc const&e) {
|
||||
oxErrorf("Could not read ROM file due to new failure: {}", e.what());
|
||||
return OxError(2, "Could not allocate memory for ROM file");
|
||||
}
|
||||
}
|
||||
|
||||
void unloadRom(char *rom) noexcept {
|
||||
ox::safeDelete(rom);
|
||||
}
|
||||
|
||||
static void clearUuidMap(Context &ctx) noexcept {
|
||||
ctx.uuidToPath.clear();
|
||||
ctx.pathToUuid.clear();
|
||||
}
|
||||
|
||||
void createUuidMapping(Context &ctx, ox::StringView filePath, ox::UUID const&uuid) noexcept {
|
||||
ctx.pathToUuid[filePath] = uuid;
|
||||
ctx.uuidToPath[uuid.toString()] = filePath;
|
||||
}
|
||||
|
||||
static ox::Error buildUuidMap(Context &ctx, ox::CRStringView path) noexcept {
|
||||
oxRequire(files, ctx.rom->ls(path));
|
||||
for (auto const&f : files) {
|
||||
oxRequireM(filePath, ox::join("/", ox::Array<ox::StringView, 2>{path, f}));
|
||||
oxRequire(stat, ctx.rom->stat(filePath));
|
||||
if (stat.fileType == ox::FileType::NormalFile) {
|
||||
oxRequire(data, ctx.rom->read(filePath));
|
||||
auto const [hdr, err] = readAssetHeader(data);
|
||||
if (!err) {
|
||||
createUuidMapping(ctx, filePath, hdr.uuid);
|
||||
}
|
||||
} else if (stat.fileType == ox::FileType::Directory) {
|
||||
if (!beginsWith(f, ".")) {
|
||||
oxReturnError(buildUuidMap(ctx, filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error buildUuidMap(Context &ctx) noexcept {
|
||||
if (!ctx.rom) {
|
||||
return OxError(1, "No ROM FS");
|
||||
}
|
||||
return buildUuidMap(ctx, "");
|
||||
}
|
||||
|
||||
ox::Result<ox::UUID> pathToUuid(Context &ctx, ox::CRStringView path) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
oxRequire(out, ctx.pathToUuid.at(path));
|
||||
return *out;
|
||||
#else
|
||||
return OxError(1, "UUID to path conversion not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
ox::Result<ox::String> uuidToPath(Context &ctx, ox::CRStringView uuid) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
oxRequire(out, ctx.uuidToPath.at(uuid));
|
||||
return *out;
|
||||
#else
|
||||
return OxError(1, "UUID to path conversion not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
ox::Result<ox::String> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
oxRequire(out, ctx.uuidToPath.at(uuid.toString()));
|
||||
return *out;
|
||||
#else
|
||||
return OxError(1, "UUID to path conversion not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
for (auto tr : ctx.packTransforms) {
|
||||
oxReturnError(tr(ctx, clawData));
|
||||
}
|
||||
return {};
|
||||
#else
|
||||
return OxError(1, "Transformations not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <keel/context.hpp>
|
||||
|
||||
#define MEM_ROM reinterpret_cast<char*>(0x0800'0000)
|
||||
|
||||
namespace keel {
|
||||
|
||||
static void clearUuidMap(Context&) noexcept {
|
||||
}
|
||||
|
||||
ox::Error buildUuidMap(Context&) noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Result<char*> loadRom(ox::CRStringView) noexcept {
|
||||
// put the header in the wrong order to prevent mistaking this code for the
|
||||
// media section
|
||||
constexpr auto headerP2 = "R_______________";
|
||||
constexpr auto headerP1 = "KEEL_MEDIA_HEADE";
|
||||
constexpr auto headerP1Len = ox_strlen(headerP2);
|
||||
constexpr auto headerP2Len = ox_strlen(headerP1);
|
||||
constexpr auto headerLen = headerP1Len + headerP2Len;
|
||||
for (auto current = MEM_ROM; current < reinterpret_cast<char*>(0x0a000000); current += headerLen) {
|
||||
if (ox_memcmp(current, headerP1, headerP1Len) == 0 &&
|
||||
ox_memcmp(current + headerP1Len, headerP2, headerP2Len) == 0) {
|
||||
return current + headerLen;
|
||||
}
|
||||
}
|
||||
return OxError(1);
|
||||
}
|
||||
|
||||
void unloadRom(char*) noexcept {
|
||||
}
|
||||
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::CRStringView path) noexcept {
|
||||
oxRequire(stat, ctx.rom->stat(path));
|
||||
oxRequire(buff, static_cast<ox::MemFS*>(ctx.rom.get())->directAccess(path));
|
||||
PreloadPtr p;
|
||||
oxReturnError(ox::readMC(buff, static_cast<std::size_t>(stat.size), &p));
|
||||
return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset;
|
||||
}
|
||||
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&file) noexcept {
|
||||
oxRequire(stat, ctx.rom->stat(file));
|
||||
oxRequire(buff, static_cast<ox::MemFS*>(ctx.rom.get())->directAccess(file));
|
||||
PreloadPtr p;
|
||||
oxReturnError(ox::readMC(buff, static_cast<std::size_t>(stat.size), &p));
|
||||
return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace keel {
|
||||
|
||||
ox::Error setRomFs(Context &ctx, ox::UPtr<ox::FileSystem> &&fs) noexcept {
|
||||
ctx.rom = std::move(fs);
|
||||
clearUuidMap(ctx);
|
||||
return buildUuidMap(ctx);
|
||||
}
|
||||
|
||||
ox::Result<ox::UniquePtr<ox::FileSystem>> loadRomFs(ox::CRStringView path) noexcept {
|
||||
auto const lastDot = ox::lastIndexOf(path, '.');
|
||||
if (!lastDot.error && substr(path, lastDot.value) == ".oxfs") {
|
||||
oxRequire(rom, loadRom(path));
|
||||
return {ox::make_unique<ox::FileSystem32>(rom, 32 * ox::units::MB, unloadRom)};
|
||||
} else {
|
||||
#ifdef OX_HAS_PASSTHROUGHFS
|
||||
return {ox::make_unique<ox::PassThroughFS>(path)};
|
||||
#else
|
||||
return OxError(2);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
33
deps/nostalgia/src/olympic/keel/src/module.cpp
vendored
Normal file
33
deps/nostalgia/src/olympic/keel/src/module.cpp
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <keel/module.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
static ox::Vector<Module const*> mods;
|
||||
|
||||
void registerModule(Module const*mod) noexcept {
|
||||
mods.emplace_back(mod);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<Module const*> const&modules() noexcept {
|
||||
return mods;
|
||||
}
|
||||
|
||||
|
||||
ox::Vector<TypeDescGenerator> Module::types() const noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Vector<keel::BaseConverter const*> Module::converters() const noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Vector<PackTransform> Module::packTransforms() const noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
103
deps/nostalgia/src/olympic/keel/src/pack-applib.cpp
vendored
Normal file
103
deps/nostalgia/src/olympic/keel/src/pack-applib.cpp
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <ox/clargs/clargs.hpp>
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/logconn/def.hpp>
|
||||
#include <ox/logconn/logconn.hpp>
|
||||
|
||||
#include <keel/keel.hpp>
|
||||
|
||||
static ox::Error writeFileBuff(ox::StringView path, ox::Buffer const&buff) noexcept {
|
||||
try {
|
||||
std::ofstream f(std::string(toStdStringView(path)), std::ios::binary);
|
||||
f.write(buff.data(), static_cast<intptr_t>(buff.size()));
|
||||
} catch (std::fstream::failure const&) {
|
||||
return OxError(2, "failed to write file");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Result<ox::Buffer> readFileBuff(ox::StringView path) noexcept {
|
||||
std::ifstream file(std::string(toStdStringView(path)), std::ios::binary | std::ios::ate);
|
||||
if (!file.good()) {
|
||||
oxErrorf("Could not find OxFS file: {}", path);
|
||||
return OxError(1, "Could not find OxFS file");
|
||||
}
|
||||
try {
|
||||
auto const size = static_cast<std::size_t>(file.tellg());
|
||||
ox::Buffer buff(size);
|
||||
file.seekg(0, std::ios::beg);
|
||||
file.read(buff.data(), static_cast<std::streamsize>(buff.size()));
|
||||
return buff;
|
||||
} catch (std::ios_base::failure const&e) {
|
||||
oxErrorf("Could not read OxFS file: {}", e.what());
|
||||
return OxError(2, "Could not read OxFS file");
|
||||
}
|
||||
}
|
||||
|
||||
static ox::Error generateTypes(ox::TypeStore *ts) noexcept {
|
||||
for (auto const mod : keel::modules()) {
|
||||
for (auto gen : mod->types()) {
|
||||
oxReturnError(gen(*ts));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error pack(ox::StringView argSrc, ox::StringView argRomBin, ox::StringView projectDataDir) noexcept {
|
||||
ox::Buffer dstBuff(32 * ox::units::MB);
|
||||
oxReturnError(ox::FileSystem32::format(dstBuff.data(), dstBuff.size()));
|
||||
ox::FileSystem32 dst(dstBuff);
|
||||
oxRequire(ctx, keel::init(ox::make_unique<ox::PassThroughFS>(argSrc), "keel-pack"));
|
||||
keel::TypeStore ts(*ctx->rom, ox::sfmt("{}/type_descriptors", projectDataDir));
|
||||
oxReturnError(generateTypes(&ts));
|
||||
oxReturnError(keel::pack(*ctx, ts, dst));
|
||||
oxRequireM(pl, keel::GbaPreloader::make());
|
||||
oxReturnError(preload(ts, dst, *pl));
|
||||
oxReturnError(dst.resize());
|
||||
// resize buffer
|
||||
oxRequire(dstSize, dst.size());
|
||||
dstBuff.resize(dstSize);
|
||||
|
||||
oxRequireM(romBuff, readFileBuff(argRomBin));
|
||||
oxReturnError(appendBinary(romBuff, dstBuff, *pl));
|
||||
|
||||
oxOutf("Dest FS size: {} bytes\n", dstSize);
|
||||
oxOutf("Preload buff size: {} bytes\n", pl->buff().size());
|
||||
oxOutf("ROM buff size: {} bytes\n", romBuff.size());
|
||||
|
||||
oxReturnError(writeFileBuff(argRomBin, romBuff));
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error run(int argc, char const**argv, ox::StringView projectDataDir) noexcept {
|
||||
ox::ClArgs const args(argc, argv);
|
||||
auto const argSrc = args.getString("src", "");
|
||||
auto const argRomBin = args.getString("rom-bin", "");
|
||||
if (argSrc == "") {
|
||||
oxErr("\033[31;1;1merror:\033[0m must specify a source directory\n");
|
||||
return OxError(1, "must specify a source directory");
|
||||
}
|
||||
if (argRomBin == "") {
|
||||
oxErr("\033[31;1;1merror:\033[0m must specify a path for ROM file\n");
|
||||
return OxError(1, "must specify a path for preload file");
|
||||
}
|
||||
return pack(argSrc, argRomBin, projectDataDir);
|
||||
}
|
||||
|
||||
namespace olympic {
|
||||
|
||||
ox::Error run(
|
||||
[[maybe_unused]] ox::StringView project,
|
||||
[[maybe_unused]] ox::StringView appName,
|
||||
ox::StringView projectDataDir,
|
||||
int argc,
|
||||
char const**argv) noexcept {
|
||||
return ::run(argc, argv, projectDataDir);
|
||||
}
|
||||
|
||||
}
|
169
deps/nostalgia/src/olympic/keel/src/pack.cpp
vendored
Normal file
169
deps/nostalgia/src/olympic/keel/src/pack.cpp
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/model/modelvalue.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include <keel/pack.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
static ox::Error pathToInode(
|
||||
keel::Context &ctx, ox::FileSystem &dest, ox::ModelObject &obj) noexcept {
|
||||
auto &o = obj;
|
||||
auto type = static_cast<ox::FileAddressType>(o["type"].get<int8_t>());
|
||||
auto &data = o["data"].get<ox::ModelUnion>();
|
||||
ox::String path;
|
||||
switch (type) {
|
||||
case ox::FileAddressType::Path:
|
||||
path = data["path"].get<ox::String>();
|
||||
break;
|
||||
case ox::FileAddressType::ConstPath:
|
||||
path = data["constPath"].get<ox::String>();
|
||||
break;
|
||||
case ox::FileAddressType::Inode:
|
||||
case ox::FileAddressType::None:
|
||||
return {};
|
||||
}
|
||||
if (beginsWith(path, "uuid://")) {
|
||||
auto const uuid = substr<ox::StringView>(path, 7);
|
||||
oxReturnError(keel::uuidToPath(ctx, uuid).moveTo(path));
|
||||
}
|
||||
oxRequire(s, dest.stat(path));
|
||||
oxReturnError(o["type"].set(static_cast<int8_t>(ox::FileAddressType::Inode)));
|
||||
return data.set(2, s.inode);
|
||||
}
|
||||
|
||||
static ox::Error transformFileAddressesObj(
|
||||
keel::Context &ctx, ox::FileSystem &dest, ox::ModelObject &obj) noexcept;
|
||||
static ox::Error transformFileAddressesVec(
|
||||
keel::Context &ctx, ox::FileSystem &dest, ox::ModelValueVector &v) noexcept;
|
||||
|
||||
static ox::Error transformFileAddresses(
|
||||
keel::Context &ctx,
|
||||
ox::FileSystem &dest,
|
||||
ox::ModelValue &v) noexcept {
|
||||
if (v.type() == ox::ModelValue::Type::Object) {
|
||||
auto &obj = v.get<ox::ModelObject>();
|
||||
return transformFileAddressesObj(ctx, dest, obj);
|
||||
} else if (v.type() == ox::ModelValue::Type::Vector) {
|
||||
auto &vec = v.get<ox::ModelValueVector>();
|
||||
return transformFileAddressesVec(ctx, dest, vec);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error transformFileAddressesVec(
|
||||
keel::Context &ctx,
|
||||
ox::FileSystem &dest,
|
||||
ox::ModelValueVector &v) noexcept {
|
||||
for (auto &f : v) {
|
||||
oxReturnError(transformFileAddresses(ctx, dest, f));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert path references in Claw data to inodes to save space
|
||||
* @return error
|
||||
*/
|
||||
static ox::Error transformFileAddressesObj(
|
||||
keel::Context &ctx,
|
||||
ox::FileSystem &dest,
|
||||
ox::ModelObject &obj) noexcept {
|
||||
if (obj.typeName() == "net.drinkingtea.ox.FileAddress" && obj.typeVersion() == 1) {
|
||||
return pathToInode(ctx, dest, obj);
|
||||
}
|
||||
for (auto &f : obj) {
|
||||
auto &v = f->value;
|
||||
oxReturnError(transformFileAddresses(ctx, dest, v));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error doTransformations(
|
||||
keel::Context &ctx,
|
||||
ox::TypeStore &ts,
|
||||
ox::FileSystem &dest,
|
||||
ox::CRStringView filePath) noexcept {
|
||||
// load file
|
||||
oxRequire(s, dest.stat(filePath));
|
||||
// do transformations
|
||||
oxRequireM(buff, dest.read(s.inode));
|
||||
oxReturnError(keel::performPackTransforms(ctx, buff));
|
||||
// transform FileAddresses
|
||||
oxRequireM(obj, keel::readAsset(ts, buff));
|
||||
oxReturnError(transformFileAddressesObj(ctx, dest, obj));
|
||||
oxReturnError(ox::writeClaw(obj).moveTo(buff));
|
||||
// write file to dest
|
||||
oxReturnError(dest.write(s.inode, buff));
|
||||
return {};
|
||||
}
|
||||
|
||||
// claw file transformations are broken out from copy because path to inode
|
||||
// transformations need to be done after the copy to the new FS is complete
|
||||
static ox::Error transformClaw(
|
||||
keel::Context &ctx,
|
||||
ox::TypeStore &ts,
|
||||
ox::FileSystem &dest,
|
||||
ox::CRStringView path) noexcept {
|
||||
// copy
|
||||
oxTracef("pack.transformClaw", "path: {}", path);
|
||||
oxRequire(fileList, dest.ls(path));
|
||||
for (auto const&name : fileList) {
|
||||
auto const filePath = ox::sfmt("{}{}", path, name);
|
||||
oxRequire(stat, dest.stat(filePath));
|
||||
if (stat.fileType == ox::FileType::Directory) {
|
||||
auto const dir = ox::sfmt("{}{}/", path, name);
|
||||
oxReturnError(transformClaw(ctx, ts, dest, dir));
|
||||
} else {
|
||||
oxReturnError(doTransformations(ctx, ts, dest, filePath));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error copy(
|
||||
ox::FileSystem &src,
|
||||
ox::FileSystem &dest,
|
||||
ox::CRStringView path) noexcept {
|
||||
oxOutf("copying directory: {}\n", path);
|
||||
// copy
|
||||
oxRequire(fileList, src.ls(path));
|
||||
for (auto const&name : fileList) {
|
||||
auto currentFile = ox::sfmt("{}{}", path, name);
|
||||
if (beginsWith(name, ".")) {
|
||||
continue;
|
||||
}
|
||||
oxOutf("reading {}\n", currentFile);
|
||||
oxRequire(stat, src.stat(currentFile));
|
||||
if (stat.fileType == ox::FileType::Directory) {
|
||||
oxReturnError(dest.mkdir(currentFile, true));
|
||||
oxReturnError(copy(src, dest, currentFile + '/'));
|
||||
} else {
|
||||
// load file
|
||||
oxRequireM(buff, src.read(currentFile));
|
||||
// write file to dest
|
||||
oxOutf("writing {}\n", currentFile);
|
||||
oxReturnError(dest.write(currentFile, buff));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error copyFS(ox::FileSystem &src, ox::FileSystem &dest) noexcept {
|
||||
oxReturnError(copy(src, dest, "/"));
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error pack(keel::Context &ctx, ox::TypeStore &ts, ox::FileSystem &dest) noexcept {
|
||||
oxReturnError(copyFS(*ctx.rom, dest));
|
||||
oxOut("Doing transforms\n");
|
||||
oxReturnError(transformClaw(ctx, ts, dest, "/"));
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
75
deps/nostalgia/src/olympic/keel/src/typeconv.cpp
vendored
Normal file
75
deps/nostalgia/src/olympic/keel/src/typeconv.cpp
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/claw/read.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
#include <keel/typeconv.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
[[nodiscard]]
|
||||
static ox::Result<const BaseConverter*> findConverter(
|
||||
ox::Vector<const BaseConverter*> const&converters,
|
||||
ox::CRStringView srcTypeName,
|
||||
int srcTypeVersion,
|
||||
ox::CRStringView dstTypeName,
|
||||
int dstTypeVersion) noexcept {
|
||||
for (auto &c : converters) {
|
||||
if (c->matches(srcTypeName, srcTypeVersion, dstTypeName, dstTypeVersion)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return OxError(1, "Could not find converter");
|
||||
};
|
||||
|
||||
static ox::Result<ox::UniquePtr<Wrap>> convert(
|
||||
[[maybe_unused]] keel::Context &ctx,
|
||||
ox::Vector<const BaseConverter*> const&converters,
|
||||
[[maybe_unused]] ox::Buffer const&srcBuffer,
|
||||
[[maybe_unused]] ox::CRStringView srcTypeName,
|
||||
[[maybe_unused]] int srcTypeVersion,
|
||||
[[maybe_unused]] ox::CRStringView dstTypeName,
|
||||
[[maybe_unused]] int dstTypeVersion) noexcept {
|
||||
// look for direct converter
|
||||
auto [c, err] = findConverter(converters, srcTypeName, srcTypeVersion, dstTypeName, dstTypeVersion);
|
||||
if (!err) {
|
||||
return c->convertBuffToPtr(ctx, srcBuffer);
|
||||
}
|
||||
// try to chain multiple converters
|
||||
for (const auto &subConverter : converters) {
|
||||
if (!subConverter->dstMatches(dstTypeName, dstTypeVersion)) {
|
||||
continue;
|
||||
}
|
||||
const auto [intermediate, chainErr] =
|
||||
convert(ctx, converters, srcBuffer, srcTypeName, srcTypeVersion,
|
||||
subConverter->srcTypeName(), subConverter->srcTypeVersion());
|
||||
if (!chainErr) {
|
||||
return subConverter->convertPtrToPtr(ctx, *intermediate);
|
||||
}
|
||||
}
|
||||
return OxError(1, "Could not convert between types");
|
||||
}
|
||||
|
||||
ox::Result<ox::UniquePtr<Wrap>> convert(
|
||||
[[maybe_unused]] keel::Context &ctx,
|
||||
[[maybe_unused]] ox::Buffer const&srcBuffer,
|
||||
[[maybe_unused]] ox::CRStringView dstTypeName,
|
||||
[[maybe_unused]] int dstTypeVersion) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
oxRequire(hdr, readAssetHeader(srcBuffer));
|
||||
return convert(
|
||||
ctx,
|
||||
ctx.converters,
|
||||
srcBuffer,
|
||||
hdr.clawHdr.typeName,
|
||||
hdr.clawHdr.typeVersion,
|
||||
dstTypeName,
|
||||
dstTypeVersion);
|
||||
#else
|
||||
return OxError(1, "Operation not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
22
deps/nostalgia/src/olympic/keel/src/typestore.cpp
vendored
Normal file
22
deps/nostalgia/src/olympic/keel/src/typestore.cpp
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <keel/typestore.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
TypeStore::TypeStore(ox::FileSystem &fs, ox::StringView descPath) noexcept:
|
||||
m_fs(fs),
|
||||
m_descPath(descPath) {
|
||||
}
|
||||
|
||||
ox::Result<ox::UPtr<ox::DescriptorType>> TypeStore::loadDescriptor(ox::CRStringView typeId) noexcept {
|
||||
auto path = ox::sfmt("{}/{}", m_descPath, typeId);
|
||||
oxRequire(buff, m_fs.read(path));
|
||||
auto dt = ox::make_unique<ox::DescriptorType>();
|
||||
oxReturnError(ox::readClaw<ox::DescriptorType>(buff, dt.get()));
|
||||
return dt;
|
||||
}
|
||||
|
||||
}
|
11
deps/nostalgia/src/olympic/keel/test/CMakeLists.txt
vendored
Normal file
11
deps/nostalgia/src/olympic/keel/test/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
add_executable(
|
||||
KeelTest
|
||||
tests.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
KeelTest
|
||||
Keel
|
||||
)
|
||||
|
||||
add_test("[keel] writeUuidHeader" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/KeelTest writeUuidHeader)
|
39
deps/nostalgia/src/olympic/keel/test/tests.cpp
vendored
Normal file
39
deps/nostalgia/src/olympic/keel/test/tests.cpp
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#undef NDEBUG
|
||||
|
||||
#include <map>
|
||||
#include <ox/std/std.hpp>
|
||||
|
||||
#include <keel/keel.hpp>
|
||||
|
||||
static std::map<ox::StringView, ox::Error(*)()> tests = {
|
||||
{
|
||||
"writeUuidHeader",
|
||||
[]() -> ox::Error {
|
||||
constexpr ox::StringView uuidStr = "8d814442-f46e-4cc3-8edc-ca3c01cc86db";
|
||||
constexpr ox::StringView hdr = "K1;8d814442-f46e-4cc3-8edc-ca3c01cc86db;";
|
||||
oxRequire(uuid, ox::UUID::fromString(uuidStr));
|
||||
ox::Array<char, hdr.len()> buff;
|
||||
ox::CharBuffWriter bw(buff);
|
||||
oxReturnError(keel::writeUuidHeader(bw, uuid));
|
||||
oxExpect(ox::StringView(buff.data(), buff.size()), hdr);
|
||||
return {};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
int main(int argc, const char **args) {
|
||||
int retval = -1;
|
||||
if (argc > 0) {
|
||||
auto testName = args[1];
|
||||
if (tests.find(testName) != tests.end()) {
|
||||
retval = static_cast<int>(tests[testName]());
|
||||
} else {
|
||||
retval = 1;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
2
deps/nostalgia/src/olympic/studio/CMakeLists.txt
vendored
Normal file
2
deps/nostalgia/src/olympic/studio/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
add_subdirectory(applib)
|
||||
add_subdirectory(modlib)
|
1
deps/nostalgia/src/olympic/studio/applib/CMakeLists.txt
vendored
Normal file
1
deps/nostalgia/src/olympic/studio/applib/CMakeLists.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
add_subdirectory(src)
|
15
deps/nostalgia/src/olympic/studio/applib/include/studioapp/studioapp.hpp
vendored
Normal file
15
deps/nostalgia/src/olympic/studio/applib/include/studioapp/studioapp.hpp
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <studio/module.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
void registerModule(studio::Module const*) noexcept;
|
||||
|
||||
}
|
42
deps/nostalgia/src/olympic/studio/applib/src/CMakeLists.txt
vendored
Normal file
42
deps/nostalgia/src/olympic/studio/applib/src/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
add_library(
|
||||
StudioAppLib
|
||||
aboutpopup.cpp
|
||||
clawviewer.cpp
|
||||
filedialogmanager.cpp
|
||||
main.cpp
|
||||
newmenu.cpp
|
||||
projectexplorer.cpp
|
||||
projecttreemodel.cpp
|
||||
studioapp.cpp
|
||||
)
|
||||
target_compile_definitions(
|
||||
StudioAppLib PUBLIC
|
||||
OLYMPIC_LOAD_STUDIO_MODULES=1
|
||||
OLYMPIC_APP_NAME="Studio"
|
||||
)
|
||||
target_link_libraries(
|
||||
StudioAppLib PUBLIC
|
||||
OxClArgs
|
||||
OxLogConn
|
||||
Studio
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
StudioAppLib PUBLIC
|
||||
../include
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY
|
||||
../include/studioapp
|
||||
DESTINATION
|
||||
include
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
StudioAppLib
|
||||
DESTINATION
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
61
deps/nostalgia/src/olympic/studio/applib/src/aboutpopup.cpp
vendored
Normal file
61
deps/nostalgia/src/olympic/studio/applib/src/aboutpopup.cpp
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <studio/imguiuitl.hpp>
|
||||
#include "aboutpopup.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
AboutPopup::AboutPopup(turbine::Context &ctx) noexcept {
|
||||
m_text = ox::sfmt("{} - dev build", keelCtx(ctx).appName);
|
||||
}
|
||||
|
||||
void AboutPopup::open() noexcept {
|
||||
m_stage = Stage::Opening;
|
||||
}
|
||||
|
||||
void AboutPopup::close() noexcept {
|
||||
m_stage = Stage::Closed;
|
||||
}
|
||||
|
||||
bool AboutPopup::isOpen() const noexcept {
|
||||
return m_stage == Stage::Open;
|
||||
}
|
||||
|
||||
void AboutPopup::draw(turbine::Context &ctx) noexcept {
|
||||
switch (m_stage) {
|
||||
case Stage::Closed:
|
||||
break;
|
||||
case Stage::Opening:
|
||||
ImGui::OpenPopup("About");
|
||||
m_stage = Stage::Open;
|
||||
[[fallthrough]];
|
||||
case Stage::Open: {
|
||||
constexpr auto modalFlags =
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
||||
ImGui::SetNextWindowSize(ImVec2(215, 90));
|
||||
studio::ig::centerNextWindow(ctx);
|
||||
auto open = true;
|
||||
if (ImGui::BeginPopupModal("About", &open, modalFlags)) {
|
||||
ImGui::Text("%s", m_text.c_str());
|
||||
ImGui::NewLine();
|
||||
ImGui::Dummy(ImVec2(148.0f, 0.0f));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
open = false;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
if (!open) {
|
||||
m_stage = Stage::Closed;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
42
deps/nostalgia/src/olympic/studio/applib/src/aboutpopup.hpp
vendored
Normal file
42
deps/nostalgia/src/olympic/studio/applib/src/aboutpopup.hpp
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include <studio/popup.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
class AboutPopup: public studio::Popup {
|
||||
public:
|
||||
enum class Stage {
|
||||
Closed,
|
||||
Opening,
|
||||
Open,
|
||||
};
|
||||
|
||||
private:
|
||||
Stage m_stage = Stage::Closed;
|
||||
ox::String m_text;
|
||||
|
||||
public:
|
||||
explicit AboutPopup(turbine::Context &ctx) noexcept;
|
||||
|
||||
void open() noexcept override;
|
||||
|
||||
void close() noexcept override;
|
||||
|
||||
[[nodiscard]]
|
||||
bool isOpen() const noexcept override;
|
||||
|
||||
void draw(turbine::Context &ctx) noexcept override;
|
||||
|
||||
};
|
||||
|
||||
}
|
174
deps/nostalgia/src/olympic/studio/applib/src/clawviewer.cpp
vendored
Normal file
174
deps/nostalgia/src/olympic/studio/applib/src/clawviewer.cpp
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "clawviewer.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
ClawEditor::ClawEditor(ox::CRStringView path, ox::ModelObject obj) noexcept:
|
||||
Editor(path),
|
||||
m_obj(std::move(obj)) {
|
||||
}
|
||||
|
||||
void ClawEditor::draw(turbine::Context&) noexcept {
|
||||
ImGui::BeginChild("PaletteEditor");
|
||||
static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
|
||||
if (ImGui::BeginTable("ObjTree", 3, flags)) {
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 100);
|
||||
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 250);
|
||||
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_NoHide);
|
||||
ImGui::TableHeadersRow();
|
||||
ObjPath objPath;
|
||||
drawTree(objPath, m_obj);
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void ClawEditor::drawRow(ox::ModelValue const&value) noexcept {
|
||||
using Str = ox::BasicString<100>;
|
||||
Str val, type;
|
||||
switch (value.type()) {
|
||||
case ox::ModelValue::Type::Undefined:
|
||||
val = "undefined";
|
||||
type = "undefined";
|
||||
break;
|
||||
case ox::ModelValue::Type::Bool:
|
||||
val = value.get<bool>() ? "true" : "false";
|
||||
type = "bool";
|
||||
break;
|
||||
case ox::ModelValue::Type::UnsignedInteger8:
|
||||
val = ox::sfmt<Str>("{}", value.get<uint8_t>());
|
||||
type = "uint8";
|
||||
break;
|
||||
case ox::ModelValue::Type::UnsignedInteger16:
|
||||
val = ox::sfmt<Str>("{}", value.get<uint16_t>());
|
||||
type = "uint16";
|
||||
break;
|
||||
case ox::ModelValue::Type::UnsignedInteger32:
|
||||
val = ox::sfmt<Str>("{}", value.get<uint32_t>());
|
||||
type = "uint32";
|
||||
break;
|
||||
case ox::ModelValue::Type::UnsignedInteger64:
|
||||
val = ox::sfmt<Str>("{}", value.get<uint64_t>());
|
||||
type = "uint64";
|
||||
break;
|
||||
case ox::ModelValue::Type::SignedInteger8:
|
||||
val = ox::sfmt<Str>("{}", value.get<int8_t>());
|
||||
type = "int8";
|
||||
break;
|
||||
case ox::ModelValue::Type::SignedInteger16:
|
||||
val = ox::sfmt<Str>("{}", value.get<int16_t>());
|
||||
type = "int16";
|
||||
break;
|
||||
case ox::ModelValue::Type::SignedInteger32:
|
||||
val = ox::sfmt<Str>("{}", value.get<int32_t>());
|
||||
type = "int32";
|
||||
break;
|
||||
case ox::ModelValue::Type::SignedInteger64:
|
||||
val = ox::sfmt<Str>("{}", value.get<int64_t>());
|
||||
type = "int64";
|
||||
break;
|
||||
case ox::ModelValue::Type::String:
|
||||
val = ox::sfmt<Str>("\"{}\"", value.get<ox::String>());
|
||||
type = "string";
|
||||
break;
|
||||
case ox::ModelValue::Type::Object:
|
||||
type = value.get<ox::ModelObject>().type()->typeName.c_str();
|
||||
break;
|
||||
case ox::ModelValue::Type::Union:
|
||||
type = "union";
|
||||
break;
|
||||
case ox::ModelValue::Type::Vector:
|
||||
type = "list";
|
||||
break;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", type.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", val.c_str());
|
||||
}
|
||||
|
||||
void ClawEditor::drawVar(ObjPath &path, ox::CRStringView name, ox::ModelValue const&value) noexcept {
|
||||
using Str = ox::BasicString<100>;
|
||||
path.push_back(name);
|
||||
if (value.type() == ox::ModelValue::Type::Object) {
|
||||
drawTree(path, value.get<ox::ModelObject>());
|
||||
} else if (value.type() == ox::ModelValue::Type::Vector) {
|
||||
auto const&vec = value.get<ox::ModelValueVector>();
|
||||
auto const pathStr = ox::join<Str>("##", path).unwrap();
|
||||
auto const lbl = ox::sfmt<Str>("{}##{}", name, pathStr);
|
||||
auto const flags = ImGuiTreeNodeFlags_SpanFullWidth
|
||||
| ImGuiTreeNodeFlags_OpenOnArrow
|
||||
| (vec.size() ? 0 : ImGuiTreeNodeFlags_Leaf)
|
||||
| (false ? ImGuiTreeNodeFlags_Selected : 0);
|
||||
auto const open = ImGui::TreeNodeEx(lbl.c_str(), flags);
|
||||
ImGui::SameLine();
|
||||
drawRow(value);
|
||||
if (open) {
|
||||
for (auto i = 0lu; auto const&e: vec) {
|
||||
auto const iStr = ox::sfmt<Str>("{}", i);
|
||||
path.push_back(iStr);
|
||||
ImGui::TableNextRow(0, 5);
|
||||
ImGui::TableNextColumn();
|
||||
drawVar(path, ox::sfmt<Str>("[{}]", i), e);
|
||||
path.pop_back();
|
||||
++i;
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
} else {
|
||||
auto const pathStr = ox::join<Str>("##", path).unwrap();
|
||||
auto const lbl = ox::sfmt<Str>("{}##{}", name, pathStr);
|
||||
auto const flags = ImGuiTreeNodeFlags_SpanFullWidth
|
||||
| ImGuiTreeNodeFlags_OpenOnArrow
|
||||
| ImGuiTreeNodeFlags_Leaf
|
||||
| (false ? ImGuiTreeNodeFlags_Selected : 0);
|
||||
auto const open = ImGui::TreeNodeEx(lbl.c_str(), flags);
|
||||
ImGui::SameLine();
|
||||
drawRow(value);
|
||||
if (open) {
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
path.pop_back();
|
||||
}
|
||||
|
||||
void ClawEditor::drawTree(ObjPath &path, ox::ModelObject const&obj) noexcept {
|
||||
using Str = ox::BasicString<100>;
|
||||
for (const auto &c : obj) {
|
||||
ImGui::TableNextRow(0, 5);
|
||||
auto pathStr = ox::join<Str>("##", path).unwrap();
|
||||
auto lbl = ox::sfmt<Str>("{}##{}", c->name, pathStr);
|
||||
const auto rowSelected = false;
|
||||
const auto hasChildren = c->value.type() == ox::ModelValue::Type::Object
|
||||
|| c->value.type() == ox::ModelValue::Type::Vector;
|
||||
const auto flags = ImGuiTreeNodeFlags_SpanFullWidth
|
||||
| ImGuiTreeNodeFlags_OpenOnArrow
|
||||
| (hasChildren ? 0 : ImGuiTreeNodeFlags_Leaf)
|
||||
| (rowSelected ? ImGuiTreeNodeFlags_Selected : 0);
|
||||
ImGui::TableNextColumn();
|
||||
//if (ImGui::IsItemClicked()) {
|
||||
//}
|
||||
//if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
|
||||
//}
|
||||
path.push_back(c->name);
|
||||
if (c->value.type() == ox::ModelValue::Type::Object) {
|
||||
const auto open = ImGui::TreeNodeEx(lbl.c_str(), flags);
|
||||
ImGui::SameLine();
|
||||
drawRow(c->value);
|
||||
if (open) {
|
||||
drawTree(path, c->value.get<ox::ModelObject>());
|
||||
ImGui::TreePop();
|
||||
}
|
||||
} else {
|
||||
drawVar(path, c->name, c->value);
|
||||
}
|
||||
path.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
31
deps/nostalgia/src/olympic/studio/applib/src/clawviewer.hpp
vendored
Normal file
31
deps/nostalgia/src/olympic/studio/applib/src/clawviewer.hpp
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/model/modelvalue.hpp>
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include <studio/editor.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
class ClawEditor: public studio::Editor {
|
||||
private:
|
||||
using ObjPath = ox::Vector<ox::StringView, 8>;
|
||||
ox::ModelObject m_obj;
|
||||
public:
|
||||
ClawEditor(ox::CRStringView path, ox::ModelObject obj) noexcept;
|
||||
|
||||
void draw(turbine::Context&) noexcept final;
|
||||
|
||||
private:
|
||||
static void drawRow(ox::ModelValue const&value) noexcept;
|
||||
|
||||
void drawVar(ObjPath &path, ox::CRStringView name, ox::ModelValue const&value) noexcept;
|
||||
|
||||
void drawTree(ObjPath &path, ox::ModelObject const&obj) noexcept;
|
||||
};
|
||||
|
||||
}
|
37
deps/nostalgia/src/olympic/studio/applib/src/filedialogmanager.cpp
vendored
Normal file
37
deps/nostalgia/src/olympic/studio/applib/src/filedialogmanager.cpp
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <turbine/gfx.hpp>
|
||||
|
||||
#include "filedialogmanager.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
studio::TaskState FileDialogManager::update(turbine::Context &ctx) noexcept {
|
||||
switch (m_state) {
|
||||
case UpdateProjectPathState::EnableSystemCursor: {
|
||||
// switch to system cursor in this update and open file dialog in the next
|
||||
// the cursor state has to be set before the ImGui frame starts
|
||||
m_state = UpdateProjectPathState::RunFileDialog;
|
||||
break;
|
||||
}
|
||||
case UpdateProjectPathState::RunFileDialog: {
|
||||
// switch to system cursor
|
||||
auto [path, err] = studio::chooseDirectory();
|
||||
// Mac file dialog doesn't restore focus to main window when closed...
|
||||
turbine::focusWindow(ctx);
|
||||
if (!err) {
|
||||
err = pathChosen.emitCheckError(path);
|
||||
oxAssert(err, "Path chosen response failed");
|
||||
}
|
||||
m_state = UpdateProjectPathState::None;
|
||||
return studio::TaskState::Done;
|
||||
}
|
||||
case UpdateProjectPathState::None:
|
||||
break;
|
||||
}
|
||||
return studio::TaskState::Running;
|
||||
}
|
||||
|
||||
}
|
43
deps/nostalgia/src/olympic/studio/applib/src/filedialogmanager.hpp
vendored
Normal file
43
deps/nostalgia/src/olympic/studio/applib/src/filedialogmanager.hpp
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include <studio/filedialog.hpp>
|
||||
#include <studio/task.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
class FileDialogManager: public studio::Task {
|
||||
private:
|
||||
enum class UpdateProjectPathState {
|
||||
None,
|
||||
EnableSystemCursor,
|
||||
RunFileDialog,
|
||||
Start = EnableSystemCursor,
|
||||
} m_state = UpdateProjectPathState::Start;
|
||||
|
||||
public:
|
||||
FileDialogManager() noexcept = default;
|
||||
|
||||
template<class... Args>
|
||||
explicit FileDialogManager(Args ...args) noexcept {
|
||||
pathChosen.connect(args...);
|
||||
}
|
||||
|
||||
~FileDialogManager() noexcept override = default;
|
||||
|
||||
studio::TaskState update(turbine::Context &ctx) noexcept final;
|
||||
|
||||
// signals
|
||||
ox::Signal<ox::Error(ox::String const&)> pathChosen;
|
||||
|
||||
};
|
||||
|
||||
}
|
95
deps/nostalgia/src/olympic/studio/applib/src/main.cpp
vendored
Normal file
95
deps/nostalgia/src/olympic/studio/applib/src/main.cpp
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#include <ox/logconn/logconn.hpp>
|
||||
#include <ox/logconn/def.hpp>
|
||||
#include <ox/std/trace.hpp>
|
||||
#include <ox/std/uuid.hpp>
|
||||
#include <keel/media.hpp>
|
||||
#include <turbine/turbine.hpp>
|
||||
|
||||
#include <studio/context.hpp>
|
||||
#include <studioapp/studioapp.hpp>
|
||||
#include "studioapp.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
class StudioUIDrawer: public turbine::gl::Drawer {
|
||||
private:
|
||||
StudioUI &m_ui;
|
||||
public:
|
||||
explicit StudioUIDrawer(StudioUI &ui) noexcept: m_ui(ui) {
|
||||
}
|
||||
protected:
|
||||
void draw(turbine::Context&) noexcept final {
|
||||
m_ui.draw();
|
||||
}
|
||||
};
|
||||
|
||||
static int updateHandler(turbine::Context &ctx) noexcept {
|
||||
auto sctx = turbine::applicationData<studio::StudioContext>(ctx);
|
||||
auto ui = dynamic_cast<StudioUI*>(sctx->ui);
|
||||
ui->update();
|
||||
return 16;
|
||||
}
|
||||
|
||||
static void keyEventHandler(turbine::Context &ctx, turbine::Key key, bool down) noexcept {
|
||||
auto sctx = turbine::applicationData<studio::StudioContext>(ctx);
|
||||
auto ui = dynamic_cast<StudioUI*>(sctx->ui);
|
||||
ui->handleKeyEvent(key, down);
|
||||
}
|
||||
|
||||
static ox::Error runApp(
|
||||
ox::CRStringView appName,
|
||||
ox::CRStringView projectDataDir,
|
||||
ox::UniquePtr<ox::FileSystem> fs) noexcept {
|
||||
oxRequireM(ctx, turbine::init(std::move(fs), appName));
|
||||
turbine::setWindowTitle(*ctx, keelCtx(*ctx).appName);
|
||||
turbine::setUpdateHandler(*ctx, updateHandler);
|
||||
turbine::setKeyEventHandler(*ctx, keyEventHandler);
|
||||
turbine::setConstantRefresh(*ctx, false);
|
||||
studio::StudioContext studioCtx;
|
||||
turbine::setApplicationData(*ctx, &studioCtx);
|
||||
StudioUI ui(*ctx, projectDataDir);
|
||||
studioCtx.ui = &ui;
|
||||
StudioUIDrawer drawer(ui);
|
||||
turbine::gl::addDrawer(*ctx, &drawer);
|
||||
auto const err = turbine::run(*ctx);
|
||||
turbine::gl::removeDrawer(*ctx, &drawer);
|
||||
return err;
|
||||
}
|
||||
|
||||
ox::Error run(
|
||||
ox::CRStringView appName,
|
||||
ox::CRStringView projectDataDir,
|
||||
int,
|
||||
char const**) {
|
||||
// seed UUID generator
|
||||
auto const time = std::time(nullptr);
|
||||
ox::UUID::seedGenerator({
|
||||
static_cast<uint64_t>(time),
|
||||
static_cast<uint64_t>(time << 1)
|
||||
});
|
||||
// run app
|
||||
auto const err = runApp(appName, projectDataDir, ox::UniquePtr<ox::FileSystem>(nullptr));
|
||||
oxAssert(err, "Something went wrong...");
|
||||
return err;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace olympic {
|
||||
|
||||
ox::Error run(
|
||||
ox::StringView project,
|
||||
ox::StringView appName,
|
||||
ox::StringView projectDataDir,
|
||||
int argc,
|
||||
char const**argv) noexcept {
|
||||
return studio::run(ox::sfmt("{} {}", project, appName), projectDataDir, argc, argv);
|
||||
}
|
||||
|
||||
}
|
127
deps/nostalgia/src/olympic/studio/applib/src/newmenu.cpp
vendored
Normal file
127
deps/nostalgia/src/olympic/studio/applib/src/newmenu.cpp
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <studio/imguiuitl.hpp>
|
||||
|
||||
#include "newmenu.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
NewMenu::NewMenu() noexcept {
|
||||
setTitle(ox::String("New Item"));
|
||||
setSize({230, 140});
|
||||
}
|
||||
|
||||
void NewMenu::open() noexcept {
|
||||
m_stage = Stage::Opening;
|
||||
m_selectedType = 0;
|
||||
m_itemName = "";
|
||||
m_typeName = "";
|
||||
}
|
||||
|
||||
void NewMenu::close() noexcept {
|
||||
m_stage = Stage::Closed;
|
||||
m_open = false;
|
||||
}
|
||||
|
||||
bool NewMenu::isOpen() const noexcept {
|
||||
return m_open;
|
||||
}
|
||||
|
||||
void NewMenu::draw(turbine::Context &ctx) noexcept {
|
||||
switch (m_stage) {
|
||||
case Stage::Opening:
|
||||
ImGui::OpenPopup(title().c_str());
|
||||
m_stage = Stage::NewItemType;
|
||||
m_open = true;
|
||||
[[fallthrough]];
|
||||
case Stage::NewItemType:
|
||||
drawNewItemType(ctx);
|
||||
break;
|
||||
case Stage::NewItemName:
|
||||
drawNewItemName(ctx);
|
||||
break;
|
||||
case Stage::Closed:
|
||||
m_open = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NewMenu::addItemMaker(ox::UniquePtr<studio::ItemMaker> &&im) noexcept {
|
||||
m_types.emplace_back(std::move(im));
|
||||
std::sort(
|
||||
m_types.begin(), m_types.end(),
|
||||
[](ox::UPtr<ItemMaker> const&im1, ox::UPtr<ItemMaker> const&im2) {
|
||||
return im1->name < im2->name;
|
||||
});
|
||||
}
|
||||
|
||||
void NewMenu::drawNewItemType(turbine::Context &ctx) noexcept {
|
||||
drawWindow(ctx, &m_open, [this] {
|
||||
auto items = ox_malloca(m_types.size() * sizeof(char const*), char const*, nullptr);
|
||||
for (auto i = 0u; auto const&im : m_types) {
|
||||
items.get()[i] = im->name.c_str();
|
||||
++i;
|
||||
}
|
||||
ImGui::ListBox("Item Type", &m_selectedType, items.get(), static_cast<int>(m_types.size()));
|
||||
drawFirstPageButtons();
|
||||
});
|
||||
}
|
||||
|
||||
void NewMenu::drawNewItemName(turbine::Context &ctx) noexcept {
|
||||
drawWindow(ctx, &m_open, [this, &ctx] {
|
||||
auto const typeIdx = static_cast<std::size_t>(m_selectedType);
|
||||
if (typeIdx < m_types.size()) {
|
||||
ImGui::InputText("Name", m_itemName.data(), m_itemName.cap());
|
||||
}
|
||||
drawLastPageButtons(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
void NewMenu::drawFirstPageButtons() noexcept {
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 130);
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 20);
|
||||
auto const btnSz = ImVec2(60, 20);
|
||||
if (ImGui::Button("Next", btnSz)) {
|
||||
m_stage = Stage::NewItemName;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", btnSz)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
m_stage = Stage::Closed;
|
||||
}
|
||||
}
|
||||
|
||||
void NewMenu::drawLastPageButtons(turbine::Context &ctx) noexcept {
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 138);
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 20);
|
||||
if (ImGui::Button("Back")) {
|
||||
m_stage = Stage::NewItemType;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Finish")) {
|
||||
finish(ctx);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Quit")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
m_stage = Stage::Closed;
|
||||
}
|
||||
}
|
||||
|
||||
void NewMenu::finish(turbine::Context &ctx) noexcept {
|
||||
auto const err = m_types[static_cast<std::size_t>(m_selectedType)]->write(ctx, m_itemName);
|
||||
if (err) {
|
||||
oxLogError(err);
|
||||
return;
|
||||
}
|
||||
finished.emit("");
|
||||
m_stage = Stage::Closed;
|
||||
}
|
||||
|
||||
}
|
79
deps/nostalgia/src/olympic/studio/applib/src/newmenu.hpp
vendored
Normal file
79
deps/nostalgia/src/olympic/studio/applib/src/newmenu.hpp
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/claw/claw.hpp>
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <studio/itemmaker.hpp>
|
||||
#include <studio/popup.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
class NewMenu: public studio::Popup {
|
||||
public:
|
||||
enum class Stage {
|
||||
Closed,
|
||||
Opening,
|
||||
NewItemType,
|
||||
NewItemName,
|
||||
};
|
||||
|
||||
// emits path parameter
|
||||
ox::Signal<ox::Error(ox::StringView)> finished;
|
||||
|
||||
private:
|
||||
Stage m_stage = Stage::Closed;
|
||||
ox::String m_typeName;
|
||||
ox::BString<255> m_itemName;
|
||||
ox::Vector<ox::UniquePtr<studio::ItemMaker>> m_types;
|
||||
int m_selectedType = 0;
|
||||
bool m_open = false;
|
||||
|
||||
public:
|
||||
NewMenu() noexcept;
|
||||
|
||||
void open() noexcept override;
|
||||
|
||||
void close() noexcept override;
|
||||
|
||||
[[nodiscard]]
|
||||
bool isOpen() const noexcept override;
|
||||
|
||||
void draw(turbine::Context &ctx) noexcept override;
|
||||
|
||||
template<typename T>
|
||||
void addItemType(ox::String name, ox::String parentDir, ox::String fileExt, T itemTempl, ox::ClawFormat pFmt = ox::ClawFormat::Metal) noexcept;
|
||||
|
||||
template<typename T>
|
||||
void addItemType(ox::String name, ox::String parentDir, ox::String fileExt, ox::ClawFormat pFmt = ox::ClawFormat::Metal) noexcept;
|
||||
|
||||
void addItemMaker(ox::UniquePtr<studio::ItemMaker> &&im) noexcept;
|
||||
|
||||
private:
|
||||
void drawNewItemType(turbine::Context &ctx) noexcept;
|
||||
|
||||
void drawNewItemName(turbine::Context &ctx) noexcept;
|
||||
|
||||
void drawFirstPageButtons() noexcept;
|
||||
|
||||
void drawLastPageButtons(turbine::Context &ctx) noexcept;
|
||||
|
||||
void finish(turbine::Context &ctx) noexcept;
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
void NewMenu::addItemType(ox::String displayName, ox::String parentDir, ox::String fileExt, T itemTempl, ox::ClawFormat pFmt) noexcept {
|
||||
m_types.emplace_back(ox::make<studio::ItemMakerT<T>>(std::move(displayName), std::move(parentDir), std::move(fileExt), std::move(itemTempl), pFmt));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void NewMenu::addItemType(ox::String displayName, ox::String parentDir, ox::String fileExt, ox::ClawFormat pFmt) noexcept {
|
||||
m_types.emplace_back(ox::make<studio::ItemMakerT<T>>(std::move(displayName), std::move(parentDir), std::move(fileExt), pFmt));
|
||||
}
|
||||
|
||||
}
|
57
deps/nostalgia/src/olympic/studio/applib/src/projectexplorer.cpp
vendored
Normal file
57
deps/nostalgia/src/olympic/studio/applib/src/projectexplorer.cpp
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "projectexplorer.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
static ox::Result<ox::UniquePtr<ProjectTreeModel>>
|
||||
buildProjectTreeModel(ProjectExplorer &explorer, ox::StringView name, ox::CRStringView path, ProjectTreeModel *parent) noexcept {
|
||||
auto const fs = explorer.romFs();
|
||||
oxRequire(stat, fs->stat(path));
|
||||
auto out = ox::make_unique<ProjectTreeModel>(explorer, ox::String(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 (auto const&childName : children) {
|
||||
if (childName[0] != '.') {
|
||||
auto const 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(turbine::Context &ctx) noexcept: m_ctx(ctx) {
|
||||
}
|
||||
|
||||
void ProjectExplorer::draw(turbine::Context &ctx) noexcept {
|
||||
auto const viewport = ImGui::GetContentRegionAvail();
|
||||
ImGui::BeginChild("ProjectExplorer", ImVec2(300, viewport.y), true);
|
||||
ImGui::SetNextItemOpen(true);
|
||||
if (m_treeModel) {
|
||||
m_treeModel->draw(ctx);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void ProjectExplorer::setModel(ox::UPtr<ProjectTreeModel> &&model) noexcept {
|
||||
m_treeModel = std::move(model);
|
||||
}
|
||||
|
||||
ox::Error ProjectExplorer::refreshProjectTreeModel(ox::CRStringView) noexcept {
|
||||
oxRequireM(model, buildProjectTreeModel(*this, "Project", "/", nullptr));
|
||||
setModel(std::move(model));
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
}
|
38
deps/nostalgia/src/olympic/studio/applib/src/projectexplorer.hpp
vendored
Normal file
38
deps/nostalgia/src/olympic/studio/applib/src/projectexplorer.hpp
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/std/memory.hpp>
|
||||
|
||||
#include <studio/widget.hpp>
|
||||
#include "projecttreemodel.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
class ProjectExplorer: public studio::Widget {
|
||||
private:
|
||||
ox::UPtr<ProjectTreeModel> m_treeModel;
|
||||
turbine::Context &m_ctx;
|
||||
public:
|
||||
explicit ProjectExplorer(turbine::Context &ctx) noexcept;
|
||||
|
||||
void draw(turbine::Context &ctx) noexcept override;
|
||||
|
||||
void setModel(ox::UPtr<ProjectTreeModel> &&model) noexcept;
|
||||
|
||||
ox::Error refreshProjectTreeModel(ox::CRStringView = {}) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
inline ox::FileSystem *romFs() noexcept {
|
||||
return rom(m_ctx);
|
||||
}
|
||||
|
||||
// slots
|
||||
public:
|
||||
ox::Signal<ox::Error(ox::StringView const&)> fileChosen;
|
||||
};
|
||||
|
||||
}
|
58
deps/nostalgia/src/olympic/studio/applib/src/projecttreemodel.cpp
vendored
Normal file
58
deps/nostalgia/src/olympic/studio/applib/src/projecttreemodel.cpp
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "projectexplorer.hpp"
|
||||
#include "projecttreemodel.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
ProjectTreeModel::ProjectTreeModel(ProjectExplorer &explorer, ox::String name,
|
||||
ProjectTreeModel *parent) noexcept:
|
||||
m_explorer(explorer),
|
||||
m_parent(parent),
|
||||
m_name(std::move(name)) {
|
||||
}
|
||||
|
||||
ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept:
|
||||
m_explorer(other.m_explorer),
|
||||
m_parent(other.m_parent),
|
||||
m_name(std::move(other.m_name)),
|
||||
m_children(std::move(other.m_children)) {
|
||||
}
|
||||
|
||||
void ProjectTreeModel::draw(turbine::Context &ctx) const noexcept {
|
||||
constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
||||
if (!m_children.empty()) {
|
||||
if (ImGui::TreeNodeEx(m_name.c_str(), dirFlags)) {
|
||||
for (auto const&child : m_children) {
|
||||
child->draw(ctx);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
} else {
|
||||
auto const path = fullPath();
|
||||
auto const name = ox::sfmt<ox::BasicString<255>>("{}##{}", m_name, path);
|
||||
if (ImGui::TreeNodeEx(name.c_str(), ImGuiTreeNodeFlags_Leaf)) {
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
|
||||
m_explorer.fileChosen.emit(path);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectTreeModel::setChildren(ox::Vector<ox::UPtr<ProjectTreeModel>> children) noexcept {
|
||||
m_children = std::move(children);
|
||||
}
|
||||
|
||||
ox::BasicString<255> ProjectTreeModel::fullPath() const noexcept {
|
||||
if (m_parent) {
|
||||
return m_parent->fullPath() + "/" + ox::StringView(m_name);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
35
deps/nostalgia/src/olympic/studio/applib/src/projecttreemodel.hpp
vendored
Normal file
35
deps/nostalgia/src/olympic/studio/applib/src/projecttreemodel.hpp
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/memory.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
class ProjectTreeModel {
|
||||
private:
|
||||
class ProjectExplorer &m_explorer;
|
||||
ProjectTreeModel *m_parent = nullptr;
|
||||
ox::String m_name;
|
||||
ox::Vector<ox::UPtr<ProjectTreeModel>> m_children;
|
||||
public:
|
||||
explicit ProjectTreeModel(class ProjectExplorer &explorer, ox::String name,
|
||||
ProjectTreeModel *parent = nullptr) noexcept;
|
||||
|
||||
ProjectTreeModel(ProjectTreeModel &&other) noexcept;
|
||||
|
||||
void draw(turbine::Context &ctx) const noexcept;
|
||||
|
||||
void setChildren(ox::Vector<ox::UPtr<ProjectTreeModel>> children) noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]]
|
||||
ox::BasicString<255> fullPath() const noexcept;
|
||||
};
|
||||
|
||||
}
|
404
deps/nostalgia/src/olympic/studio/applib/src/studioapp.cpp
vendored
Normal file
404
deps/nostalgia/src/olympic/studio/applib/src/studioapp.cpp
vendored
Normal file
@ -0,0 +1,404 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
#include <glutils/glutils.hpp>
|
||||
#include <turbine/turbine.hpp>
|
||||
|
||||
#include <studio/configio.hpp>
|
||||
#include "clawviewer.hpp"
|
||||
#include "filedialogmanager.hpp"
|
||||
#include "studioapp.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
static ox::Vector<studio::Module const*> modules;
|
||||
|
||||
void registerModule(studio::Module const*mod) noexcept {
|
||||
modules.emplace_back(mod);
|
||||
}
|
||||
|
||||
|
||||
struct StudioConfig {
|
||||
static constexpr auto TypeName = "net.drinkingtea.studio.StudioConfig";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
ox::String projectPath;
|
||||
ox::String activeTabItemName;
|
||||
ox::Vector<ox::String> openFiles;
|
||||
bool showProjectExplorer = true;
|
||||
};
|
||||
|
||||
oxModelBegin(StudioConfig)
|
||||
oxModelFieldRename(active_tab_item_name, activeTabItemName)
|
||||
oxModelFieldRename(project_path, projectPath)
|
||||
oxModelFieldRename(open_files, openFiles)
|
||||
oxModelFieldRename(show_project_explorer, showProjectExplorer)
|
||||
oxModelEnd()
|
||||
|
||||
StudioUI::StudioUI(turbine::Context &ctx, ox::StringView projectDataDir) noexcept:
|
||||
m_ctx(ctx),
|
||||
m_projectDataDir(projectDataDir),
|
||||
m_projectExplorer(m_ctx),
|
||||
m_aboutPopup(m_ctx) {
|
||||
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
|
||||
ImGui::GetIO().IniFilename = nullptr;
|
||||
loadModules();
|
||||
// open project and files
|
||||
auto const [config, err] = studio::readConfig<StudioConfig>(keelCtx(m_ctx));
|
||||
m_showProjectExplorer = config.showProjectExplorer;
|
||||
if (!err) {
|
||||
auto const openProjErr = openProject(config.projectPath);
|
||||
if (!openProjErr) {
|
||||
for (auto const&f: config.openFiles) {
|
||||
auto openFileErr = openFileActiveTab(f, config.activeTabItemName == f);
|
||||
if (openFileErr) {
|
||||
oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if constexpr(!ox::defines::Debug) {
|
||||
oxErrf("Could not open studio config file: {}: {}\n", err.errCode, toStr(err));
|
||||
} else {
|
||||
oxErrf(
|
||||
"Could not open studio config file: {}: {} ({}:{})\n",
|
||||
err.errCode, toStr(err), err.file, err.line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::update() noexcept {
|
||||
m_taskRunner.update(m_ctx);
|
||||
}
|
||||
|
||||
void StudioUI::handleKeyEvent(turbine::Key key, bool down) noexcept {
|
||||
auto const ctrlDown = turbine::buttonDown(m_ctx, turbine::Key::Mod_Ctrl);
|
||||
for (auto p : m_popups) {
|
||||
if (p->isOpen()) {
|
||||
if (key == turbine::Key::Escape) {
|
||||
p->close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (down && ctrlDown) {
|
||||
switch (key) {
|
||||
case turbine::Key::Num_1:
|
||||
toggleProjectExplorer();
|
||||
break;
|
||||
case turbine::Key::Alpha_C:
|
||||
if (m_activeEditor && m_activeEditor->copyEnabled()) {
|
||||
m_activeEditor->copy();
|
||||
}
|
||||
break;
|
||||
case turbine::Key::Alpha_N:
|
||||
m_newMenu.open();
|
||||
break;
|
||||
case turbine::Key::Alpha_O:
|
||||
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProject));
|
||||
break;
|
||||
case turbine::Key::Alpha_Q:
|
||||
turbine::requestShutdown(m_ctx);
|
||||
break;
|
||||
case turbine::Key::Alpha_S:
|
||||
save();
|
||||
break;
|
||||
case turbine::Key::Alpha_V:
|
||||
if (m_activeEditor && m_activeEditor->pasteEnabled()) {
|
||||
m_activeEditor->paste();
|
||||
}
|
||||
break;
|
||||
case turbine::Key::Alpha_X:
|
||||
if (m_activeEditor && m_activeEditor->cutEnabled()) {
|
||||
m_activeEditor->cut();
|
||||
}
|
||||
break;
|
||||
case turbine::Key::Alpha_Y:
|
||||
redo();
|
||||
break;
|
||||
case turbine::Key::Alpha_Z:
|
||||
undo();
|
||||
break;
|
||||
default:
|
||||
if (m_activeEditor) {
|
||||
m_activeEditor->keyStateChanged(key, down);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (m_activeEditor && !ctrlDown) {
|
||||
m_activeEditor->keyStateChanged(key, down);
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::draw() noexcept {
|
||||
glutils::clearScreen();
|
||||
drawMenu();
|
||||
auto const viewport = ImGui::GetMainViewport();
|
||||
constexpr auto menuHeight = 18;
|
||||
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + menuHeight));
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, viewport->Size.y - menuHeight));
|
||||
constexpr auto windowFlags = ImGuiWindowFlags_NoTitleBar
|
||||
| ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoMove
|
||||
| ImGuiWindowFlags_NoScrollbar
|
||||
| ImGuiWindowFlags_NoSavedSettings;
|
||||
ImGui::Begin("MainWindow##Studio", nullptr, windowFlags);
|
||||
{
|
||||
if (m_showProjectExplorer) {
|
||||
m_projectExplorer.draw(m_ctx);
|
||||
ImGui::SameLine();
|
||||
}
|
||||
drawTabBar();
|
||||
for (auto &w: m_widgets) {
|
||||
w->draw(m_ctx);
|
||||
}
|
||||
for (auto p: m_popups) {
|
||||
p->draw(m_ctx);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void StudioUI::drawMenu() noexcept {
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("New...", "Ctrl+N")) {
|
||||
m_newMenu.open();
|
||||
}
|
||||
if (ImGui::MenuItem("Open Project...", "Ctrl+O")) {
|
||||
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProject));
|
||||
}
|
||||
if (ImGui::MenuItem("Save", "Ctrl+S", false, m_activeEditor && m_activeEditor->unsavedChanges())) {
|
||||
m_activeEditor->save();
|
||||
}
|
||||
if (ImGui::MenuItem("Quit", "Ctrl+Q")) {
|
||||
turbine::requestShutdown(m_ctx);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Edit")) {
|
||||
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
||||
if (ImGui::MenuItem("Undo", "Ctrl+Z", false, undoStack && undoStack->canUndo())) {
|
||||
undoStack->undo();
|
||||
}
|
||||
if (ImGui::MenuItem("Redo", "Ctrl+Y", false, undoStack && undoStack->canRedo())) {
|
||||
undoStack->redo();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Copy", "Ctrl+C", false, m_activeEditor && m_activeEditor->copyEnabled())) {
|
||||
m_activeEditor->copy();
|
||||
}
|
||||
if (ImGui::MenuItem("Cut", "Ctrl+X", false, m_activeEditor && m_activeEditor->cutEnabled())) {
|
||||
m_activeEditor->cut();
|
||||
}
|
||||
if (ImGui::MenuItem("Paste", "Ctrl+V", false, m_activeEditor && m_activeEditor->pasteEnabled()) && m_activeEditor) {
|
||||
m_activeEditor->paste();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
if (ImGui::MenuItem("Project Explorer", "Ctrl+1", m_showProjectExplorer)) {
|
||||
toggleProjectExplorer();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Help")) {
|
||||
if (ImGui::MenuItem("About")) {
|
||||
m_aboutPopup.open();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::drawTabBar() noexcept {
|
||||
auto const viewport = ImGui::GetContentRegionAvail();
|
||||
ImGui::BeginChild("TabWindow##MainWindow##Studio", ImVec2(viewport.x, viewport.y));
|
||||
constexpr auto tabBarFlags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_TabListPopupButton;
|
||||
if (ImGui::BeginTabBar("TabBar##TabWindow##MainWindow##Studio", tabBarFlags)) {
|
||||
drawTabs();
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void StudioUI::drawTabs() noexcept {
|
||||
for (auto it = m_editors.begin(); it != m_editors.end();) {
|
||||
auto const &e = *it;
|
||||
auto open = true;
|
||||
auto const unsavedChanges = e->unsavedChanges() ? ImGuiTabItemFlags_UnsavedDocument : 0;
|
||||
auto const selected = m_activeEditorUpdatePending == e.get() ? ImGuiTabItemFlags_SetSelected : 0;
|
||||
auto const flags = unsavedChanges | selected;
|
||||
if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open, flags)) {
|
||||
if (m_activeEditor != e.get()) {
|
||||
m_activeEditor = e.get();
|
||||
studio::editConfig<StudioConfig>(keelCtx(m_ctx), [&](StudioConfig *config) {
|
||||
config->activeTabItemName = m_activeEditor->itemPath();
|
||||
});
|
||||
}
|
||||
if (m_activeEditorUpdatePending == e.get()) {
|
||||
m_activeEditorUpdatePending = nullptr;
|
||||
}
|
||||
if (m_activeEditorOnLastDraw != e.get()) [[unlikely]] {
|
||||
m_activeEditor->onActivated();
|
||||
turbine::setConstantRefresh(m_ctx, m_activeEditor->requiresConstantRefresh());
|
||||
}
|
||||
e->draw(m_ctx);
|
||||
m_activeEditorOnLastDraw = e.get();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (!open) {
|
||||
e->close();
|
||||
if (m_activeEditor == (*it).get()) {
|
||||
m_activeEditor = nullptr;
|
||||
}
|
||||
try {
|
||||
oxThrowError(m_editors.erase(it).moveTo(it));
|
||||
} catch (ox::Exception const&ex) {
|
||||
oxErrf("Editor tab deletion failed: {} ({}:{})\n", ex.what(), ex.file, ex.line);
|
||||
} catch (std::exception const&ex) {
|
||||
oxErrf("Editor tab deletion failed: {}\n", ex.what());
|
||||
}
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::loadEditorMaker(studio::EditorMaker const&editorMaker) noexcept {
|
||||
for (auto const&ext : editorMaker.fileTypes) {
|
||||
m_editorMakers[ext] = editorMaker.make;
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::loadModule(studio::Module const*mod) noexcept {
|
||||
for (auto const&editorMaker : mod->editors(m_ctx)) {
|
||||
loadEditorMaker(editorMaker);
|
||||
}
|
||||
for (auto &im : mod->itemMakers(m_ctx)) {
|
||||
m_newMenu.addItemMaker(std::move(im));
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::loadModules() noexcept {
|
||||
for (auto const mod : modules) {
|
||||
loadModule(mod);
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::toggleProjectExplorer() noexcept {
|
||||
m_showProjectExplorer = !m_showProjectExplorer;
|
||||
studio::editConfig<StudioConfig>(keelCtx(m_ctx), [&](StudioConfig *config) {
|
||||
config->showProjectExplorer = m_showProjectExplorer;
|
||||
});
|
||||
}
|
||||
|
||||
void StudioUI::redo() noexcept {
|
||||
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
||||
if (undoStack && undoStack->canRedo()) {
|
||||
m_activeEditor->undoStack()->redo();
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::undo() noexcept {
|
||||
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
||||
if (undoStack && undoStack->canUndo()) {
|
||||
m_activeEditor->undoStack()->undo();
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::save() noexcept {
|
||||
if (m_activeEditor && m_activeEditor->unsavedChanges()) {
|
||||
m_activeEditor->save();
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error StudioUI::openProject(ox::CRStringView path) noexcept {
|
||||
oxRequireM(fs, keel::loadRomFs(path));
|
||||
oxReturnError(keel::setRomFs(keelCtx(m_ctx), std::move(fs)));
|
||||
turbine::setWindowTitle(m_ctx, ox::sfmt("{} - {}", keelCtx(m_ctx).appName, path));
|
||||
m_project = ox::make_unique<studio::Project>(keelCtx(m_ctx), ox::String(path), m_projectDataDir);
|
||||
auto sctx = applicationData<studio::StudioContext>(m_ctx);
|
||||
sctx->project = m_project.get();
|
||||
m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
|
||||
m_project->fileDeleted.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
|
||||
m_openFiles.clear();
|
||||
m_editors.clear();
|
||||
studio::editConfig<StudioConfig>(keelCtx(m_ctx), [&](StudioConfig *config) {
|
||||
config->projectPath = ox::String(path);
|
||||
config->openFiles.clear();
|
||||
});
|
||||
return m_projectExplorer.refreshProjectTreeModel();
|
||||
}
|
||||
|
||||
ox::Error StudioUI::openFile(ox::CRStringView path) noexcept {
|
||||
return openFileActiveTab(path, true);
|
||||
}
|
||||
|
||||
ox::Error StudioUI::openFileActiveTab(ox::CRStringView path, bool makeActiveTab) noexcept {
|
||||
if (!m_project) {
|
||||
return OxError(1, "No project open to open a file from");
|
||||
}
|
||||
if (m_openFiles.contains(path)) {
|
||||
for (auto &e : m_editors) {
|
||||
if (makeActiveTab && e->itemPath() == path) {
|
||||
m_activeEditor = e.get();
|
||||
m_activeEditorUpdatePending = e.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
oxRequire(ext, studio::fileExt(path).to<ox::String>([](auto const&v) {return ox::String(v);}));
|
||||
// create Editor
|
||||
studio::BaseEditor *editor = nullptr;
|
||||
if (!m_editorMakers.contains(ext)) {
|
||||
auto [obj, err] = m_project->loadObj<ox::ModelObject>(path);
|
||||
if (err) {
|
||||
return OxError(1, "There is no editor for this file extension");
|
||||
}
|
||||
editor = ox::make<ClawEditor>(path, std::move(obj));
|
||||
} else {
|
||||
auto const err = m_editorMakers[ext](path).moveTo(editor);
|
||||
if (err) {
|
||||
if constexpr(!ox::defines::Debug) {
|
||||
oxErrf("Could not open Editor: {}\n", toStr(err));
|
||||
} else {
|
||||
oxErrf("Could not open Editor: {} ({}:{})\n", err.errCode, err.file, err.line);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
editor->closed.connect(this, &StudioUI::closeFile);
|
||||
m_editors.emplace_back(editor);
|
||||
m_openFiles.emplace_back(path);
|
||||
if (makeActiveTab) {
|
||||
m_activeEditor = m_editors.back().value->get();
|
||||
m_activeEditorUpdatePending = editor;
|
||||
}
|
||||
// save to config
|
||||
studio::editConfig<StudioConfig>(keelCtx(m_ctx), [&](StudioConfig *config) {
|
||||
if (!config->openFiles.contains(path)) {
|
||||
config->openFiles.emplace_back(path);
|
||||
}
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error StudioUI::closeFile(ox::CRStringView path) noexcept {
|
||||
if (!m_openFiles.contains(path)) {
|
||||
return {};
|
||||
}
|
||||
oxIgnoreError(m_openFiles.erase(std::remove(m_openFiles.begin(), m_openFiles.end(), path)));
|
||||
// save to config
|
||||
studio::editConfig<StudioConfig>(keelCtx(m_ctx), [&](StudioConfig *config) {
|
||||
oxIgnoreError(config->openFiles.erase(std::remove(config->openFiles.begin(), config->openFiles.end(), path)));
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
91
deps/nostalgia/src/olympic/studio/applib/src/studioapp.hpp
vendored
Normal file
91
deps/nostalgia/src/olympic/studio/applib/src/studioapp.hpp
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/std/memory.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <studio/editor.hpp>
|
||||
#include <studio/module.hpp>
|
||||
#include <studio/project.hpp>
|
||||
#include <studio/task.hpp>
|
||||
#include "aboutpopup.hpp"
|
||||
#include "newmenu.hpp"
|
||||
#include "projectexplorer.hpp"
|
||||
#include "projecttreemodel.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
class StudioUI: public ox::SignalHandler {
|
||||
friend class StudioUIDrawer;
|
||||
|
||||
private:
|
||||
turbine::Context &m_ctx;
|
||||
ox::String m_projectDataDir;
|
||||
ox::UPtr<studio::Project> m_project;
|
||||
studio::TaskRunner m_taskRunner;
|
||||
ox::Vector<ox::UPtr<studio::BaseEditor>> m_editors;
|
||||
ox::Vector<ox::UPtr<studio::Widget>> m_widgets;
|
||||
ox::HashMap<ox::String, studio::EditorMaker::Func> m_editorMakers;
|
||||
ProjectExplorer m_projectExplorer;
|
||||
ox::Vector<ox::String> m_openFiles;
|
||||
studio::BaseEditor *m_activeEditorOnLastDraw = nullptr;
|
||||
studio::BaseEditor *m_activeEditor = nullptr;
|
||||
studio::BaseEditor *m_activeEditorUpdatePending = nullptr;
|
||||
NewMenu m_newMenu;
|
||||
AboutPopup m_aboutPopup;
|
||||
ox::Array<studio::Popup*, 2> const m_popups = {
|
||||
&m_newMenu,
|
||||
&m_aboutPopup
|
||||
};
|
||||
bool m_showProjectExplorer = true;
|
||||
|
||||
public:
|
||||
explicit StudioUI(turbine::Context &ctx, ox::StringView projectDataDir) noexcept;
|
||||
|
||||
void update() noexcept;
|
||||
|
||||
void handleKeyEvent(turbine::Key, bool down) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr studio::Project *project() noexcept {
|
||||
return m_project.get();
|
||||
}
|
||||
|
||||
protected:
|
||||
void draw() noexcept;
|
||||
|
||||
private:
|
||||
void drawMenu() noexcept;
|
||||
|
||||
void drawTabBar() noexcept;
|
||||
|
||||
void drawTabs() noexcept;
|
||||
|
||||
void loadEditorMaker(studio::EditorMaker const&editorMaker) noexcept;
|
||||
|
||||
void loadModule(studio::Module const*mod) noexcept;
|
||||
|
||||
void loadModules() noexcept;
|
||||
|
||||
void toggleProjectExplorer() noexcept;
|
||||
|
||||
void redo() noexcept;
|
||||
|
||||
void undo() noexcept;
|
||||
|
||||
void save() noexcept;
|
||||
|
||||
ox::Error openProject(ox::CRStringView path) noexcept;
|
||||
|
||||
ox::Error openFile(ox::CRStringView path) noexcept;
|
||||
|
||||
ox::Error openFileActiveTab(ox::CRStringView path, bool makeActiveTab) noexcept;
|
||||
|
||||
ox::Error closeFile(ox::CRStringView path) noexcept;
|
||||
};
|
||||
|
||||
}
|
1
deps/nostalgia/src/olympic/studio/modlib/CMakeLists.txt
vendored
Normal file
1
deps/nostalgia/src/olympic/studio/modlib/CMakeLists.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
add_subdirectory(src)
|
95
deps/nostalgia/src/olympic/studio/modlib/include/studio/configio.hpp
vendored
Normal file
95
deps/nostalgia/src/olympic/studio/modlib/include/studio/configio.hpp
vendored
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");
|
||||
auto const path = ox::sfmt("/{}.json", name);
|
||||
ox::PassThroughFS fs(configPath(ctx));
|
||||
auto const [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");
|
||||
auto const path = ox::sfmt("/{}.json", name);
|
||||
ox::PassThroughFS fs(configPath(ctx));
|
||||
if (auto const 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 (auto const 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 {};
|
||||
}
|
||||
|
||||
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");
|
||||
auto const [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
deps/nostalgia/src/olympic/studio/modlib/include/studio/context.hpp
vendored
Normal file
18
deps/nostalgia/src/olympic/studio/modlib/include/studio/context.hpp
vendored
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;
|
||||
};
|
||||
|
||||
}
|
139
deps/nostalgia/src/olympic/studio/modlib/include/studio/editor.hpp
vendored
Normal file
139
deps/nostalgia/src/olympic/studio/modlib/include/studio/editor.hpp
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 itemPath() 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 noexcept;
|
||||
|
||||
void setCopyEnabled(bool);
|
||||
|
||||
[[nodiscard]]
|
||||
bool copyEnabled() const noexcept;
|
||||
|
||||
void setPasteEnabled(bool);
|
||||
|
||||
[[nodiscard]]
|
||||
bool pasteEnabled() const noexcept;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
ox::String m_itemPath;
|
||||
ox::String m_itemName;
|
||||
|
||||
public:
|
||||
Editor(ox::StringView itemPath) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::CStringView itemPath() const noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::CStringView itemDisplayName() const noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
UndoStack *undoStack() noexcept final;
|
||||
|
||||
private:
|
||||
ox::Error markUnsavedChanges(UndoCommand const*) noexcept;
|
||||
};
|
||||
|
||||
|
||||
}
|
27
deps/nostalgia/src/olympic/studio/modlib/include/studio/filedialog.hpp
vendored
Normal file
27
deps/nostalgia/src/olympic/studio/modlib/include/studio/filedialog.hpp
vendored
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
deps/nostalgia/src/olympic/studio/modlib/include/studio/imguiuitl.hpp
vendored
Normal file
13
deps/nostalgia/src/olympic/studio/modlib/include/studio/imguiuitl.hpp
vendored
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
deps/nostalgia/src/olympic/studio/modlib/include/studio/itemmaker.hpp
vendored
Normal file
72
deps/nostalgia/src/olympic/studio/modlib/include/studio/itemmaker.hpp
vendored
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:
|
||||
ox::String const name;
|
||||
ox::String const parentDir;
|
||||
ox::String const 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:
|
||||
T const m_item;
|
||||
ox::ClawFormat const m_fmt;
|
||||
public:
|
||||
constexpr ItemMakerT(
|
||||
ox::StringView pDisplayName,
|
||||
ox::StringView pParentDir,
|
||||
ox::StringView fileExt,
|
||||
ox::ClawFormat pFmt = ox::ClawFormat::Metal) noexcept:
|
||||
ItemMaker(pDisplayName, pParentDir, fileExt),
|
||||
m_fmt(pFmt) {
|
||||
}
|
||||
constexpr ItemMakerT(
|
||||
ox::StringView pDisplayName,
|
||||
ox::StringView pParentDir,
|
||||
ox::StringView fileExt,
|
||||
T pItem,
|
||||
ox::ClawFormat pFmt) noexcept:
|
||||
ItemMaker(pDisplayName, pParentDir, fileExt),
|
||||
m_item(pItem),
|
||||
m_fmt(pFmt) {
|
||||
}
|
||||
constexpr ItemMakerT(
|
||||
ox::StringView pDisplayName,
|
||||
ox::StringView pParentDir,
|
||||
ox::StringView fileExt,
|
||||
T &&pItem,
|
||||
ox::ClawFormat pFmt) noexcept:
|
||||
ItemMaker(pDisplayName, pParentDir, fileExt),
|
||||
m_item(std::move(pItem)),
|
||||
m_fmt(pFmt) {
|
||||
}
|
||||
ox::Error write(turbine::Context &ctx, ox::CRStringView pName) const noexcept override {
|
||||
auto const 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, m_item, m_fmt);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
47
deps/nostalgia/src/olympic/studio/modlib/include/studio/module.hpp
vendored
Normal file
47
deps/nostalgia/src/olympic/studio/modlib/include/studio/module.hpp
vendored
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
deps/nostalgia/src/olympic/studio/modlib/include/studio/popup.hpp
vendored
Normal file
55
deps/nostalgia/src/olympic/studio/modlib/include/studio/popup.hpp
vendored
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(ox::String const&)> 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 ox::String const&title() const noexcept {
|
||||
return m_title;
|
||||
}
|
||||
|
||||
void drawWindow(turbine::Context &ctx, bool *open, std::function<void()> const&drawContents);
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
169
deps/nostalgia/src/olympic/studio/modlib/include/studio/project.hpp
vendored
Normal file
169
deps/nostalgia/src/olympic/studio/modlib/include/studio/project.hpp
vendored
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 {
|
||||
auto const 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]]
|
||||
ox::Vector<ox::String> const&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
|
||||
auto const 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
|
||||
auto const 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
deps/nostalgia/src/olympic/studio/modlib/include/studio/studio.hpp
vendored
Normal file
17
deps/nostalgia/src/olympic/studio/modlib/include/studio/studio.hpp
vendored
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
deps/nostalgia/src/olympic/studio/modlib/include/studio/task.hpp
vendored
Normal file
33
deps/nostalgia/src/olympic/studio/modlib/include/studio/task.hpp
vendored
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
deps/nostalgia/src/olympic/studio/modlib/include/studio/undostack.hpp
vendored
Normal file
51
deps/nostalgia/src/olympic/studio/modlib/include/studio/undostack.hpp
vendored
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(UndoCommand const*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(studio::UndoCommand const*)> redoTriggered;
|
||||
ox::Signal<ox::Error(studio::UndoCommand const*)> undoTriggered;
|
||||
ox::Signal<ox::Error(studio::UndoCommand const*)> changeTriggered;
|
||||
};
|
||||
|
||||
}
|
19
deps/nostalgia/src/olympic/studio/modlib/include/studio/widget.hpp
vendored
Normal file
19
deps/nostalgia/src/olympic/studio/modlib/include/studio/widget.hpp
vendored
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;
|
||||
};
|
||||
|
||||
}
|
45
deps/nostalgia/src/olympic/studio/modlib/src/CMakeLists.txt
vendored
Normal file
45
deps/nostalgia/src/olympic/studio/modlib/src/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
add_library(
|
||||
Studio
|
||||
configio.cpp
|
||||
editor.cpp
|
||||
imguiutil.cpp
|
||||
module.cpp
|
||||
popup.cpp
|
||||
project.cpp
|
||||
task.cpp
|
||||
undostack.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
deps/nostalgia/src/olympic/studio/modlib/src/configio.cpp
vendored
Normal file
31
deps/nostalgia/src/olympic/studio/modlib/src/configio.cpp
vendored
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(keel::Context const&ctx) noexcept {
|
||||
auto const homeDir = std::getenv(ox::defines::OS == ox::OS::Windows ? "USERPROFILE" : "HOME");
|
||||
return ox::sfmt(ConfigDir, homeDir, ctx.appName);
|
||||
}
|
||||
|
||||
}
|
139
deps/nostalgia/src/olympic/studio/modlib/src/editor.cpp
vendored
Normal file
139
deps/nostalgia/src/olympic/studio/modlib/src/editor.cpp
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 itemPath();
|
||||
}
|
||||
|
||||
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(itemPath());
|
||||
}
|
||||
|
||||
void BaseEditor::save() noexcept {
|
||||
auto const err = saveItem();
|
||||
if (!err) {
|
||||
setUnsavedChanges(false);
|
||||
} else {
|
||||
if constexpr(ox::defines::Debug) {
|
||||
oxErrorf("Could not save file {}: {} ({}:{})", itemPath(), toStr(err), err.file, err.line);
|
||||
} else {
|
||||
oxErrorf("Could not save file {}: {}", itemPath(), 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 noexcept {
|
||||
return m_cutEnabled;
|
||||
}
|
||||
|
||||
void BaseEditor::setCopyEnabled(bool v) {
|
||||
m_copyEnabled = v;
|
||||
copyEnabledChanged.emit(v);
|
||||
}
|
||||
|
||||
bool BaseEditor::copyEnabled() const noexcept {
|
||||
return m_copyEnabled;
|
||||
}
|
||||
|
||||
void BaseEditor::setPasteEnabled(bool v) {
|
||||
m_pasteEnabled = v;
|
||||
pasteEnabledChanged.emit(v);
|
||||
}
|
||||
|
||||
bool BaseEditor::pasteEnabled() const noexcept {
|
||||
return m_pasteEnabled;
|
||||
}
|
||||
|
||||
ox::Error BaseEditor::saveItem() noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
UndoStack *BaseEditor::undoStack() noexcept {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BaseEditor::setRequiresConstantRefresh(bool value) noexcept {
|
||||
m_requiresConstantRefresh = value;
|
||||
}
|
||||
|
||||
|
||||
Editor::Editor(ox::StringView itemPath) noexcept:
|
||||
m_itemPath(itemPath),
|
||||
m_itemName(m_itemPath.substr(std::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset() + 1)) {
|
||||
m_undoStack.changeTriggered.connect(this, &Editor::markUnsavedChanges);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::CStringView Editor::itemPath() const noexcept {
|
||||
return m_itemPath;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::CStringView Editor::itemDisplayName() const noexcept {
|
||||
return m_itemName;
|
||||
}
|
||||
|
||||
UndoStack *Editor::undoStack() noexcept {
|
||||
return &m_undoStack;
|
||||
}
|
||||
|
||||
ox::Error Editor::markUnsavedChanges(UndoCommand const*) noexcept {
|
||||
setUnsavedChanges(true);
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
57
deps/nostalgia/src/olympic/studio/modlib/src/filedialog_nfd.cpp
vendored
Normal file
57
deps/nostalgia/src/olympic/studio/modlib/src/filedialog_nfd.cpp
vendored
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) {
|
||||
auto const 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; auto const&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
deps/nostalgia/src/olympic/studio/modlib/src/imguiutil.cpp
vendored
Normal file
19
deps/nostalgia/src/olympic/studio/modlib/src/imguiutil.cpp
vendored
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 {
|
||||
auto const sz = turbine::getScreenSize(ctx);
|
||||
auto const screenW = static_cast<float>(sz.width);
|
||||
auto const screenH = static_cast<float>(sz.height);
|
||||
auto const mod = ImGui::GetWindowDpiScale() * 2;
|
||||
ImGui::SetNextWindowPos(ImVec2(screenW / mod, screenH / mod), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
}
|
||||
|
||||
}
|
17
deps/nostalgia/src/olympic/studio/modlib/src/module.cpp
vendored
Normal file
17
deps/nostalgia/src/olympic/studio/modlib/src/module.cpp
vendored
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
deps/nostalgia/src/olympic/studio/modlib/src/popup.cpp
vendored
Normal file
20
deps/nostalgia/src/olympic/studio/modlib/src/popup.cpp
vendored
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
deps/nostalgia/src/olympic/studio/modlib/src/project.cpp
vendored
Normal file
136
deps/nostalgia/src/olympic/studio/modlib/src/project.cpp
vendored
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 (auto const 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;
|
||||
}
|
||||
|
||||
ox::Vector<ox::String> const&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 (auto const&file : files) {
|
||||
if (!beginsWith(file, ox::sfmt("/.{}/", m_projectDataDir))) {
|
||||
indexFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Project::indexFile(ox::CRStringView path) noexcept {
|
||||
auto const [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);
|
||||
auto const [uuid, err] = m_ctx.pathToUuid.at(path);
|
||||
if (!err) {
|
||||
oxReturnError(keel::writeUuidHeader(writer, *uuid));
|
||||
}
|
||||
oxReturnError(writer.write(buff.data(), buff.size()));
|
||||
auto const 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 (auto const&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
deps/nostalgia/src/olympic/studio/modlib/src/task.cpp
vendored
Normal file
25
deps/nostalgia/src/olympic/studio/modlib/src/task.cpp
vendored
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) {
|
||||
auto const 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
deps/nostalgia/src/olympic/studio/modlib/src/undostack.cpp
vendored
Normal file
44
deps/nostalgia/src/olympic/studio/modlib/src/undostack.cpp
vendored
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(UndoCommand const*) noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
void UndoStack::push(ox::UPtr<UndoCommand> &&cmd) noexcept {
|
||||
for (auto const 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1
deps/nostalgia/src/olympic/turbine/CMakeLists.txt
vendored
Normal file
1
deps/nostalgia/src/olympic/turbine/CMakeLists.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
add_subdirectory(src)
|
50
deps/nostalgia/src/olympic/turbine/include/turbine/clipboard.hpp
vendored
Normal file
50
deps/nostalgia/src/olympic/turbine/include/turbine/clipboard.hpp
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/claw/claw.hpp>
|
||||
#include <ox/std/memory.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
class BaseClipboardObject {
|
||||
public:
|
||||
virtual ~BaseClipboardObject() noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual ox::String typeId() const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto typeMatch(ox::StringView name, int version) const noexcept {
|
||||
return typeId() == ox::buildTypeId(name, version);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class ClipboardObject: public BaseClipboardObject {
|
||||
[[nodiscard]]
|
||||
ox::String typeId() const noexcept final {
|
||||
return ox::buildTypeId(T::TypeName, T::TypeVersion);
|
||||
}
|
||||
};
|
||||
|
||||
ox::String getClipboardText(Context &ctx) noexcept;
|
||||
|
||||
void setClipboardText(Context &ctx, ox::CRStringView text) noexcept;
|
||||
|
||||
void setClipboardObject(Context &ctx, ox::UPtr<BaseClipboardObject> &&obj) noexcept;
|
||||
|
||||
ox::Result<BaseClipboardObject*> getClipboardData(Context &ctx, ox::StringView typeName, int typeVersion) noexcept;
|
||||
|
||||
template<typename T>
|
||||
ox::Result<T*> getClipboardObject(Context &ctx) noexcept {
|
||||
oxRequire(p, getClipboardData(ctx, T::TypeName, T::TypeVersion));
|
||||
return dynamic_cast<T*>(p);
|
||||
}
|
||||
|
||||
}
|
56
deps/nostalgia/src/olympic/turbine/include/turbine/context.hpp
vendored
Normal file
56
deps/nostalgia/src/olympic/turbine/include/turbine/context.hpp
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/model/desctypes.hpp>
|
||||
#include <ox/std/buffer.hpp>
|
||||
#include <ox/std/size.hpp>
|
||||
|
||||
#include <keel/context.hpp>
|
||||
|
||||
#include "input.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
class Context;
|
||||
|
||||
struct ContextDeleter {
|
||||
void operator()(Context *p) noexcept;
|
||||
};
|
||||
|
||||
using ContextUPtr = ox::UPtr<Context, ContextDeleter>;
|
||||
|
||||
void shutdown(Context &ctx) noexcept;
|
||||
|
||||
keel::Context const&keelCtx(Context const&ctx) noexcept;
|
||||
|
||||
keel::Context &keelCtx(Context &ctx) noexcept;
|
||||
|
||||
inline ox::FileSystem const*rom(Context const&ctx) noexcept {
|
||||
return keelCtx(ctx).rom.get();
|
||||
}
|
||||
|
||||
inline ox::FileSystem *rom(Context &ctx) noexcept {
|
||||
return keelCtx(ctx).rom.get();
|
||||
}
|
||||
|
||||
void setApplicationData(Context &ctx, void *applicationData) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
void *applicationDataRaw(Context &ctx) noexcept;
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]]
|
||||
T *applicationData(Context &ctx) noexcept {
|
||||
return static_cast<T*>(applicationDataRaw(ctx));
|
||||
}
|
||||
|
||||
void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept;
|
||||
|
||||
KeyEventHandler keyEventHandler(Context &ctx) noexcept;
|
||||
|
||||
}
|
||||
|
17
deps/nostalgia/src/olympic/turbine/include/turbine/event.hpp
vendored
Normal file
17
deps/nostalgia/src/olympic/turbine/include/turbine/event.hpp
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace turbine {
|
||||
|
||||
class Context;
|
||||
|
||||
using UpdateHandler = int(*)(Context&);
|
||||
|
||||
// Sets event handler that sleeps for the time given in the return value. The
|
||||
// sleep time is a minimum of ~16 milliseconds.
|
||||
void setUpdateHandler(Context &ctx, UpdateHandler) noexcept;
|
||||
|
||||
}
|
48
deps/nostalgia/src/olympic/turbine/include/turbine/gfx.hpp
vendored
Normal file
48
deps/nostalgia/src/olympic/turbine/include/turbine/gfx.hpp
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/array.hpp>
|
||||
#include <ox/std/point.hpp>
|
||||
#include <ox/std/size.hpp>
|
||||
#include <ox/std/types.hpp>
|
||||
#include <ox/model/def.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
namespace gl {
|
||||
class Drawer {
|
||||
public:
|
||||
virtual ~Drawer() = default;
|
||||
virtual void draw(Context&) noexcept = 0;
|
||||
};
|
||||
void addDrawer(Context &ctx, Drawer *cd) noexcept;
|
||||
void removeDrawer(Context &ctx, Drawer *cd) noexcept;
|
||||
}
|
||||
|
||||
ox::Error initGfx(Context &ctx) noexcept;
|
||||
|
||||
void setWindowTitle(Context &ctx, ox::CRStringView title) noexcept;
|
||||
|
||||
void focusWindow(Context &ctx) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
int getScreenWidth(Context &ctx) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
int getScreenHeight(Context &ctx) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Size getScreenSize(Context &ctx) noexcept;
|
||||
|
||||
ox::Bounds getWindowBounds(Context &ctx) noexcept;
|
||||
|
||||
ox::Error setWindowBounds(Context &ctx, ox::Bounds const&bnds) noexcept;
|
||||
|
||||
void setConstantRefresh(Context &ctx, bool r) noexcept;
|
||||
|
||||
}
|
79
deps/nostalgia/src/olympic/turbine/include/turbine/input.hpp
vendored
Normal file
79
deps/nostalgia/src/olympic/turbine/include/turbine/input.hpp
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/defines.hpp>
|
||||
|
||||
namespace turbine {
|
||||
|
||||
enum Key {
|
||||
// GBA implementation currently relies on GamePad entry order
|
||||
GamePad_A = 0,
|
||||
GamePad_B,
|
||||
GamePad_Select,
|
||||
GamePad_Start,
|
||||
GamePad_Right,
|
||||
GamePad_Left,
|
||||
GamePad_Up,
|
||||
GamePad_Down,
|
||||
GamePad_R,
|
||||
GamePad_L,
|
||||
|
||||
Num_0,
|
||||
Num_1,
|
||||
Num_2,
|
||||
Num_3,
|
||||
Num_4,
|
||||
Num_5,
|
||||
Num_6,
|
||||
Num_7,
|
||||
Num_8,
|
||||
Num_9,
|
||||
|
||||
Alpha_A,
|
||||
Alpha_B,
|
||||
Alpha_C,
|
||||
Alpha_D,
|
||||
Alpha_E,
|
||||
Alpha_F,
|
||||
Alpha_G,
|
||||
Alpha_H,
|
||||
Alpha_I,
|
||||
Alpha_J,
|
||||
Alpha_K,
|
||||
Alpha_L,
|
||||
Alpha_M,
|
||||
Alpha_N,
|
||||
Alpha_O,
|
||||
Alpha_P,
|
||||
Alpha_Q,
|
||||
Alpha_R,
|
||||
Alpha_S,
|
||||
Alpha_T,
|
||||
Alpha_U,
|
||||
Alpha_V,
|
||||
Alpha_W,
|
||||
Alpha_X,
|
||||
Alpha_Y,
|
||||
Alpha_Z,
|
||||
|
||||
Mod_Alt,
|
||||
Mod_Ctrl,
|
||||
Mod_Super,
|
||||
Mod_Shift,
|
||||
|
||||
Escape,
|
||||
|
||||
End
|
||||
};
|
||||
|
||||
class Context;
|
||||
|
||||
[[nodiscard]]
|
||||
bool buttonDown(Context const&ctx, Key) noexcept;
|
||||
|
||||
using KeyEventHandler = void(*)(Context&, Key, bool);
|
||||
|
||||
}
|
28
deps/nostalgia/src/olympic/turbine/include/turbine/turbine.hpp
vendored
Normal file
28
deps/nostalgia/src/olympic/turbine/include/turbine/turbine.hpp
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/memory.hpp>
|
||||
#include <ox/fs/fs.hpp>
|
||||
|
||||
#include "clipboard.hpp"
|
||||
#include "event.hpp"
|
||||
#include "gfx.hpp"
|
||||
#include "input.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
ox::Result<ContextUPtr> init(ox::UPtr<ox::FileSystem> &&fs, ox::CRStringView appName) noexcept;
|
||||
|
||||
ox::Error run(Context &ctx) noexcept;
|
||||
|
||||
// Returns the number of milliseconds that have passed since the start of the
|
||||
// program.
|
||||
[[nodiscard]]
|
||||
uint64_t ticksMs(Context const&ctx) noexcept;
|
||||
|
||||
void requestShutdown(Context &ctx) noexcept;
|
||||
|
||||
}
|
40
deps/nostalgia/src/olympic/turbine/src/CMakeLists.txt
vendored
Normal file
40
deps/nostalgia/src/olympic/turbine/src/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
add_library(Turbine)
|
||||
|
||||
set(TURBINE_BACKEND_GBA ${TURBINE_BUILD_TYPE} STREQUAL "GBA")
|
||||
set(TURBINE_BACKEND_GLFW NOT ${TURBINE_BACKEND_GBA})
|
||||
|
||||
add_subdirectory(gba)
|
||||
if(${TURBINE_BACKEND_GLFW})
|
||||
add_subdirectory(glfw)
|
||||
endif()
|
||||
|
||||
target_include_directories(
|
||||
Turbine PUBLIC
|
||||
../include
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
Turbine PUBLIC
|
||||
Keel
|
||||
)
|
||||
|
||||
target_compile_definitions(
|
||||
Turbine PRIVATE
|
||||
TURBINE_BACKEND_GBA=$<IF:$<BOOL:${TURBINE_BACKEND_GBA}>,1,0>
|
||||
TURBINE_BACKEND_GLFW=$<IF:$<BOOL:${TURBINE_BACKEND_GLFW}>,1,0>
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY
|
||||
../include/turbine
|
||||
DESTINATION
|
||||
include/turbine
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
Turbine
|
||||
DESTINATION
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
46
deps/nostalgia/src/olympic/turbine/src/gba/CMakeLists.txt
vendored
Normal file
46
deps/nostalgia/src/olympic/turbine/src/gba/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
option(TURBINE_GBA_EVENT_LOOP_TIMER_BASED "Run event loop on time instead of vsync" OFF)
|
||||
set(TURBINE_GBA_TIMER_BITS "32" CACHE STRING "Bits for system time (16, 32, 64)")
|
||||
|
||||
add_library(Turbine-GBA OBJECT)
|
||||
target_sources(
|
||||
Turbine-GBA PRIVATE
|
||||
context.cpp
|
||||
clipboard.cpp
|
||||
event.cpp
|
||||
gfx.cpp
|
||||
irq.arm.cpp
|
||||
turbine.arm.cpp
|
||||
turbine.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(
|
||||
Turbine-GBA PRIVATE
|
||||
TURBINE_GBA_EVENT_LOOP_TIMER_BASED=$<IF:$<BOOL:${TURBINE_GBA_EVENT_LOOP_TIMER_BASED}>,true,false>
|
||||
TURBINE_GBA_TIMER_BITS=${TURBINE_GBA_TIMER_BITS}
|
||||
)
|
||||
|
||||
if(TURBINE_BUILD_TYPE STREQUAL "GBA")
|
||||
enable_language(ASM)
|
||||
set_source_files_properties(turbine.arm.cpp irq.arm.cpp PROPERTIES COMPILE_FLAGS -marm)
|
||||
target_sources(
|
||||
Turbine-GBA PRIVATE
|
||||
irq.s
|
||||
)
|
||||
target_link_libraries(Turbine PUBLIC Turbine-GBA)
|
||||
else()
|
||||
target_sources(
|
||||
Turbine-GBA PRIVATE
|
||||
irqstub.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(
|
||||
Turbine-GBA PUBLIC
|
||||
../../include
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
Turbine-GBA PUBLIC
|
||||
TeaGBA
|
||||
Keel
|
||||
)
|
18
deps/nostalgia/src/olympic/turbine/src/gba/clipboard.cpp
vendored
Normal file
18
deps/nostalgia/src/olympic/turbine/src/gba/clipboard.cpp
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
namespace turbine {
|
||||
|
||||
ox::String getClipboardText(Context&) noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
void setClipboardText(Context&, ox::CRStringView) noexcept {
|
||||
}
|
||||
|
||||
}
|
12
deps/nostalgia/src/olympic/turbine/src/gba/config.hpp
vendored
Normal file
12
deps/nostalgia/src/olympic/turbine/src/gba/config.hpp
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace turbine::config {
|
||||
|
||||
constexpr bool GbaEventLoopTimerBased = TURBINE_GBA_EVENT_LOOP_TIMER_BASED;
|
||||
constexpr int GbaTimerBits = TURBINE_GBA_TIMER_BITS;
|
||||
|
||||
}
|
29
deps/nostalgia/src/olympic/turbine/src/gba/context.cpp
vendored
Normal file
29
deps/nostalgia/src/olympic/turbine/src/gba/context.cpp
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
void ContextDeleter::operator()(Context *p) noexcept {
|
||||
ox::safeDelete(p);
|
||||
}
|
||||
|
||||
keel::Context const&keelCtx(Context const&ctx) noexcept {
|
||||
return ctx.keelCtx;
|
||||
}
|
||||
|
||||
keel::Context &keelCtx(Context &ctx) noexcept {
|
||||
return ctx.keelCtx;
|
||||
}
|
||||
|
||||
void setApplicationData(Context &ctx, void *applicationData) noexcept {
|
||||
ctx.applicationData = applicationData;
|
||||
}
|
||||
|
||||
void *applicationDataRaw(Context &ctx) noexcept {
|
||||
return ctx.applicationData;
|
||||
}
|
||||
|
||||
}
|
36
deps/nostalgia/src/olympic/turbine/src/gba/context.hpp
vendored
Normal file
36
deps/nostalgia/src/olympic/turbine/src/gba/context.hpp
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <keel/context.hpp>
|
||||
|
||||
#include <turbine/clipboard.hpp>
|
||||
#include <turbine/context.hpp>
|
||||
#include <turbine/event.hpp>
|
||||
|
||||
namespace turbine {
|
||||
|
||||
class Context {
|
||||
public:
|
||||
UpdateHandler updateHandler = [](Context&) -> int {return 0;};
|
||||
keel::Context keelCtx;
|
||||
KeyEventHandler keyEventHandler = nullptr;
|
||||
void *applicationData = nullptr;
|
||||
|
||||
// GBA impl data /////////////////////////////////////////////////////////
|
||||
bool running = true;
|
||||
|
||||
Context() noexcept = default;
|
||||
Context(Context &other) noexcept = delete;
|
||||
Context(Context const&other) noexcept = delete;
|
||||
Context(Context const&&other) noexcept = delete;
|
||||
|
||||
virtual inline ~Context() noexcept {
|
||||
shutdown(*this);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
23
deps/nostalgia/src/olympic/turbine/src/gba/event.cpp
vendored
Normal file
23
deps/nostalgia/src/olympic/turbine/src/gba/event.cpp
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <turbine/event.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
void setUpdateHandler(Context &ctx, UpdateHandler h) noexcept {
|
||||
ctx.updateHandler = h;
|
||||
}
|
||||
|
||||
void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept {
|
||||
ctx.keyEventHandler = h;
|
||||
}
|
||||
|
||||
KeyEventHandler keyEventHandler(Context &ctx) noexcept {
|
||||
return ctx.keyEventHandler;
|
||||
}
|
||||
|
||||
}
|
50
deps/nostalgia/src/olympic/turbine/src/gba/gfx.cpp
vendored
Normal file
50
deps/nostalgia/src/olympic/turbine/src/gba/gfx.cpp
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/std/size.hpp>
|
||||
#include <ox/std/stringview.hpp>
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/gfx.hpp>
|
||||
#include <teagba/irq.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
namespace turbine {
|
||||
|
||||
ox::Error initGfx(Context&) noexcept {
|
||||
REG_DISPCTL = teagba::DispCtl_Mode0
|
||||
| teagba::DispCtl_SpriteMap1D
|
||||
| teagba::DispCtl_Obj;
|
||||
// tell display to trigger vblank interrupts
|
||||
REG_DISPSTAT = REG_DISPSTAT | teagba::DispStat_irq_vblank;
|
||||
// enable vblank interrupt
|
||||
REG_IE = REG_IE | teagba::Int_vblank;
|
||||
return {};
|
||||
}
|
||||
|
||||
void setWindowTitle(Context&, ox::CRStringView) noexcept {
|
||||
}
|
||||
|
||||
int getScreenWidth(Context&) noexcept {
|
||||
return 240;
|
||||
}
|
||||
|
||||
int getScreenHeight(Context&) noexcept {
|
||||
return 160;
|
||||
}
|
||||
|
||||
ox::Size getScreenSize(Context&) noexcept {
|
||||
return {240, 160};
|
||||
}
|
||||
|
||||
ox::Bounds getWindowBounds(Context&) noexcept {
|
||||
return {0, 0, 240, 160};
|
||||
}
|
||||
|
||||
ox::Error setWindowBounds(Context&, ox::Bounds const&) noexcept {
|
||||
return OxError(1, "setWindowBounds not supported on GBA");
|
||||
}
|
||||
|
||||
}
|
36
deps/nostalgia/src/olympic/turbine/src/gba/irq.arm.cpp
vendored
Normal file
36
deps/nostalgia/src/olympic/turbine/src/gba/irq.arm.cpp
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
// NOTE: this file is compiled as ARM and not THUMB, so don't but too much in
|
||||
// here
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/gfx.hpp>
|
||||
#include <teagba/irq.hpp>
|
||||
|
||||
#include "turbine.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
volatile gba_timer_t g_timerMs = 0;
|
||||
|
||||
}
|
||||
|
||||
using namespace turbine;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void turbine_isr_vblank() noexcept {
|
||||
teagba::applySpriteUpdates();
|
||||
if constexpr(config::GbaEventLoopTimerBased) {
|
||||
// disable vblank interrupt until it is needed again
|
||||
REG_IE = REG_IE & ~teagba::Int_vblank;
|
||||
}
|
||||
}
|
||||
|
||||
void turbine_isr_timer0() noexcept {
|
||||
g_timerMs = g_timerMs + 1;
|
||||
}
|
||||
|
||||
}
|
110
deps/nostalgia/src/olympic/turbine/src/gba/irq.s
vendored
Normal file
110
deps/nostalgia/src/olympic/turbine/src/gba/irq.s
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
//
|
||||
// Copyright 2016 - 2023 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/.
|
||||
//
|
||||
|
||||
.section .iwram, "ax", %progbits
|
||||
.arm
|
||||
.align
|
||||
|
||||
.extern turbine_isr_vblank
|
||||
.extern turbine_isr_timer0
|
||||
|
||||
.equ REG_IFBIOS, 0x03007ff8
|
||||
|
||||
.equ REG_IE, 0x04000200
|
||||
.equ REG_IF, 0x04000202
|
||||
.equ REG_IME, 0x04000208
|
||||
|
||||
.equ Int_vblank, 1
|
||||
.equ Int_hblank, 2
|
||||
.equ Int_vcount, 4
|
||||
.equ Int_timer0, 8
|
||||
.equ Int_timer1, 16
|
||||
.equ Int_timer2, 32
|
||||
.equ Int_timer3, 64
|
||||
.equ Int_serial, 128 // link cable
|
||||
.equ Int_dma0, 256
|
||||
.equ Int_dma1, 512
|
||||
.equ Int_dma2, 1024
|
||||
.equ Int_dma3, 2048
|
||||
.equ Int_dma4, 4096
|
||||
.equ Int_dma5, 8192
|
||||
.equ Int_input, 16384 // gamepad
|
||||
.equ Int_cart, 32768 // cartridge removed
|
||||
|
||||
|
||||
.global turbine_isr
|
||||
.type turbine_isr, %function
|
||||
turbine_isr:
|
||||
// read IE
|
||||
ldr r0, =#REG_IE
|
||||
ldrh r1, [r0] // r1 becomes IE value
|
||||
ldrh r2, [r0, #2] // r2 becomes IF value
|
||||
and r1, r1, r2 // r1 becomes IE & IF
|
||||
// done with r2 as IF value
|
||||
|
||||
// Acknowledge IRQ in REG_IF
|
||||
strh r1, [r0, #2]
|
||||
ldr r0, =#REG_IFBIOS
|
||||
// Acknowledge IRQ in REG_IFBIOS
|
||||
ldr r2, [r0] // r2 becomes REG_IFBIOS value
|
||||
orr r2, r2, r1
|
||||
str r2, [r0]
|
||||
// done with r2 as IFBIOS value
|
||||
// done with r0 as REG_IFBIOS
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Interrupt Table Begin //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
cmp r1, #Int_timer0
|
||||
ldreq r0, =turbine_isr_timer0
|
||||
beq isr_call_handler
|
||||
|
||||
cmp r1, #Int_vblank
|
||||
ldreq r0, =turbine_isr_vblank
|
||||
beq isr_call_handler
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Interrupt Table End //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
bx lr
|
||||
|
||||
isr_call_handler:
|
||||
// clear IME to disable interrupts
|
||||
ldr r2, =#REG_IME
|
||||
mov r1, #0
|
||||
str r1, [r2]
|
||||
|
||||
// enter system mode
|
||||
mrs r1, cpsr
|
||||
bic r1, r1, #0xDF
|
||||
orr r1, r1, #0x1F
|
||||
msr cpsr, r1
|
||||
|
||||
push {lr}
|
||||
ldr lr, =isr_restore
|
||||
bx r0
|
||||
|
||||
isr_restore:
|
||||
pop {lr}
|
||||
|
||||
// re-enter irq mode
|
||||
mrs r0, cpsr
|
||||
bic r0, r0, #0xDF
|
||||
orr r0, r0, #0x92
|
||||
msr cpsr, r0
|
||||
|
||||
// set IME to re-enable interrupts
|
||||
ldr r2, =#REG_IME
|
||||
mov r0, #1
|
||||
str r0, [r2]
|
||||
|
||||
bx lr
|
||||
|
||||
// vim: ft=armv4
|
8
deps/nostalgia/src/olympic/turbine/src/gba/irqstub.cpp
vendored
Normal file
8
deps/nostalgia/src/olympic/turbine/src/gba/irqstub.cpp
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
// stub for building TeaGBA for PC targets, for purposes of not having to
|
||||
// switch back and forth between builds when editing GBA files
|
||||
|
||||
extern "C" void turbine_isr() {}
|
40
deps/nostalgia/src/olympic/turbine/src/gba/turbine.arm.cpp
vendored
Normal file
40
deps/nostalgia/src/olympic/turbine/src/gba/turbine.arm.cpp
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/bios.hpp>
|
||||
#include <teagba/irq.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
#include "turbine.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
extern volatile gba_timer_t g_timerMs;
|
||||
static gba_timer_t g_wakeupTime{};
|
||||
|
||||
ox::Error run(Context &ctx) noexcept {
|
||||
g_wakeupTime = 0;
|
||||
while (ctx.running) {
|
||||
if (g_wakeupTime <= g_timerMs && ctx.updateHandler) {
|
||||
auto sleepTime = ctx.updateHandler(ctx);
|
||||
if (sleepTime >= 0) {
|
||||
g_wakeupTime = g_timerMs + static_cast<unsigned>(sleepTime);
|
||||
} else {
|
||||
g_wakeupTime = ~gba_timer_t(0);
|
||||
}
|
||||
}
|
||||
if constexpr(config::GbaEventLoopTimerBased) {
|
||||
// wait for timer interrupt
|
||||
teagba_intrwait(
|
||||
0,
|
||||
teagba::Int_timer0 | teagba::Int_timer1 | teagba::Int_timer2 | teagba::Int_timer3);
|
||||
} else {
|
||||
teagba_vblankintrwait();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
86
deps/nostalgia/src/olympic/turbine/src/gba/turbine.cpp
vendored
Normal file
86
deps/nostalgia/src/olympic/turbine/src/gba/turbine.cpp
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
#include <teagba/irq.hpp>
|
||||
|
||||
#include <keel/keel.hpp>
|
||||
#include <turbine/context.hpp>
|
||||
#include <turbine/gfx.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
#include "turbine.hpp"
|
||||
|
||||
extern "C" void turbine_isr();
|
||||
|
||||
namespace turbine {
|
||||
|
||||
// Timer Consts
|
||||
constexpr int NanoSecond = 1'000'000'000;
|
||||
constexpr int MilliSecond = 1000;
|
||||
constexpr int TicksMs59ns =
|
||||
65535 - static_cast<uint16_t>(static_cast<double>(NanoSecond / MilliSecond) / 59.59);
|
||||
|
||||
extern volatile gba_timer_t g_timerMs;
|
||||
|
||||
static void initIrq() noexcept {
|
||||
REG_ISR = turbine_isr;
|
||||
REG_IME = 1; // enable interrupts
|
||||
}
|
||||
|
||||
static void initTimer() noexcept {
|
||||
// make timer0 a ~1 millisecond timer
|
||||
REG_TIMER0 = TicksMs59ns;
|
||||
REG_TIMER0CTL = 0b11000000;
|
||||
// enable interrupt for timer0
|
||||
REG_IE = REG_IE | teagba::Int_timer0;
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
static ox::Result<std::size_t> findPreloadSection() noexcept {
|
||||
// put the header in the wrong order to prevent mistaking this code for the
|
||||
// media section
|
||||
constexpr auto headerP2 = "DER_____________";
|
||||
constexpr auto headerP1 = "KEEL_PRELOAD_HEA";
|
||||
constexpr auto headerP1Len = ox_strlen(headerP2);
|
||||
constexpr auto headerP2Len = ox_strlen(headerP1);
|
||||
constexpr auto headerLen = headerP1Len + headerP2Len;
|
||||
for (auto current = MEM_ROM; current < reinterpret_cast<char*>(0x0a000000); current += headerLen) {
|
||||
if (memcmp(current, headerP1, headerP1Len) == 0 &&
|
||||
memcmp(current + headerP1Len, headerP2, headerP2Len) == 0) {
|
||||
return reinterpret_cast<std::size_t>(current + headerLen);
|
||||
}
|
||||
}
|
||||
return OxError(1);
|
||||
}
|
||||
|
||||
ox::Result<ContextUPtr> init(
|
||||
ox::UPtr<ox::FileSystem> &&fs, ox::CRStringView appName) noexcept {
|
||||
auto ctx = ox::make_unique<Context>();
|
||||
oxReturnError(keel::init(ctx->keelCtx, std::move(fs), appName));
|
||||
#ifdef OX_BARE_METAL
|
||||
oxReturnError(findPreloadSection().moveTo(ctx->keelCtx.preloadSectionOffset));
|
||||
#endif
|
||||
oxReturnError(initGfx(*ctx));
|
||||
initTimer();
|
||||
initIrq();
|
||||
return ox::UPtr<turbine::Context, ContextDeleter>(std::move(ctx));
|
||||
}
|
||||
|
||||
void shutdown(Context&) noexcept {
|
||||
}
|
||||
|
||||
uint64_t ticksMs(Context&) noexcept {
|
||||
return g_timerMs;
|
||||
}
|
||||
|
||||
bool buttonDown(Context const&, Key k) noexcept {
|
||||
return k <= Key::GamePad_L && !(REG_GAMEPAD & (1 << static_cast<int>(k)));
|
||||
}
|
||||
|
||||
void requestShutdown(Context &ctx) noexcept {
|
||||
ctx.running = false;
|
||||
}
|
||||
|
||||
}
|
15
deps/nostalgia/src/olympic/turbine/src/gba/turbine.hpp
vendored
Normal file
15
deps/nostalgia/src/olympic/turbine/src/gba/turbine.hpp
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/types.hpp>
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
using gba_timer_t = ox::Uint<config::GbaTimerBits>;
|
||||
|
||||
}
|
24
deps/nostalgia/src/olympic/turbine/src/glfw/CMakeLists.txt
vendored
Normal file
24
deps/nostalgia/src/olympic/turbine/src/glfw/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
option(TURBINE_USE_IMGUI "Include DearImGUI in build (GLFW only)" ON)
|
||||
option(TURBINE_GL_FPS_PRINT "Print FPS to stdout" OFF)
|
||||
|
||||
target_sources(
|
||||
Turbine PRIVATE
|
||||
context.cpp
|
||||
clipboard.cpp
|
||||
event.cpp
|
||||
gfx.cpp
|
||||
turbine.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(
|
||||
Turbine PRIVATE
|
||||
TURBINE_USE_IMGUI=$<IF:$<BOOL:${TURBINE_USE_IMGUI}>,1,0>
|
||||
TURBINE_GL_FPS_PRINT=$<IF:$<BOOL:${TURBINE_GL_FPS_PRINT}>,true,false>
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
Turbine PUBLIC
|
||||
glad
|
||||
glfw
|
||||
imgui
|
||||
)
|
36
deps/nostalgia/src/olympic/turbine/src/glfw/clipboard.cpp
vendored
Normal file
36
deps/nostalgia/src/olympic/turbine/src/glfw/clipboard.cpp
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <turbine/turbine.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
ox::String getClipboardText(Context &ctx) noexcept {
|
||||
return ox::String(glfwGetClipboardString(ctx.window));
|
||||
}
|
||||
|
||||
void setClipboardText(Context &ctx, ox::CRStringView text) noexcept {
|
||||
auto cstr = ox_malloca(text.bytes() + 1, char);
|
||||
ox_strncpy(cstr.get(), text.data(), text.bytes());
|
||||
glfwSetClipboardString(ctx.window, cstr.get());
|
||||
}
|
||||
|
||||
void setClipboardObject(Context &ctx, ox::UPtr<BaseClipboardObject> &&obj) noexcept {
|
||||
ctx.clipboard = std::move(obj);
|
||||
}
|
||||
|
||||
ox::Result<BaseClipboardObject*> getClipboardData(Context &ctx, ox::StringView typeName, int typeVersion) noexcept {
|
||||
if (ctx.clipboard && ctx.clipboard->typeMatch(typeName, typeVersion)) {
|
||||
return ctx.clipboard.get();
|
||||
}
|
||||
return OxError(1);
|
||||
}
|
||||
|
||||
}
|
11
deps/nostalgia/src/olympic/turbine/src/glfw/config.hpp
vendored
Normal file
11
deps/nostalgia/src/olympic/turbine/src/glfw/config.hpp
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace turbine::config {
|
||||
|
||||
constexpr bool GlFpsPrint = TURBINE_GL_FPS_PRINT;
|
||||
|
||||
}
|
31
deps/nostalgia/src/olympic/turbine/src/glfw/context.cpp
vendored
Normal file
31
deps/nostalgia/src/olympic/turbine/src/glfw/context.cpp
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
void ContextDeleter::operator()(Context *p) noexcept {
|
||||
ox::safeDelete(p);
|
||||
}
|
||||
|
||||
keel::Context const&keelCtx(Context const&ctx) noexcept {
|
||||
return ctx.keelCtx;
|
||||
}
|
||||
|
||||
keel::Context &keelCtx(Context &ctx) noexcept {
|
||||
return ctx.keelCtx;
|
||||
}
|
||||
|
||||
void setApplicationData(Context &ctx, void *applicationData) noexcept {
|
||||
ctx.applicationData = applicationData;
|
||||
}
|
||||
|
||||
void *applicationDataRaw(Context &ctx) noexcept {
|
||||
return ctx.applicationData;
|
||||
}
|
||||
|
||||
}
|
41
deps/nostalgia/src/olympic/turbine/src/glfw/context.hpp
vendored
Normal file
41
deps/nostalgia/src/olympic/turbine/src/glfw/context.hpp
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <turbine/clipboard.hpp>
|
||||
#include <turbine/context.hpp>
|
||||
#include <turbine/gfx.hpp>
|
||||
#include <turbine/event.hpp>
|
||||
|
||||
namespace turbine {
|
||||
|
||||
class Context {
|
||||
public:
|
||||
UpdateHandler updateHandler = [](Context&) -> int {return 0;};
|
||||
keel::Context keelCtx;
|
||||
KeyEventHandler keyEventHandler = nullptr;
|
||||
void *applicationData = nullptr;
|
||||
|
||||
// GLFW impl data ////////////////////////////////////////////////////////
|
||||
int uninterruptedRefreshes = 3;
|
||||
ox::UPtr<BaseClipboardObject> clipboard;
|
||||
struct GLFWwindow *window = nullptr;
|
||||
// sets screen refresh to constant instead of only on event
|
||||
bool constantRefresh = true;
|
||||
ox::Vector<gl::Drawer*, 5> drawers;
|
||||
int64_t startTime = 0;
|
||||
uint64_t wakeupTime = 0;
|
||||
uint64_t keysDown = 0;
|
||||
uint64_t prevFpsCheckTime = 0;
|
||||
uint64_t draws = 0;
|
||||
|
||||
Context() noexcept = default;
|
||||
|
||||
Context(Context const&other) noexcept = delete;
|
||||
Context(Context const&&other) noexcept = delete;
|
||||
|
||||
};
|
||||
|
||||
}
|
23
deps/nostalgia/src/olympic/turbine/src/glfw/event.cpp
vendored
Normal file
23
deps/nostalgia/src/olympic/turbine/src/glfw/event.cpp
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <turbine/event.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
void setUpdateHandler(Context &ctx, UpdateHandler h) noexcept {
|
||||
ctx.updateHandler = h;
|
||||
}
|
||||
|
||||
void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept {
|
||||
ctx.keyEventHandler = h;
|
||||
}
|
||||
|
||||
KeyEventHandler keyEventHandler(Context &ctx) noexcept {
|
||||
return ctx.keyEventHandler;
|
||||
}
|
||||
|
||||
}
|
265
deps/nostalgia/src/olympic/turbine/src/glfw/gfx.cpp
vendored
Normal file
265
deps/nostalgia/src/olympic/turbine/src/glfw/gfx.cpp
vendored
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#if TURBINE_USE_IMGUI
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#endif
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
namespace gl {
|
||||
|
||||
void addDrawer(Context &ctx, Drawer *cd) noexcept {
|
||||
ctx.drawers.emplace_back(cd);
|
||||
}
|
||||
|
||||
void removeDrawer(Context &ctx, Drawer *cd) noexcept {
|
||||
for (auto i = 0u; i < ctx.drawers.size(); ++i) {
|
||||
if (ctx.drawers[i] == cd) {
|
||||
oxIgnoreError(ctx.drawers.erase(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void handleGlfwError(int err, char const*desc) noexcept {
|
||||
oxErrf("GLFW error ({}): {}\n", err, desc);
|
||||
}
|
||||
|
||||
static auto setKeyDownStatus(Context &ctx, Key key, bool down) noexcept {
|
||||
ctx.keysDown &= ~(1llu << static_cast<int>(key));
|
||||
ctx.keysDown |= static_cast<uint64_t>(down) << static_cast<int>(key);
|
||||
}
|
||||
|
||||
static void handleKeyPress(Context &ctx, int key, bool down) noexcept {
|
||||
static constexpr auto keyMap = [] {
|
||||
ox::Array<Key, GLFW_KEY_LAST> map = {};
|
||||
for (auto i = 0u; i < 26; ++i) {
|
||||
map[GLFW_KEY_A + i] = static_cast<Key>(static_cast<unsigned>(Key::Alpha_A) + i);
|
||||
}
|
||||
for (auto i = 0u; i < 10; ++i) {
|
||||
map[GLFW_KEY_0 + i] = static_cast<Key>(static_cast<unsigned>(Key::Num_0) + i);
|
||||
}
|
||||
map[GLFW_KEY_LEFT_ALT] = Key::Mod_Alt;
|
||||
map[GLFW_KEY_RIGHT_ALT] = Key::Mod_Alt;
|
||||
map[GLFW_KEY_LEFT_CONTROL] = Key::Mod_Ctrl;
|
||||
map[GLFW_KEY_RIGHT_CONTROL] = Key::Mod_Ctrl;
|
||||
map[GLFW_KEY_LEFT_SUPER] = Key::Mod_Super;
|
||||
map[GLFW_KEY_RIGHT_SUPER] = Key::Mod_Super;
|
||||
map[GLFW_KEY_ESCAPE] = Key::Escape;
|
||||
return map;
|
||||
}();
|
||||
auto const eventHandler = keyEventHandler(ctx);
|
||||
auto const k = keyMap[static_cast<std::size_t>(key)];
|
||||
setKeyDownStatus(ctx, k, down);
|
||||
if (eventHandler) {
|
||||
eventHandler(ctx, k, down);
|
||||
}
|
||||
}
|
||||
|
||||
static void handleGlfwCursorPosEvent(GLFWwindow*, double, double) noexcept {
|
||||
}
|
||||
|
||||
static void handleGlfwMouseButtonEvent(GLFWwindow *window, int, int, int) noexcept {
|
||||
auto const ctx = static_cast<Context*>(glfwGetWindowUserPointer(window));
|
||||
ctx->uninterruptedRefreshes = 25;
|
||||
}
|
||||
|
||||
static void handleGlfwKeyEvent(GLFWwindow *window, int key, int, int action, int) noexcept {
|
||||
auto const ctx = static_cast<Context*>(glfwGetWindowUserPointer(window));
|
||||
ctx->uninterruptedRefreshes = 25;
|
||||
if (action == GLFW_PRESS) {
|
||||
handleKeyPress(*ctx, key, true);
|
||||
} else if (action == GLFW_RELEASE) {
|
||||
handleKeyPress(*ctx, key, false);
|
||||
}
|
||||
}
|
||||
|
||||
#if TURBINE_USE_IMGUI
|
||||
static void themeImgui() noexcept {
|
||||
// Dark Ruda style by Raikiri from ImThemes
|
||||
auto &style = ImGui::GetStyle();
|
||||
style.Alpha = 1.0;
|
||||
style.DisabledAlpha = 0.6000000238418579;
|
||||
style.WindowPadding = ImVec2(8.0, 8.0);
|
||||
style.WindowRounding = 0.0;
|
||||
style.WindowBorderSize = 1.0;
|
||||
style.WindowMinSize = ImVec2(32.0, 32.0);
|
||||
style.WindowTitleAlign = ImVec2(0.0, 0.5);
|
||||
style.WindowMenuButtonPosition = ImGuiDir_Left;
|
||||
style.ChildRounding = 0.0;
|
||||
style.ChildBorderSize = 1.0;
|
||||
style.PopupRounding = 0.0;
|
||||
style.PopupBorderSize = 1.0;
|
||||
style.FramePadding = ImVec2(4.0, 3.0);
|
||||
// custom value
|
||||
style.FrameRounding = 3.0;
|
||||
style.FrameBorderSize = 0.0;
|
||||
style.ItemSpacing = ImVec2(8.0, 4.0);
|
||||
style.ItemInnerSpacing = ImVec2(4.0, 4.0);
|
||||
style.CellPadding = ImVec2(4.0, 2.0);
|
||||
style.IndentSpacing = 21.0;
|
||||
style.ColumnsMinSpacing = 6.0;
|
||||
style.ScrollbarSize = 14.0;
|
||||
style.ScrollbarRounding = 9.0;
|
||||
style.GrabMinSize = 10.0;
|
||||
style.GrabRounding = 4.0;
|
||||
style.TabRounding = 4.0;
|
||||
style.TabBorderSize = 0.0;
|
||||
style.TabMinWidthForCloseButton = 0.0;
|
||||
style.ColorButtonPosition = ImGuiDir_Right;
|
||||
style.ButtonTextAlign = ImVec2(0.5, 0.5);
|
||||
style.SelectableTextAlign = ImVec2(0.0, 0.0);
|
||||
// colors
|
||||
constexpr auto imVec4 = [](double r, double g, double b, double a) {
|
||||
return ImVec4(
|
||||
static_cast<float>(r),
|
||||
static_cast<float>(g),
|
||||
static_cast<float>(b),
|
||||
static_cast<float>(a));
|
||||
};
|
||||
style.Colors[ImGuiCol_Text] = imVec4(0.9490196108818054, 0.95686274766922, 0.9764705896377563, 1.0);
|
||||
style.Colors[ImGuiCol_TextDisabled] = imVec4(0.3568627536296844, 0.4196078479290009, 0.4666666686534882, 1.0);
|
||||
style.Colors[ImGuiCol_WindowBg] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
|
||||
style.Colors[ImGuiCol_ChildBg] = imVec4(0.1490196138620377, 0.1764705926179886, 0.2196078449487686, 1.0);
|
||||
style.Colors[ImGuiCol_PopupBg] = imVec4(0.0784313753247261, 0.0784313753247261, 0.0784313753247261, 0.9399999976158142);
|
||||
style.Colors[ImGuiCol_Border] = imVec4(0.0784313753247261, 0.09803921729326248, 0.1176470592617989, 1.0);
|
||||
style.Colors[ImGuiCol_BorderShadow] = imVec4(0.0, 0.0, 0.0, 0.0);
|
||||
style.Colors[ImGuiCol_FrameBg] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
|
||||
style.Colors[ImGuiCol_FrameBgHovered] = imVec4(0.1176470592617989, 0.2000000029802322, 0.2784313857555389, 1.0);
|
||||
style.Colors[ImGuiCol_FrameBgActive] = imVec4(0.08627451211214066, 0.1176470592617989, 0.1372549086809158, 1.0);
|
||||
style.Colors[ImGuiCol_TitleBg] = imVec4(0.08627451211214066, 0.1176470592617989, 0.1372549086809158, 0.6499999761581421);
|
||||
style.Colors[ImGuiCol_TitleBgActive] = imVec4(0.0784313753247261, 0.09803921729326248, 0.1176470592617989, 1.0);
|
||||
style.Colors[ImGuiCol_TitleBgCollapsed] = imVec4(0.0, 0.0, 0.0, 0.5099999904632568);
|
||||
style.Colors[ImGuiCol_MenuBarBg] = imVec4(0.1490196138620377, 0.1764705926179886, 0.2196078449487686, 1.0);
|
||||
style.Colors[ImGuiCol_ScrollbarBg] = imVec4(0.01960784383118153, 0.01960784383118153, 0.01960784383118153, 0.3899999856948853);
|
||||
style.Colors[ImGuiCol_ScrollbarGrab] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
|
||||
style.Colors[ImGuiCol_ScrollbarGrabHovered] = imVec4(0.1764705926179886, 0.2196078449487686, 0.2470588237047195, 1.0);
|
||||
style.Colors[ImGuiCol_ScrollbarGrabActive] = imVec4(0.08627451211214066, 0.2078431397676468, 0.3098039329051971, 1.0);
|
||||
style.Colors[ImGuiCol_CheckMark] = imVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0);
|
||||
style.Colors[ImGuiCol_SliderGrab] = imVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0);
|
||||
style.Colors[ImGuiCol_SliderGrabActive] = imVec4(0.3686274588108063, 0.6078431606292725, 1.0, 1.0);
|
||||
style.Colors[ImGuiCol_Button] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
|
||||
style.Colors[ImGuiCol_ButtonHovered] = imVec4(0.2784313857555389, 0.5568627715110779, 1.0, 1.0);
|
||||
style.Colors[ImGuiCol_ButtonActive] = imVec4(0.05882352963089943, 0.529411792755127, 0.9764705896377563, 1.0);
|
||||
// custom value
|
||||
style.Colors[ImGuiCol_Header] = imVec4(0.4000000029802322, 0.4470588237047195, 0.4862745225429535, 0.550000011920929);
|
||||
style.Colors[ImGuiCol_HeaderHovered] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.800000011920929);
|
||||
style.Colors[ImGuiCol_HeaderActive] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 1.0);
|
||||
style.Colors[ImGuiCol_Separator] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
|
||||
style.Colors[ImGuiCol_SeparatorHovered] = imVec4(0.09803921729326248, 0.4000000059604645, 0.7490196228027344, 0.7799999713897705);
|
||||
style.Colors[ImGuiCol_SeparatorActive] = imVec4(0.09803921729326248, 0.4000000059604645, 0.7490196228027344, 1.0);
|
||||
style.Colors[ImGuiCol_ResizeGrip] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.25);
|
||||
style.Colors[ImGuiCol_ResizeGripHovered] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.6700000166893005);
|
||||
style.Colors[ImGuiCol_ResizeGripActive] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.949999988079071);
|
||||
style.Colors[ImGuiCol_Tab] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
|
||||
style.Colors[ImGuiCol_TabHovered] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.800000011920929);
|
||||
style.Colors[ImGuiCol_TabActive] = imVec4(0.2000000029802322, 0.2470588237047195, 0.2862745225429535, 1.0);
|
||||
style.Colors[ImGuiCol_TabUnfocused] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
|
||||
style.Colors[ImGuiCol_TabUnfocusedActive] = imVec4(0.1098039224743843, 0.1490196138620377, 0.168627455830574, 1.0);
|
||||
style.Colors[ImGuiCol_PlotLines] = imVec4(0.6078431606292725, 0.6078431606292725, 0.6078431606292725, 1.0);
|
||||
style.Colors[ImGuiCol_PlotLinesHovered] = imVec4(1.0, 0.4274509847164154, 0.3490196168422699, 1.0);
|
||||
style.Colors[ImGuiCol_PlotHistogram] = imVec4(0.8980392217636108, 0.6980392336845398, 0.0, 1.0);
|
||||
style.Colors[ImGuiCol_PlotHistogramHovered] = imVec4(1.0, 0.6000000238418579, 0.0, 1.0);
|
||||
style.Colors[ImGuiCol_TableHeaderBg] = imVec4(0.1882352977991104, 0.1882352977991104, 0.2000000029802322, 1.0);
|
||||
style.Colors[ImGuiCol_TableBorderStrong] = imVec4(0.3098039329051971, 0.3098039329051971, 0.3490196168422699, 1.0);
|
||||
style.Colors[ImGuiCol_TableBorderLight] = imVec4(0.2274509817361832, 0.2274509817361832, 0.2470588237047195, 1.0);
|
||||
style.Colors[ImGuiCol_TableRowBg] = imVec4(0.0, 0.0, 0.0, 0.0);
|
||||
style.Colors[ImGuiCol_TableRowBgAlt] = imVec4(1.0, 1.0, 1.0, 0.05999999865889549);
|
||||
style.Colors[ImGuiCol_TextSelectedBg] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 0.3499999940395355);
|
||||
style.Colors[ImGuiCol_DragDropTarget] = imVec4(1.0, 1.0, 0.0, 0.8999999761581421);
|
||||
style.Colors[ImGuiCol_NavHighlight] = imVec4(0.2588235437870026, 0.5882353186607361, 0.9764705896377563, 1.0);
|
||||
style.Colors[ImGuiCol_NavWindowingHighlight] = imVec4(1.0, 1.0, 1.0, 0.699999988079071);
|
||||
style.Colors[ImGuiCol_NavWindowingDimBg] = imVec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 0.2000000029802322);
|
||||
style.Colors[ImGuiCol_ModalWindowDimBg] = imVec4(0.800000011920929, 0.800000011920929, 0.800000011920929, 0.3499999940395355);
|
||||
}
|
||||
#endif
|
||||
|
||||
ox::Error initGfx(Context &ctx) noexcept {
|
||||
glfwSetErrorCallback(handleGlfwError);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
|
||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
|
||||
constexpr auto Scale = 5;
|
||||
ctx.window = glfwCreateWindow(240 * Scale, 160 * Scale, ctx.keelCtx.appName.c_str(), nullptr, nullptr);
|
||||
//ctx.window = glfwCreateWindow(876, 743, ctx.keelCtx.appName.c_str(), nullptr, nullptr);
|
||||
if (ctx.window == nullptr) {
|
||||
return OxError(1, "Could not open GLFW window");
|
||||
}
|
||||
glfwSetCursorPosCallback(ctx.window, handleGlfwCursorPosEvent);
|
||||
glfwSetMouseButtonCallback(ctx.window, handleGlfwMouseButtonEvent);
|
||||
glfwSetKeyCallback(ctx.window, handleGlfwKeyEvent);
|
||||
glfwSetWindowUserPointer(ctx.window, &ctx);
|
||||
glfwMakeContextCurrent(ctx.window);
|
||||
if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(glfwGetProcAddress))) {
|
||||
return OxError(2, "Could not init Glad");
|
||||
}
|
||||
#if TURBINE_USE_IMGUI
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
auto &io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
//io.MouseDrawCursor = true;
|
||||
ImGui_ImplGlfw_InitForOpenGL(ctx.window, true);
|
||||
ImGui_ImplOpenGL3_Init();
|
||||
themeImgui();
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
void setWindowTitle(Context &ctx, ox::CRStringView title) noexcept {
|
||||
auto cstr = ox_malloca(title.bytes() + 1, char);
|
||||
ox_strncpy(cstr.get(), title.data(), title.bytes());
|
||||
glfwSetWindowTitle(ctx.window, cstr.get());
|
||||
}
|
||||
|
||||
void focusWindow(Context &ctx) noexcept {
|
||||
glfwFocusWindow(ctx.window);
|
||||
}
|
||||
|
||||
int getScreenWidth(Context &ctx) noexcept {
|
||||
int w = 0, h = 0;
|
||||
glfwGetFramebufferSize(ctx.window, &w, &h);
|
||||
return w;
|
||||
}
|
||||
|
||||
int getScreenHeight(Context &ctx) noexcept {
|
||||
int w = 0, h = 0;
|
||||
glfwGetFramebufferSize(ctx.window, &w, &h);
|
||||
return h;
|
||||
}
|
||||
|
||||
ox::Size getScreenSize(Context &ctx) noexcept {
|
||||
int w = 0, h = 0;
|
||||
glfwGetFramebufferSize(ctx.window, &w, &h);
|
||||
return {w, h};
|
||||
}
|
||||
|
||||
ox::Bounds getWindowBounds(Context &ctx) noexcept {
|
||||
ox::Bounds bnds;
|
||||
glfwGetWindowPos(ctx.window, &bnds.x, &bnds.y);
|
||||
glfwGetWindowSize(ctx.window, &bnds.width, &bnds.height);
|
||||
return bnds;
|
||||
}
|
||||
|
||||
ox::Error setWindowBounds(Context &ctx, ox::Bounds const&bnds) noexcept {
|
||||
glfwSetWindowPos(ctx.window, bnds.x, bnds.y);
|
||||
glfwSetWindowSize(ctx.window, bnds.width, bnds.height);
|
||||
return {};
|
||||
}
|
||||
|
||||
void setConstantRefresh(Context &ctx, bool r) noexcept {
|
||||
ctx.constantRefresh = r;
|
||||
}
|
||||
|
||||
}
|
123
deps/nostalgia/src/olympic/turbine/src/glfw/turbine.cpp
vendored
Normal file
123
deps/nostalgia/src/olympic/turbine/src/glfw/turbine.cpp
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
#if TURBINE_USE_IMGUI
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#endif
|
||||
|
||||
#include <keel/keel.hpp>
|
||||
|
||||
#include <turbine/turbine.hpp>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "context.hpp"
|
||||
|
||||
namespace turbine {
|
||||
|
||||
static void draw(Context &ctx) noexcept {
|
||||
// draw start
|
||||
#if TURBINE_USE_IMGUI
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
#endif
|
||||
for (auto d : ctx.drawers) {
|
||||
d->draw(ctx);
|
||||
}
|
||||
#if TURBINE_USE_IMGUI
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
#endif
|
||||
// draw end
|
||||
glfwSwapBuffers(ctx.window);
|
||||
}
|
||||
|
||||
static void draw(GLFWwindow *window, int, int) noexcept {
|
||||
auto &ctx = *static_cast<Context*>(glfwGetWindowUserPointer(window));
|
||||
draw(ctx);
|
||||
}
|
||||
|
||||
ox::Result<ContextUPtr> init(
|
||||
ox::UPtr<ox::FileSystem> &&fs, ox::CRStringView appName) noexcept {
|
||||
auto ctx = ox::make_unique<Context>();
|
||||
oxReturnError(keel::init(ctx->keelCtx, std::move(fs), appName));
|
||||
using namespace std::chrono;
|
||||
ctx->startTime = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
glfwInit();
|
||||
oxReturnError(initGfx(*ctx));
|
||||
glfwSetWindowSizeCallback(ctx->window, draw);
|
||||
return ox::UPtr<Context, ContextDeleter>(ctx.release());
|
||||
}
|
||||
|
||||
static void tickFps(Context &ctx, uint64_t nowMs) noexcept {
|
||||
++ctx.draws;
|
||||
if (ctx.draws >= 500) {
|
||||
auto const duration = static_cast<double>(nowMs - ctx.prevFpsCheckTime) / 1000.0;
|
||||
auto const fps = static_cast<int>(static_cast<double>(ctx.draws) / duration);
|
||||
if constexpr(config::GlFpsPrint) {
|
||||
oxOutf("FPS: {}\n", fps);
|
||||
}
|
||||
oxTracef("turbine.fps", "FPS: {}", fps);
|
||||
ctx.prevFpsCheckTime = nowMs;
|
||||
ctx.draws = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error run(Context &ctx) noexcept {
|
||||
int sleepTime = 0;
|
||||
while (!glfwWindowShouldClose(ctx.window)) {
|
||||
glfwPollEvents();
|
||||
auto const ticks = ticksMs(ctx);
|
||||
if (ctx.wakeupTime <= ticks) {
|
||||
sleepTime = ctx.updateHandler(ctx);
|
||||
if (sleepTime >= 0) {
|
||||
ctx.wakeupTime = ticks + static_cast<unsigned>(sleepTime);
|
||||
} else {
|
||||
ctx.wakeupTime = ~uint64_t(0);
|
||||
}
|
||||
} else {
|
||||
sleepTime = 10;
|
||||
}
|
||||
tickFps(ctx, ticks);
|
||||
draw(ctx);
|
||||
if (!ctx.constantRefresh) {
|
||||
if (ctx.uninterruptedRefreshes) {
|
||||
--ctx.uninterruptedRefreshes;
|
||||
} else {
|
||||
glfwWaitEventsTimeout(sleepTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
shutdown(ctx);
|
||||
return {};
|
||||
}
|
||||
|
||||
void shutdown(Context &ctx) noexcept {
|
||||
if (ctx.window) {
|
||||
#if TURBINE_USE_IMGUI
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
#endif
|
||||
glfwDestroyWindow(ctx.window);
|
||||
ctx.window = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t ticksMs(Context const&ctx) noexcept {
|
||||
using namespace std::chrono;
|
||||
auto const now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
return static_cast<uint64_t>(now - ctx.startTime);
|
||||
}
|
||||
|
||||
bool buttonDown(Context const&ctx, Key key) noexcept {
|
||||
return (ctx.keysDown >> static_cast<int>(key)) & 1;
|
||||
}
|
||||
|
||||
void requestShutdown(Context &ctx) noexcept {
|
||||
glfwSetWindowShouldClose(ctx.window, true);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user