Add TOML support and playback error handling.
This commit is contained in:
parent
7edd8bbc87
commit
893cd8f9d4
12 changed files with 18097 additions and 203 deletions
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
16
assets/licenses/TomlPlusPlus.txt
Normal file
16
assets/licenses/TomlPlusPlus.txt
Normal 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.
|
|
@ -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
|
||||
|
|
4
main.cpp
4
main.cpp
|
@ -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];
|
||||
|
|
|
@ -62,6 +62,7 @@ if get_option('portals') and target_machine.system() == 'linux'
|
|||
endif
|
||||
|
||||
srcs = [
|
||||
'IconSet.cpp',
|
||||
'main.cpp',
|
||||
'RendererBackend.cpp',
|
||||
'playback.cpp',
|
||||
|
|
91
playback.cpp
91
playback.cpp
|
@ -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) {
|
||||
if (max >= bufsize) {
|
||||
st->putSamples((SAMPLETYPE*)buf, i/unit);
|
||||
max = 0;
|
||||
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;
|
||||
}
|
22
playback.h
22
playback.h
|
@ -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
374
theme.cpp
|
@ -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
14
theme.h
|
@ -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
17748
thirdparty/toml.hpp
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue