Add TOML support and playback error handling.

This commit is contained in:
Zachary Hall 2023-12-22 14:13:48 -08:00
parent 7edd8bbc87
commit 893cd8f9d4
No known key found for this signature in database
GPG key ID: 87248117B6C9569A
12 changed files with 18097 additions and 203 deletions

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/neko-player.iml" filepath="$PROJECT_DIR$/.idea/neko-player.iml" />
</modules>
</component>
</project>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -6,5 +6,8 @@
<mapping directory="$PROJECT_DIR$/imgui" vcs="Git" />
<mapping directory="$PROJECT_DIR$/subprojects/SDL-Mixer-X" vcs="Git" />
<mapping directory="$PROJECT_DIR$/subprojects/jsoncpp" vcs="Git" />
<mapping directory="$PROJECT_DIR$/subprojects/vgmstream" vcs="Git" />
<mapping directory="$PROJECT_DIR$/subprojects/vgmstream/dependencies/LibAtrac9" vcs="Git" />
<mapping directory="$PROJECT_DIR$/subprojects/vgmstream/dependencies/libg719_decode" vcs="Git" />
</component>
</project>

View file

@ -0,0 +1,16 @@
MIT License
Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -35,6 +35,7 @@ add_basic 'licenses/libportal.txt' 'libportal_license'
add_basic 'licenses/ForkAwesome.txt' 'forkawesome_license'
add_basic 'licenses/libintl.txt' 'libintl_license'
add_basic 'licenses/cli11.txt' 'cli11_license'
add_basic 'licenses/TomlPlusPlus.txt' 'tomlplusplus_license'
add_basic '../IconFontCppHeaders/licence.txt' 'icnfntcpphdrs_license'
echo '#pragma once' > 'assets.h'
for i in "${ASSETS[@]}"; do

View file

@ -335,7 +335,8 @@ void MainLoop::GuiFunction() {
#endif
LicenseData(_TR_CTX("Library name", "Noto Sans"), "OFL-1.1-RFN"),
LicenseData(_TR_CTX("Library name", "Fork Awesome"), "OFL-1.1-RFN"),
LicenseData(_TR_CTX("Library name", "IconFontCppHeaders"), "Zlib")
LicenseData(_TR_CTX("Library name", "IconFontCppHeaders"), "Zlib"),
LicenseData(_TR_CTX("Library name", "TOML++"), "MIT")
};
// Do this in an inner scope so that 'i' isn't accidentally used outside it,
// and so that 'i' can refer to another variable such as in a for loop.
@ -356,6 +357,7 @@ void MainLoop::GuiFunction() {
LOAD_LICENSE(projects[i], notosans); i++;
LOAD_LICENSE(projects[i], forkawesome); i++;
LOAD_LICENSE(projects[i], icnfntcpphdrs); i++;
LOAD_LICENSE(projects[i], tomlplusplus); i++;
}
// Left
static LicenseData selected = projects[0];

View file

@ -62,6 +62,7 @@ if get_option('portals') and target_machine.system() == 'linux'
endif
srcs = [
'IconSet.cpp',
'main.cpp',
'RendererBackend.cpp',
'playback.cpp',

View file

@ -16,26 +16,27 @@ void Playback::SDLCallbackInner(Uint8 *stream, int len) {
SDL_memset((void*)stream, 0, len);
if (!playback_ready.load()) {
return;
}
if (st == nullptr) {
return;
}
size_t i = 0;
size_t max = 0;
size_t unit = sizeof(SAMPLETYPE) * spec.channels;
size_t bytes_per_iter = std::min(((bufsize / unit)) * unit, (size_t)fakespec.size);
while (st->numSamples() <= (uint)len) {
while (st->numSamples() < (size_t)len) {
if (general_mixer == nullptr) {
return;
}
general_mixer(NULL, buf + i, bytes_per_iter);
general_mixer(nullptr, buf + i, (int)bytes_per_iter);
i += bytes_per_iter;
max = i + bytes_per_iter;
if (i + bytes_per_iter >= bufsize) {
st->putSamples((SAMPLETYPE*)buf, i / unit);
max = 0;
if (max >= bufsize) {
st->putSamples((SAMPLETYPE*)buf, i/unit);
i = 0;
SDL_memset((void*)buf, 0, bufsize);
max = i + bytes_per_iter;
}
}
st->putSamples((SAMPLETYPE*)buf, max / unit);
st->receiveSamples((SAMPLETYPE*)stream, len / unit);
}
void Playback::SDLCallback(void *userdata, Uint8 *stream, int len) {
@ -43,9 +44,12 @@ void Playback::SDLCallback(void *userdata, Uint8 *stream, int len) {
}
Mix_Music *Playback::Load(const char *file) {
Mix_Music *output = Mix_LoadMUS(file);
if (!output) {
if (output == nullptr) {
printf("Error loading music '%s': %s\n", file, Mix_GetError());
throw std::exception();
error_mutex.lock();
errors.emplace("Error loading music!");
error_mutex.unlock();
return nullptr;
}
Mix_PlayMusicStream(output, -1);
length = Mix_MusicDuration(output);
@ -57,18 +61,22 @@ void Playback::Unload(Mix_Music *music) {
Mix_FreeMusic(music);
}
void Playback::UpdateST() {
if (speed > 0.0f) {
pitch = std::max(std::min(speed, MaxSpeed), MinSpeed);
st->setRate(speed);
}
if (tempo > 0.0f) {
pitch = std::max(std::min(tempo, MaxTempo), MinTempo);
st->setTempo(tempo);
}
if (pitch > 0.0f) {
pitch = std::max(std::min(pitch, MaxPitch), MinPitch);
st->setPitch(pitch);
}
}
double Playback::GetMaxSeconds() {
return std::max(MaxSpeed * MaxTempo, st->getInputOutputSampleRatio());
return std::max((double)(MaxSpeed * MaxTempo), st->getInputOutputSampleRatio());
}
void Playback::ThreadFunc() {
#ifdef __linux__
@ -77,13 +85,13 @@ void Playback::ThreadFunc() {
bool reload = false;
while (running) {
playback_ready.store(false);
if (reload) {
printf("Resuming playback...\n");
}
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
printf("Error initializing SDL: '%s'\n", SDL_GetError());
throw std::exception();
error_mutex.lock();
errors.emplace("Failed to initialize SDL!");
error_mutex.unlock();
return;
}
}
SDL_AudioSpec obtained;
@ -101,9 +109,13 @@ void Playback::ThreadFunc() {
desired.userdata = this;
st = new SoundTouch();
Mix_Init(MIX_INIT_FLAC|MIX_INIT_MID|MIX_INIT_MOD|MIX_INIT_MP3|MIX_INIT_OGG|MIX_INIT_OPUS|MIX_INIT_WAVPACK);
if ((device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) {
if ((device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) {
printf("Error opening audio device: '%s'\n", SDL_GetError());
throw std::exception();
error_mutex.lock();
errors.emplace("Failed to open audio device!");
error_mutex.unlock();
running = false;
break;
}
spec = obtained;
st->setSampleRate(spec.freq);
@ -116,23 +128,33 @@ void Playback::ThreadFunc() {
fakespec.samples *= maxSeconds;
size_t new_bufsize = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds);
buf = (Uint8*)malloc(new_bufsize);
if (buf == NULL) {
throw std::exception();
if (buf == nullptr) {
error_mutex.lock();
errors.emplace("Failed to allocate memory for playback!");
error_mutex.unlock();
running = false;
break;
}
bufsize = new_bufsize;
general_mixer = Mix_GetGeneralMixer();
Mix_InitMixer(&fakespec, SDL_TRUE);
Mix_InitMixer(&fakespec, SDL_FALSE);
SDL_PauseAudioDevice(device, 0);
Mix_Music *music = Load(filePath.c_str());
if (reload) {
Seek(position);
}
reload = false;
if (music) {
playback_ready.store(true);
while (running && !reload) {
} else {
playback_ready.store(false);
}
while (running) {
if (file_changed.exchange(false)) {
Unload(music);
music = Load(filePath.c_str());
if (music) {
playback_ready.store(true);
} else {
playback_ready.store(false);
}
}
if (flag_mutex.try_lock()) {
if (seeking.exchange(false)) {
@ -144,7 +166,7 @@ void Playback::ThreadFunc() {
Mix_ResumeMusicStream(music);
}
if (update.exchange(false)) {
Mix_VolumeMusicStream(music, (volume / 100.0 * MIX_MAX_VOLUME));
Mix_VolumeMusicStream(music, (int)(volume / 100.0 * MIX_MAX_VOLUME));
SDL_LockAudioDevice(device);
UpdateST();
size_t correct_buf_size = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds);
@ -161,7 +183,11 @@ void Playback::ThreadFunc() {
general_mixer = nullptr;
bufsize = 0;
buf = (Uint8*)realloc((void*)buf, correct_buf_size);
if (buf == NULL) {
if (buf == nullptr) {
error_mutex.lock();
errors.emplace("Failed to allocate memory for playback!");
error_mutex.unlock();
running = false;
break;
}
bufsize = correct_buf_size;
@ -251,3 +277,20 @@ void Playback::Update() {
bool Playback::IsStopped() {
return !running;
}
optional<std::string> Playback::GetError() {
if (ErrorExists()) {
error_mutex.lock();
std::string error = errors.back();
errors.pop();
error_mutex.unlock();
return error;
} else {
return {};
}
}
bool Playback::ErrorExists() {
error_mutex.lock();
bool output = !errors.empty();
error_mutex.unlock();
return output;
}

View file

@ -10,9 +10,13 @@
#include <SoundTouch.h>
#include <span>
#include <optional>
#include <vector>
#include <queue>
using namespace soundtouch;
using std::span;
using std::optional;
using std::vector;
using std::queue;
class Playback {
private:
std::string filePath;
@ -23,6 +27,7 @@ private:
std::atomic_bool restart;
std::atomic_bool playback_ready;
std::mutex flag_mutex;
std::mutex error_mutex;
std::thread thread;
double position;
double length;
@ -42,6 +47,7 @@ private:
void ThreadFunc();
void UpdateST();
double GetMaxSeconds();
queue<std::string> errors;
public:
Playback();
~Playback();
@ -58,11 +64,13 @@ public:
float speed;
float tempo;
float pitch;
double MaxSeconds = 100.0;
double MaxSpeed = 4.0;
double MaxPitch = 4.0;
double MaxTempo = 4.0;
double MinSpeed = 0.25;
double MinPitch = 0.25;
double MinTempo = 0.25;
float MaxSeconds = 100.0;
float MaxSpeed = 4.0;
float MaxPitch = 4.0;
float MaxTempo = 4.0;
float MinSpeed = 0.25;
float MinPitch = 0.25;
float MinTempo = 0.25;
optional<std::string> GetError();
bool ErrorExists();
};

374
theme.cpp
View file

@ -1,6 +1,7 @@
#include "theme.h"
#include "imgui.h"
#include "json/value.h"
#include "thirdparty/toml.hpp"
#include "translation.h"
#include <cmath>
#include <exception>
@ -303,7 +304,7 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid
selectedThemeName = "";
filter = "";
saveAsOpen = false;
theme->Save(Theme::themeDir / selectedThemePath.replace_extension(".json"));
theme->Save(Theme::themeDir / selectedThemePath.replace_extension(".toml"));
theme->file_path = selectedThemePath.generic_string();
}
}
@ -324,25 +325,18 @@ AccentColorizer::AccentColorizer() {
Value = false;
Alpha = false;
}
AccentColorizer::AccentColorizer(Json::Value json) : AccentColorizer() {
if (json.isBool()) {
Hue = json.asBool();
Saturation = Hue;
Value = Hue;
Alpha = Hue;
} else {
Hue = json["hue"].asBool();
Saturation = json["saturation"].asBool();
Value = json["value"].asBool();
Alpha = json["alpha"].asBool();
}
AccentColorizer::AccentColorizer(toml::table table) : AccentColorizer() {
Hue = **table["hue"].as_boolean();
Saturation = **table["saturation"].as_boolean();
Value = **table["value"].as_boolean();
Alpha = **table["alpha"].as_boolean();
}
Json::Value AccentColorizer::Serialize() {
Json::Value output;
output["hue"] = Hue;
output["saturation"] = Saturation;
output["value"] = Value;
output["alpha"] = Alpha;
toml::table AccentColorizer::Serialize() {
toml::table output;
output.insert("hue", Hue);
output.insert("saturation", Saturation);
output.insert("value", Value);
output.insert("alpha", Alpha);
return output;
}
void AccentColorizer::Colorize(ImVec4 accent, ImVec4 &color) {
@ -375,79 +369,78 @@ void Theme::Apply(ImVec4 accent) {
void Theme::Save(string path) {
printf("Saving theme to %s...\n", path.c_str());
{
Json::Value config;
toml::table config;
std::ofstream stream;
stream.open(path);
{
Json::Value metadata;
metadata["SchemaVersion"] = 2;
toml::table metadata;
metadata.insert("SchemaVersion", 3);
{
Json::Value stringsList;
toml::table stringsList;
for (auto kv : strings) {
Json::Value stringsEntryJson;
toml::table stringsEntryJson;
string language = kv.first;
ThemeStrings stringsEntry = kv.second;
stringsEntryJson["Name"] = stringsEntry.name;
stringsEntryJson["Description"] = stringsEntry.description;
stringsList[language] = stringsEntryJson;
stringsEntryJson.insert("name", stringsEntry.name);
stringsEntryJson.insert("desc", stringsEntry.description);
stringsList.insert(language, stringsEntryJson);
}
metadata["Strings"] = stringsList;
metadata.insert("Strings", stringsList);
}
config["meta"] = metadata;
config.insert("meta", metadata);
}
{
Json::Value rounding;
rounding["Frame"] = style.FrameRounding;
rounding["Window"] = style.WindowRounding;
rounding["Child"] = style.ChildRounding;
rounding["Popup"] = style.PopupRounding;
rounding["Scrollbar"] = style.ScrollbarRounding;
rounding["Grab"] = style.GrabRounding;
rounding["Tab"] = style.TabRounding;
config["rounding"] = rounding;
toml::table rounding;
rounding.insert("Frame", style.FrameRounding);
rounding.insert("Window", style.WindowRounding);
rounding.insert("Child", style.ChildRounding);
rounding.insert("Popup", style.PopupRounding);
rounding.insert("Scrollbar", style.ScrollbarRounding);
rounding.insert("Grab", style.GrabRounding);
rounding.insert("Tab", style.TabRounding);
config.insert("rounding", rounding);
}
{
Json::Value sizing;
sizing["FrameX"] = style.FramePadding.x;
sizing["FrameY"] = style.FramePadding.y;
sizing["WindowX"] = style.WindowPadding.x;
sizing["WindowY"] = style.WindowPadding.y;
sizing["CellX"] = style.CellPadding.x;
sizing["CellY"] = style.CellPadding.y;
sizing["SeparatorTextX"] = style.SeparatorTextPadding.x;
sizing["SeparatorTextY"] = style.SeparatorTextPadding.y;
sizing["ItemSpacingX"] = style.ItemSpacing.x;
sizing["ItemSpacingY"] = style.ItemSpacing.y;
sizing["Scrollbar"] = style.ScrollbarSize;
sizing["Grab"] = style.GrabMinSize;
config["sizing"] = sizing;
toml::table sizing;
sizing.insert("FrameX", style.FramePadding.x);
sizing.insert("FrameY", style.FramePadding.y);
sizing.insert("WindowX", style.WindowPadding.x);
sizing.insert("WindowY", style.WindowPadding.y);
sizing.insert("CellX", style.CellPadding.x);
sizing.insert("CellY", style.CellPadding.y);
sizing.insert("SeparatorTextX", style.SeparatorTextPadding.x);
sizing.insert("SeparatorTextY", style.SeparatorTextPadding.y);
sizing.insert("ItemSpacingX", style.ItemSpacing.x);
sizing.insert("ItemSpacingY", style.ItemSpacing.y);
sizing.insert("Scrollbar", style.ScrollbarSize);
sizing.insert("Grab", style.GrabMinSize);
config.insert("sizing", sizing);
}
{
Json::Value borders;
borders["Frame"] = style.FrameBorderSize;
borders["Window"] = style.WindowBorderSize;
borders["Child"] = style.ChildBorderSize;
borders["Popup"] = style.PopupBorderSize;
borders["Tab"] = style.TabBorderSize;
borders["Tab"] = style.TabBorderSize;
borders["SeparatorText"] = style.SeparatorTextBorderSize;
config["borders"] = borders;
toml::table borders;
borders.insert("Frame", style.FrameBorderSize);
borders.insert("Window", style.WindowBorderSize);
borders.insert("Child", style.ChildBorderSize);
borders.insert("Popup", style.PopupBorderSize);
borders.insert("Tab", style.TabBorderSize);
borders.insert("SeparatorText", style.SeparatorTextBorderSize);
config.insert("borders", borders);
}
{
Json::Value colors;
toml::table colors;
for (int i = 0; i < ImGuiCol_COUNT; i++)
{
const char* name = ImGui::GetStyleColorName(i);
ImVec4 color = style.Colors[i];
Json::Value colorValue;
colorValue["r"] = color.x;
colorValue["g"] = color.y;
colorValue["b"] = color.z;
colorValue["a"] = color.w;
colorValue["ConvertToAccent"] = AccentColorizers[i].Serialize();
colors[name] = colorValue;
toml::table colorValue;
colorValue.insert("r", color.x);
colorValue.insert("g", color.y);
colorValue.insert("b", color.z);
colorValue.insert("a", color.w);
colorValue.insert("accent", AccentColorizers[i].Serialize());
colors.insert(name, colorValue);
}
config["colors"] = colors;
config.insert("colors", colors);
}
stream << config;
stream.close();
@ -465,15 +458,16 @@ void Theme::updateAvailableThemes() {
for (auto const& dir_entry : directory_iterator(themeDir)) {
if (dir_entry.is_regular_file()) {
if (dir_entry.path().extension().string() == ".json") {
path path = dir_entry.path();
availableThemes.insert(path);
Json::Value config;
std::ifstream stream;
stream.open(path);
if (stream.is_open()) {
stream >> config;
themeStrings[path] = ThemeStrings(config);
}
string curpath = Migrate(dir_entry.path().string());
std::filesystem::remove(dir_entry.path());
availableThemes.insert(curpath);
toml::table config = toml::parse_file(curpath);
themeStrings[curpath] = ThemeStrings(config);
} else if (dir_entry.path().extension().string() == ".toml") {
string curpath = dir_entry.path().string();
availableThemes.insert(curpath);
toml::table config = toml::parse_file(curpath);
themeStrings[curpath] = ThemeStrings(config);
}
}
}
@ -524,109 +518,201 @@ ThemeStrings Theme::GetStrings() {
return strings["fallback"];
}
}
ThemeStrings::ThemeStrings(Json::Value config) : ThemeStrings() {
ThemeStrings::ThemeStrings(toml::table config) : ThemeStrings() {
char *language_c = CURRENT_LANGUAGE;
string language = language_c;
if (config.isMember("meta")) {
Json::Value metadata = config["meta"];
if (config.contains("meta")) {
toml::table metadata = *config["meta"].as_table();
//metadata["SchemaVersion"] = 1;
if (metadata.isMember("Strings")) {
Json::Value stringsList = metadata["Strings"];
if (stringsList.isMember(language)) {
Json::Value stringsEntryJson = stringsList[language];
if (stringsEntryJson.isMember("Name")) {
name = stringsEntryJson["Name"].asString();
if (metadata.contains("Strings")) {
toml::table stringsList = *metadata["Strings"].as_table();
if (stringsList.contains(language)) {
toml::table stringsEntryJson = *stringsList[language].as_table();
if (stringsEntryJson.contains("name")) {
name = **stringsEntryJson["name"].as_string();
}
if (stringsEntryJson.isMember("Description")) {
description = stringsEntryJson["Description"].asString();
if (stringsEntryJson.contains("desc")) {
description = **stringsEntryJson["desc"].as_string();
}
} else if (metadata.isMember("fallback")) {
Json::Value stringsEntryJson = stringsList["fallback"];
if (stringsEntryJson.isMember("Name")) {
name = stringsEntryJson["Name"].asString();
} else if (metadata.contains("fallback")) {
toml::table stringsEntryJson = *stringsList["fallback"].as_table();
if (stringsEntryJson.contains("name")) {
name = **stringsEntryJson["name"].as_string();
}
if (stringsEntryJson.isMember("Description")) {
description = stringsEntryJson["Description"].asString();
if (stringsEntryJson.contains("desc")) {
description = **stringsEntryJson["desc"].as_string();
}
}
}
}
}
Theme::Theme(string path) : Theme() {
std::string Theme::Migrate(std::string path) {
if (path.ends_with(".json")) {
std::ifstream stream(path);
Json::Value config;
std::ifstream stream;
stream.open(path);
if (stream.is_open()) {
stream >> config;
toml::table newConfig;
if (config.isMember("meta")) {
Json::Value metadata = config["meta"];
//metadata["SchemaVersion"] = 1;
toml::table newMeta;
if (metadata.isMember("Strings")) {
Json::Value stringsList = metadata["Strings"];
toml::table newStringsList;
for (string language : stringsList.getMemberNames()) {
Json::Value stringsEntryJson = stringsList[language];
ThemeStrings stringsEntry;
toml::table newStringsEntry;
if (stringsEntryJson.isMember("Name")) {
stringsEntry.name = stringsEntryJson["Name"].asString();
string value = stringsEntryJson["Name"].asString();
newStringsEntry.insert("name", value);
}
if (stringsEntryJson.isMember("Description")) {
stringsEntry.description = stringsEntryJson["Description"].asString();
string value = stringsEntryJson["Description"].asString();
newStringsEntry.insert("desc", value);
}
newStringsList.insert(language, newStringsEntry);
}
newMeta.insert("Strings", newStringsList);
}
newConfig.insert("meta", newMeta);
}
if (config.isMember("rounding")) {
Json::Value rounding = config["rounding"];
toml::table newRounding;
for (string key : rounding.getMemberNames()) {
newRounding.insert(key, rounding[key].asFloat());
}
newConfig.insert("rounding", newRounding);
}
if (config.isMember("sizing")) {
Json::Value rounding = config["sizing"];
toml::table newRounding;
for (string key : rounding.getMemberNames()) {
newRounding.insert(key, rounding[key].asFloat());
}
newConfig.insert("sizing", newRounding);
}
if (config.isMember("borders")) {
Json::Value rounding = config["borders"];
toml::table newRounding;
for (string key : rounding.getMemberNames()) {
newRounding.insert(key, rounding[key].asFloat());
}
newConfig.insert("borders", newRounding);
}
if (config.isMember("colors")) {
Json::Value colors = config["colors"];
toml::table newColors;
for (int i = 0; i < ImGuiCol_COUNT; i++)
{
toml::table newColor;
const char* name = ImGui::GetStyleColorName(i);
if (colors.isMember(name)) {
Json::Value colorValue = colors[name];
ImVec4 color = ImVec4(colorValue["r"].asFloat(), colorValue["g"].asFloat(), colorValue["b"].asFloat(), colorValue["a"].asFloat());
newColor.insert("r", colorValue["r"].asFloat());
newColor.insert("g", colorValue["g"].asFloat());
newColor.insert("b", colorValue["b"].asFloat());
newColor.insert("a", colorValue["a"].asFloat());
toml::table newAccentOptions;
if (colorValue["ConvertToAccent"].isBool()) {
newAccentOptions.insert("hue", colorValue["ConvertToAccent"].asBool());
newAccentOptions.insert("saturation", false);
newAccentOptions.insert("value", false);
newAccentOptions.insert("alpha", false);
} else {
Json::Value accentOptions = colorValue["ConvertToAccent"];
newAccentOptions.insert("hue", accentOptions["hue"].asBool());
newAccentOptions.insert("saturation", accentOptions["saturation"].asBool());
newAccentOptions.insert("value", accentOptions["value"].asBool());
newAccentOptions.insert("alpha", accentOptions["alpha"].asBool());
}
newColor.insert("accent", newAccentOptions);
newColors.insert(name, newColor);
}
}
}
stream.close();
std::string newPath = path.replace(path.size() - 4, 4, "toml");
std::ofstream outStream(newPath);
outStream << newConfig;
outStream.close();
return newPath;
}
return path;
}
Theme::Theme(string path) : Theme() {
path = Migrate(path);
toml::table config;
config = toml::parse_file(path);
if (config.contains("meta")) {
toml::table metadata = *config["meta"].as_table();
//metadata["SchemaVersion"] = 1;
if (metadata.contains("Strings")) {
toml::table stringsList = *metadata["Strings"].as_table();
for (auto kv : stringsList) {
string language = string(kv.first.str());
toml::table stringEntryToml = *kv.second.as_table();
ThemeStrings stringsEntry;
if (stringEntryToml.contains("name")) {
stringsEntry.name = stringEntryToml["name"].as_string()->value_or("Unknown");
}
if (stringEntryToml.contains("desc")) {
stringsEntry.description = stringEntryToml["desc"].as_string()->value_or("No description.");
}
strings[language] = stringsEntry;
}
}
}
if (config.isMember("rounding")) {
Json::Value rounding = config["rounding"];
style.FrameRounding = rounding["Frame"].asFloat();
style.WindowRounding = rounding["Window"].asFloat();
style.ChildRounding = rounding["Child"].asFloat();
style.PopupRounding = rounding["Popup"].asFloat();
style.ScrollbarRounding = rounding["Scrollbar"].asFloat();
style.GrabRounding = rounding["Grab"].asFloat();
style.TabRounding = rounding["Tab"].asFloat();
if (config.contains("rounding")) {
toml::table rounding = *config["rounding"].as_table();
style.FrameRounding = (float)(**(rounding["Frame"].as_floating_point()));
style.WindowRounding = (float)(**(rounding["Window"].as_floating_point()));
style.ChildRounding = (float)(**(rounding["Child"].as_floating_point()));
style.PopupRounding = (float)(**(rounding["Popup"].as_floating_point()));
style.ScrollbarRounding = (float)(**(rounding["Scrollbar"].as_floating_point()));
style.GrabRounding = (float)(**(rounding["Grab"].as_floating_point()));
style.TabRounding = (float)(**(rounding["Tab"].as_floating_point()));
}
if (config.isMember("sizing")) {
Json::Value sizing = config["sizing"];
style.FramePadding.x = sizing["FrameX"].asFloat();
style.FramePadding.y = sizing["FrameY"].asFloat();
style.WindowPadding.x = sizing["WindowX"].asFloat();
style.WindowPadding.y = sizing["WindowY"].asFloat();
style.CellPadding.x = sizing["CellX"].asFloat();
style.CellPadding.y = sizing["CellY"].asFloat();
style.SeparatorTextPadding.x = sizing["SeparatorTextX"].asFloat();
style.SeparatorTextPadding.y = sizing["SeparatorTextY"].asFloat();
style.ItemSpacing.x = sizing["ItemSpacingX"].asFloat();
style.ItemSpacing.y = sizing["ItemSpacingY"].asFloat();
style.ScrollbarSize = sizing["Scrollbar"].asFloat();
style.GrabMinSize = sizing["Grab"].asFloat();
if (config.contains("sizing")) {
toml::table sizing = *config["sizing"].as_table();
style.FramePadding.x = (float)(**sizing["FrameX"].as_floating_point());
style.FramePadding.y = (float)(**sizing["FrameY"].as_floating_point());
style.WindowPadding.x = (float)(**sizing["WindowX"].as_floating_point());
style.WindowPadding.y = (float)(**sizing["WindowY"].as_floating_point());
style.CellPadding.x = (float)(**sizing["CellX"].as_floating_point());
style.CellPadding.y = (float)(**sizing["CellY"].as_floating_point());
style.SeparatorTextPadding.x = (float)(**sizing["SeparatorTextX"].as_floating_point());
style.SeparatorTextPadding.y = (float)(**sizing["SeparatorTextY"].as_floating_point());
style.ItemSpacing.x = (float)(**sizing["ItemSpacingX"].as_floating_point());
style.ItemSpacing.y = (float)(**sizing["ItemSpacingY"].as_floating_point());
style.ScrollbarSize = (float)(**sizing["Scrollbar"].as_floating_point());
style.GrabMinSize = (float)(**sizing["Grab"].as_floating_point());
}
if (config.isMember("borders")) {
Json::Value borders = config["borders"];
style.FrameBorderSize = borders["Frame"].asFloat();
style.WindowBorderSize = borders["Window"].asFloat();
style.ChildBorderSize = borders["Child"].asFloat();
style.PopupBorderSize = borders["Popup"].asFloat();
style.TabBorderSize = borders["Tab"].asFloat();
if (borders.isMember("SeparatorText")) {
style.SeparatorTextBorderSize = borders["SeparatorText"].asFloat();
if (config.contains("borders")) {
toml::table borders = *config["borders"].as_table();
style.FrameBorderSize = (float)(**borders["Frame"].as_floating_point());
style.WindowBorderSize = (float)(**borders["Window"].as_floating_point());
style.ChildBorderSize = (float)(**borders["Child"].as_floating_point());
style.PopupBorderSize = (float)(**borders["Popup"].as_floating_point());
style.TabBorderSize = (float)(**borders["Tab"].as_floating_point());
if (borders.contains("SeparatorText")) {
style.SeparatorTextBorderSize = (float)(**borders["SeparatorText"].as_floating_point());
}
}
if (config.isMember("colors")) {
Json::Value colors = config["colors"];
if (config.contains("colors")) {
toml::table colors = *config["colors"].as_table();
for (int i = 0; i < ImGuiCol_COUNT; i++)
{
const char* name = ImGui::GetStyleColorName(i);
if (colors.isMember(name)) {
Json::Value colorValue = colors[name];
ImVec4 color = ImVec4(colorValue["r"].asFloat(), colorValue["g"].asFloat(), colorValue["b"].asFloat(), colorValue["a"].asFloat());
AccentColorizers[i] = AccentColorizer(colorValue["ConvertToAccent"]);
if (colors.contains(name)) {
toml::table colorValue = *colors[name].as_table();
ImVec4 color = ImVec4((float)**colorValue["r"].as_floating_point(), (float)**colorValue["g"].as_floating_point(), (float)**colorValue["b"].as_floating_point(), (float)**colorValue["a"].as_floating_point());
AccentColorizers[i] = AccentColorizer(*colorValue["accent"].as_table());
style.Colors[i] = color;
}
}
}
stream.close();
}
file_path = path;
}
Theme::Theme(path path) : Theme(path.string()) {

14
theme.h
View file

@ -7,6 +7,7 @@
#include "file_browser.h"
#include <json/json.h>
#include <filesystem>
#include "thirdparty/toml.hpp"
using std::string;
using namespace std::filesystem;
@ -14,7 +15,7 @@ struct ThemeStrings {
string name;
string description;
ThemeStrings();
ThemeStrings(Json::Value config);
ThemeStrings(toml::table config);
};
struct AccentColorizer {
@ -29,18 +30,19 @@ struct AccentColorizer {
/// @brief Colorizes a color stored as an ImVec4 according to preferences.
void Colorize(ImVec4 accent, ImVec4 &color);
/// @brief Serialize the settings to json.
/// @returns The serialized JSON, as a Json::Value.
Json::Value Serialize();
/// @returns The serialized TOML, as a toml::table.
toml::table Serialize();
/// @brief Create a default accent colorizer
AccentColorizer();
/// @brief Deserialize the settings from JSON and construct.
/// @param json The JSON to deserialize from.
AccentColorizer(Json::Value json);
/// @brief Deserialize the settings from TOML and construct.
/// @param table The TOML to deserialize from.
AccentColorizer(toml::table table);
};
class Theme {
ImGuiStyle style;
static std::string Migrate(string path);
public:
static std::set<path> availableThemes;
static std::map<path, ThemeStrings> themeStrings;

17748
thirdparty/toml.hpp vendored Normal file

File diff suppressed because it is too large Load diff