Added configuration helper types.
This commit is contained in:
parent
9bc56a2748
commit
20020318e1
@ -8,6 +8,7 @@ if not hasattr(env, 'Jinja'):
|
||||
|
||||
src_files = Split("""
|
||||
application.cpp
|
||||
config.cpp
|
||||
fonts.gen.cpp
|
||||
stb_image.cpp
|
||||
""")
|
||||
|
365
private/raid/config.cpp
Normal file
365
private/raid/config.cpp
Normal file
@ -0,0 +1,365 @@
|
||||
|
||||
#include "raid/config.hpp"
|
||||
|
||||
#include <mijin/async/coroutine_sleep.hpp>
|
||||
#include <mijin/io/stlstream.hpp>
|
||||
#include <mijin/util/string.hpp>
|
||||
#include <mijin/util/variant.hpp>
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
template<>
|
||||
struct YAML::convert<raid::ConfigSection>
|
||||
{
|
||||
static Node encode(const raid::ConfigSection& section)
|
||||
{
|
||||
Node node;
|
||||
for (const auto& [key, value] : section.getValues()) {
|
||||
node[key] = value;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static bool decode(const Node& node, raid::ConfigSection& section)
|
||||
{
|
||||
if (!node.IsMap()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mijin::VectorMap<std::string, raid::ConfigValue> values;
|
||||
for (YAML::const_iterator it = node.begin(); it != node.end(); ++it) {
|
||||
values.emplace(it->first.as<std::string>(), it->second.as<raid::ConfigValue>());
|
||||
}
|
||||
section = raid::ConfigSection(std::move(values));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
template<>
|
||||
struct YAML::convert<raid::ConfigArray>
|
||||
{
|
||||
static Node encode(const raid::ConfigArray& array)
|
||||
{
|
||||
Node node;
|
||||
for (const raid::ConfigValue& value : array.getValues()) {
|
||||
node.push_back(value);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static bool decode(const Node& node, raid::ConfigArray& array)
|
||||
{
|
||||
if (!node.IsSequence()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<raid::ConfigValue> values;
|
||||
for (const YAML::Node& value : node) {
|
||||
values.push_back(value.as<raid::ConfigValue>());
|
||||
}
|
||||
array = raid::ConfigArray(std::move(values));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
template<>
|
||||
struct YAML::convert<raid::ConfigValue>
|
||||
{
|
||||
static Node encode(const raid::ConfigValue& value)
|
||||
{
|
||||
Node node;
|
||||
value.visit([&]<typename T>(const T& content) {
|
||||
if constexpr (!std::is_same_v<T, std::nullptr_t>) {
|
||||
node = content;
|
||||
}
|
||||
else {
|
||||
node = {};
|
||||
}
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
static bool decode(const Node& node, raid::ConfigValue& value)
|
||||
{
|
||||
switch (node.Type())
|
||||
{
|
||||
case YAML::NodeType::Null:
|
||||
case YAML::NodeType::Undefined:
|
||||
value = {};
|
||||
break;
|
||||
case YAML::NodeType::Sequence:
|
||||
value = node.as<raid::ConfigArray>();
|
||||
break;
|
||||
case YAML::NodeType::Map:
|
||||
value = node.as<raid::ConfigSection>();
|
||||
break;
|
||||
case YAML::NodeType::Scalar:
|
||||
try
|
||||
{
|
||||
value = node.as<raid::config_int_t>();
|
||||
break;
|
||||
}
|
||||
catch(const YAML::Exception&) {} // NOLINT(bugprone-empty-catch)
|
||||
try
|
||||
{
|
||||
value = node.as<double>();
|
||||
}
|
||||
catch(const YAML::Exception&) {} // NOLINT(bugprone-empty-catch)
|
||||
value = node.as<std::string>();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
namespace raid
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const ConfigValue EMPTY_VALUE;
|
||||
}
|
||||
|
||||
const ConfigValue& ConfigSection::operator[](std::string_view key) const noexcept
|
||||
{
|
||||
auto it = mValues.find(key);
|
||||
if (it == mValues.end()) {
|
||||
return EMPTY_VALUE;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void ConfigArray::append(ConfigValue value)
|
||||
{
|
||||
mValues.push_back(std::move(value));
|
||||
}
|
||||
|
||||
void ConfigArray::setAt(std::size_t idx, ConfigValue value)
|
||||
{
|
||||
mValues[idx] = std::move(value);
|
||||
}
|
||||
|
||||
void ConfigArray::removeAt(std::size_t idx)
|
||||
{
|
||||
mValues.erase(mValues.begin() + static_cast<std::ptrdiff_t>(idx));
|
||||
}
|
||||
|
||||
|
||||
ConfigValue& ConfigSection::getOrAdd(std::string_view key)
|
||||
{
|
||||
return mValues[key];
|
||||
}
|
||||
|
||||
void ConfigSection::set(std::string_view key, ConfigValue value)
|
||||
{
|
||||
mValues[key] = std::move(value);
|
||||
}
|
||||
|
||||
bool ConfigValue::asBool() const noexcept
|
||||
{
|
||||
return visit(mijin::Visitor{
|
||||
[](std::nullptr_t) { return false; },
|
||||
[](bool boolValue) { return boolValue; },
|
||||
[](config_int_t intValue) { return intValue != 0; },
|
||||
[](double doubleValue) { return doubleValue != 0.0; },
|
||||
[](const std::string& stringValue) { return stringValue == "true"; },
|
||||
[](const ConfigArray&) { return false; },
|
||||
[](const ConfigSection&) { return false; }
|
||||
});
|
||||
}
|
||||
|
||||
config_int_t ConfigValue::asInt() const noexcept
|
||||
{
|
||||
return visit(mijin::Visitor{
|
||||
[](std::nullptr_t) { return config_int_t(0); },
|
||||
[](bool boolValue) { return config_int_t(boolValue); },
|
||||
[](config_int_t intValue) { return intValue; },
|
||||
[](double doubleValue) { return static_cast<config_int_t>(doubleValue); },
|
||||
[](const std::string& stringValue) {
|
||||
config_int_t intValue = 0;
|
||||
if (mijin::toNumber(stringValue, intValue)) {
|
||||
return intValue;
|
||||
}
|
||||
return config_int_t(0);
|
||||
},
|
||||
[](const ConfigArray&) { return config_int_t(0); },
|
||||
[](const ConfigSection&) { return config_int_t(0); }
|
||||
});
|
||||
}
|
||||
|
||||
double ConfigValue::asDouble() const noexcept
|
||||
{
|
||||
return visit(mijin::Visitor{
|
||||
[](std::nullptr_t) { return 0.0; },
|
||||
[](bool boolValue) { return double(boolValue); },
|
||||
[](config_int_t intValue) { return static_cast<double>(intValue); },
|
||||
[](double doubleValue) { return doubleValue; },
|
||||
[](const std::string& stringValue) {
|
||||
double doubleValue = 0;
|
||||
if (mijin::toNumber(stringValue, doubleValue)) {
|
||||
return doubleValue;
|
||||
}
|
||||
return 0.0;
|
||||
},
|
||||
[](const ConfigArray&) { return 0.0; },
|
||||
[](const ConfigSection&) { return 0.0; }
|
||||
});
|
||||
}
|
||||
|
||||
const std::string& ConfigValue::asString() const noexcept
|
||||
{
|
||||
static const std::string TRUE = "true";
|
||||
static const std::string FALSE = "false";
|
||||
static const std::string EMPTY{};
|
||||
static thread_local std::string convertBuffer;
|
||||
|
||||
return visit(mijin::Visitor{
|
||||
[](std::nullptr_t) -> const std::string& { return EMPTY; },
|
||||
[](bool boolValue) -> const std::string& { return boolValue ? TRUE : FALSE; },
|
||||
[](config_int_t intValue) -> const std::string& {
|
||||
convertBuffer = std::to_string(intValue);
|
||||
return convertBuffer;
|
||||
},
|
||||
[](double doubleValue) -> const std::string& {
|
||||
convertBuffer = std::to_string(doubleValue);
|
||||
return convertBuffer;
|
||||
},
|
||||
[](const std::string& stringValue) -> const std::string& {
|
||||
return stringValue; // NOLINT(bugprone-return-const-ref-from-parameter)
|
||||
},
|
||||
[](const ConfigArray&) -> const std::string& { return EMPTY; },
|
||||
[](const ConfigSection&) -> const std::string& { return EMPTY; }
|
||||
});
|
||||
}
|
||||
|
||||
const ConfigArray& ConfigValue::asArray() const noexcept
|
||||
{
|
||||
static const ConfigArray EMPTY;
|
||||
if (isArray()) {
|
||||
return std::get<ConfigArray>(mContent);
|
||||
}
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
ConfigSection& ConfigValue::asMutableSection() noexcept
|
||||
{
|
||||
MIJIN_ASSERT(isSection(), "Cannot call this on non-section values!");
|
||||
return std::get<ConfigSection>(mContent);
|
||||
}
|
||||
|
||||
const ConfigSection& ConfigValue::asSection() const noexcept
|
||||
{
|
||||
static const ConfigSection EMPTY;
|
||||
if (isSection()) {
|
||||
return std::get<ConfigSection>(mContent);
|
||||
}
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
const ConfigValue& FileConfig::getValue(std::string_view path) const noexcept
|
||||
{
|
||||
const ConfigSection* section = &mRoot;
|
||||
while(true)
|
||||
{
|
||||
MIJIN_ASSERT(!path.empty(), "Invalid config value path.");
|
||||
|
||||
const std::string_view::size_type pos = path.find('/');
|
||||
if (pos == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
section = &(*section)[path.substr(0, pos)].asSection();
|
||||
path = path.substr(pos + 1);
|
||||
}
|
||||
return (*section)[path];
|
||||
}
|
||||
|
||||
void FileConfig::setValue(std::string_view path, ConfigValue value) noexcept
|
||||
{
|
||||
ConfigSection* section = &mRoot;
|
||||
while (true)
|
||||
{
|
||||
MIJIN_ASSERT(!path.empty(), "Invalid config value path.");
|
||||
|
||||
const std::string_view::size_type pos = path.find('/');
|
||||
if (pos == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
|
||||
ConfigValue& existing = section->getOrAdd(path.substr(0, pos));
|
||||
if (existing.isUndefined()) {
|
||||
existing = ConfigSection();
|
||||
}
|
||||
else if (!existing.isSection())
|
||||
{
|
||||
MIJIN_ERROR("Value already exists, but is not a section.");
|
||||
return;
|
||||
}
|
||||
section = &existing.asMutableSection();
|
||||
path = path.substr(pos + 1);
|
||||
}
|
||||
|
||||
section->set(path, std::move(value));
|
||||
mDirty = true;
|
||||
}
|
||||
|
||||
|
||||
mijin::Result<> FileConfig::init(mijin::PathReference path)
|
||||
{
|
||||
mPath = std::move(path);
|
||||
|
||||
if (mPath.getInfo().exists) {
|
||||
return load();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
mijin::Result<> FileConfig::load()
|
||||
{
|
||||
std::unique_ptr<mijin::Stream> stream;
|
||||
if (const mijin::StreamError result = mPath.open(mijin::FileOpenMode::READ, stream); result != mijin::StreamError::SUCCESS) {
|
||||
return mijin::ResultError(mijin::errorName(result));
|
||||
}
|
||||
|
||||
mijin::IOStreamAdapter streamAdapter(*stream);
|
||||
YAML::Node root;
|
||||
|
||||
try {
|
||||
root = YAML::Load(streamAdapter);
|
||||
}
|
||||
catch(const YAML::Exception& exc) {
|
||||
return mijin::ResultError(exc.what());
|
||||
}
|
||||
|
||||
if (!root.IsMap()) {
|
||||
return mijin::ResultError("invalid config file, expected a map");
|
||||
}
|
||||
|
||||
try {
|
||||
mRoot = root.as<ConfigSection>();
|
||||
}
|
||||
catch(const YAML::Exception& exc) {
|
||||
return mijin::ResultError(exc.what());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
mijin::Result<> FileConfig::save(bool force)
|
||||
{
|
||||
if (!force && !mDirty) {
|
||||
return {};
|
||||
}
|
||||
|
||||
mDirty = false;
|
||||
|
||||
std::unique_ptr<mijin::Stream> stream;
|
||||
if (const mijin::StreamError result = mPath.open(mijin::FileOpenMode::WRITE, stream); result != mijin::StreamError::SUCCESS) {
|
||||
return mijin::ResultError(mijin::errorName(result));
|
||||
}
|
||||
|
||||
YAML::Emitter emitter;
|
||||
emitter << YAML::Node(mRoot);
|
||||
if (const mijin::StreamError result = stream->writeText(emitter.c_str()); result != mijin::StreamError::SUCCESS) {
|
||||
return mijin::ResultError(mijin::errorName(result));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ src_files = Split("""
|
||||
main.cpp
|
||||
|
||||
application.cpp
|
||||
frames/config.cpp
|
||||
frames/data_table.cpp
|
||||
""")
|
||||
|
||||
|
@ -14,6 +14,10 @@ bool Application::init()
|
||||
setMainWindowStyle(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
setMainWindowStyle(ImGuiStyleVar_WindowBorderSize, 0.f);
|
||||
std::ranges::fill(mFrameOpen, true);
|
||||
|
||||
if (const mijin::Result<> result = mConfig.init(getFS().getPath("/config/persistent.yml")); !result.isSuccess()) {
|
||||
msgError("Error initializing config: {}.", result.getError().message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -26,6 +30,10 @@ void Application::configureImgui()
|
||||
|
||||
void Application::render()
|
||||
{
|
||||
if (const mijin::Result<> result = mConfig.save(); !result.isSuccess()) {
|
||||
msgError("Error while saving config: {}.", result.getError().message);
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenuBar())
|
||||
{
|
||||
if (ImGui::BeginMenu("File"))
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include <array>
|
||||
#include "raid/raid.hpp"
|
||||
#include "./frames/config.hpp"
|
||||
#include "./frames/data_table.hpp"
|
||||
|
||||
namespace raid_test
|
||||
@ -20,12 +21,17 @@ private:
|
||||
render_fn_t render;
|
||||
};
|
||||
static constexpr Frame FRAMES[] = {
|
||||
{.title = CONFIG_TITLE, .render = &renderConfig},
|
||||
{.title = DATA_TABLE_TITLE, .render = &renderDataTable}
|
||||
};
|
||||
static constexpr std::size_t NUM_FRAMES = sizeof(FRAMES) / sizeof(FRAMES[0]);
|
||||
|
||||
raid::FileConfig mConfig;
|
||||
bool mShowMetrics = false;
|
||||
std::array<bool, NUM_FRAMES> mFrameOpen{};
|
||||
public:
|
||||
[[nodiscard]]
|
||||
raid::FileConfig& getConfig() noexcept { return mConfig; }
|
||||
protected:
|
||||
bool init() override;
|
||||
void configureImgui() override;
|
||||
|
26
private/raid_test/frames/config.cpp
Normal file
26
private/raid_test/frames/config.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
#include "raid_test/frames/config.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include "raid/config.hpp"
|
||||
#include "raid_test/application.hpp"
|
||||
|
||||
namespace raid_test
|
||||
{
|
||||
void renderConfig(bool& open)
|
||||
{
|
||||
if (!ImGui::Begin(CONFIG_TITLE, &open))
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr const char* TEST_BOOL_PATH = "test/section/bool";
|
||||
bool testBool = gApplication.getConfig().getValue(TEST_BOOL_PATH).asBool();
|
||||
if (ImGui::Checkbox("Test Bool", &testBool)) {
|
||||
gApplication.getConfig().setValue(TEST_BOOL_PATH, testBool);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
14
private/raid_test/frames/config.hpp
Normal file
14
private/raid_test/frames/config.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(RAID_TEST_FRAMES_CONFIG_HPP_INCLUDED)
|
||||
#define RAID_TEST_FRAMES_CONFIG_HPP_INCLUDED 1
|
||||
|
||||
namespace raid_test
|
||||
{
|
||||
inline constexpr const char* CONFIG_TITLE = "Config";
|
||||
|
||||
void renderConfig(bool& open);
|
||||
} // namespace raid_test
|
||||
|
||||
#endif // !defined(RAID_TEST_FRAMES_CONFIG_HPP_INCLUDED)
|
@ -1,6 +1,7 @@
|
||||
|
||||
#include "raid_test/frames/data_table.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
#include <imgui.h>
|
||||
|
189
public/raid/config.hpp
Normal file
189
public/raid/config.hpp
Normal file
@ -0,0 +1,189 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(RAID_PUBLIC_RAID_CONFIG_HPP_INCLUDED)
|
||||
#define RAID_PUBLIC_RAID_CONFIG_HPP_INCLUDED 1
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <mijin/async/coroutine.hpp>
|
||||
#include <mijin/container/vector_map.hpp>
|
||||
#include <mijin/memory/dynamic_pointer.hpp>
|
||||
#include <mijin/types/result.hpp>
|
||||
#include <mijin/virtual_filesystem/filesystem.hpp>
|
||||
|
||||
namespace raid
|
||||
{
|
||||
class ConfigValue;
|
||||
|
||||
class ConfigArray
|
||||
{
|
||||
public:
|
||||
using iterator = std::vector<ConfigValue>::iterator;
|
||||
using const_iterator = std::vector<ConfigValue>::const_iterator;
|
||||
private:
|
||||
std::vector<ConfigValue> mValues;
|
||||
public:
|
||||
ConfigArray() noexcept = default;
|
||||
ConfigArray(const ConfigArray&) = default;
|
||||
ConfigArray(ConfigArray&&) noexcept = default;
|
||||
explicit ConfigArray(std::vector<ConfigValue> values) noexcept : mValues(std::move(values)) {}
|
||||
|
||||
ConfigArray& operator=(const ConfigArray&) = default;
|
||||
ConfigArray& operator=(ConfigArray&&) noexcept = default;
|
||||
|
||||
const ConfigValue& operator[](std::size_t idx) const noexcept { return mValues[idx]; }
|
||||
|
||||
[[nodiscard]]
|
||||
const std::vector<ConfigValue>& getValues() const noexcept { return mValues; }
|
||||
|
||||
[[nodiscard]]
|
||||
std::size_t getSize() const noexcept { return mValues.size(); }
|
||||
|
||||
void append(ConfigValue value);
|
||||
void setAt(std::size_t idx, ConfigValue value);
|
||||
void removeAt(std::size_t idx);
|
||||
|
||||
[[nodiscard]]
|
||||
bool isEmpty() const noexcept { return mValues.empty(); }
|
||||
|
||||
[[nodiscard]]
|
||||
iterator begin() noexcept { return mValues.begin(); }
|
||||
|
||||
[[nodiscard]]
|
||||
iterator end() noexcept { return mValues.end(); }
|
||||
|
||||
[[nodiscard]]
|
||||
const_iterator begin() const noexcept { return mValues.begin(); }
|
||||
|
||||
[[nodiscard]]
|
||||
const_iterator end() const noexcept { return mValues.end(); }
|
||||
};
|
||||
|
||||
class ConfigSection
|
||||
{
|
||||
private:
|
||||
mijin::VectorMap<std::string, ConfigValue> mValues;
|
||||
public:
|
||||
ConfigSection() noexcept = default;
|
||||
ConfigSection(const ConfigSection&) = default;
|
||||
ConfigSection(ConfigSection&&) noexcept = default;
|
||||
explicit ConfigSection(mijin::VectorMap<std::string, ConfigValue> values) noexcept : mValues(std::move(values)) {}
|
||||
|
||||
ConfigSection& operator=(const ConfigSection&) = default;
|
||||
ConfigSection& operator=(ConfigSection&&) noexcept = default;
|
||||
|
||||
const ConfigValue& operator[](std::string_view key) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ConfigValue& getOrAdd(std::string_view key);
|
||||
|
||||
void set(std::string_view key, ConfigValue value);
|
||||
|
||||
[[nodiscard]]
|
||||
mijin::VectorMap<std::string, ConfigValue>& getValues() noexcept { return mValues; }
|
||||
|
||||
[[nodiscard]]
|
||||
const mijin::VectorMap<std::string, ConfigValue>& getValues() const noexcept { return mValues; }
|
||||
};
|
||||
|
||||
using config_int_t = std::int64_t;
|
||||
class ConfigValue
|
||||
{
|
||||
private:
|
||||
std::variant<std::nullptr_t, bool, config_int_t, double, std::string, ConfigArray, ConfigSection> mContent;
|
||||
public:
|
||||
ConfigValue() noexcept : mContent(nullptr) {}
|
||||
ConfigValue(const ConfigValue&) = default;
|
||||
ConfigValue(ConfigValue&&) noexcept = default;
|
||||
ConfigValue(bool content) noexcept : mContent(content) {}
|
||||
ConfigValue(config_int_t content) noexcept : mContent(content) {}
|
||||
ConfigValue(double content) noexcept : mContent(content) {}
|
||||
ConfigValue(std::string content) noexcept : mContent(std::move(content)) {}
|
||||
ConfigValue(const char* content) noexcept : mContent(std::string(content)) {}
|
||||
ConfigValue(ConfigArray content) noexcept : mContent(std::move(content)) {}
|
||||
ConfigValue(ConfigSection content) noexcept : mContent(std::move(content)) {}
|
||||
|
||||
ConfigValue& operator=(const ConfigValue&) = default;
|
||||
ConfigValue& operator=(ConfigValue&&) noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
bool isUndefined() const noexcept { return std::holds_alternative<std::nullptr_t>(mContent); }
|
||||
|
||||
[[nodiscard]]
|
||||
bool isBool() const noexcept { return std::holds_alternative<bool>(mContent); }
|
||||
|
||||
[[nodiscard]]
|
||||
bool isInt() const noexcept { return std::holds_alternative<config_int_t>(mContent); }
|
||||
|
||||
[[nodiscard]]
|
||||
bool isDouble() const noexcept { return std::holds_alternative<double>(mContent); }
|
||||
|
||||
[[nodiscard]]
|
||||
bool isString() const noexcept { return std::holds_alternative<std::string>(mContent); }
|
||||
|
||||
[[nodiscard]]
|
||||
bool isArray() const noexcept { return std::holds_alternative<ConfigArray>(mContent); }
|
||||
|
||||
[[nodiscard]]
|
||||
bool isSection() const noexcept { return std::holds_alternative<ConfigSection>(mContent); }
|
||||
|
||||
[[nodiscard]]
|
||||
bool asBool() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
config_int_t asInt() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
double asDouble() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
const std::string& asString() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
const ConfigArray& asArray() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ConfigSection& asMutableSection() noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
const ConfigSection& asSection() const noexcept;
|
||||
|
||||
template<typename TFunc>
|
||||
decltype(auto) visit(TFunc&& func) const noexcept
|
||||
{
|
||||
return std::visit(std::forward<TFunc>(func), mContent);
|
||||
}
|
||||
};
|
||||
|
||||
class FileConfig
|
||||
{
|
||||
private:
|
||||
ConfigSection mRoot;
|
||||
mijin::PathReference mPath;
|
||||
bool mDirty = false;
|
||||
public:
|
||||
[[nodiscard]]
|
||||
const ConfigSection& getRoot() const noexcept { return mRoot; }
|
||||
|
||||
[[nodiscard]]
|
||||
const ConfigValue& getValue(std::string_view path) const noexcept;
|
||||
|
||||
void setValue(std::string_view path, ConfigValue value) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
mijin::Result<> init(mijin::PathReference path);
|
||||
|
||||
[[nodiscard]]
|
||||
mijin::Result<> load();
|
||||
|
||||
[[nodiscard]]
|
||||
mijin::Result<> save(bool force = false);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !defined(RAID_PUBLIC_RAID_CONFIG_HPP_INCLUDED)
|
@ -55,6 +55,21 @@ inline bool ToggleImageButton(const char* strId, ImTextureID textureId, const Im
|
||||
return clicked;
|
||||
}
|
||||
|
||||
inline bool BeginPopupButton(const char* label)
|
||||
{
|
||||
char popupId[128] = {"popup##"};
|
||||
std::strcat(popupId, label);
|
||||
|
||||
const float popupX = ImGui::GetCursorScreenPos().x;
|
||||
if (ImGui::Button(label)) {
|
||||
ImGui::OpenPopup(popupId);
|
||||
}
|
||||
|
||||
const float popupY = ImGui::GetCursorScreenPos().y;
|
||||
ImGui::SetNextWindowPos({popupX, popupY});
|
||||
return ImGui::BeginPopup(popupId, ImGuiWindowFlags_NoNav);
|
||||
}
|
||||
|
||||
struct DataTableState
|
||||
{
|
||||
std::vector<std::size_t> sortedIndices;
|
||||
|
@ -5,5 +5,6 @@
|
||||
#define RAID_PUBLIC_RAID_RAID_HPP_INCLUDED 1
|
||||
|
||||
#include "./application.hpp"
|
||||
#include "./config.hpp"
|
||||
|
||||
#endif // !defined(RAID_PUBLIC_RAID_RAID_HPP_INCLUDED)
|
||||
|
Loading…
x
Reference in New Issue
Block a user