Various improvements, including better accent colors, opening files from command line, and drag&drop

This commit is contained in:
Zachary Hall 2023-10-16 10:44:25 -07:00
parent 70f27722df
commit f1d8334335
13 changed files with 9874 additions and 37 deletions

View file

@ -157,12 +157,13 @@ int RendererBackend::Run() {
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
window = SDL_CreateWindow(NAME, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, window_width, window_height, window_flags); window = SDL_CreateWindow(NAME, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, window_width, window_height, window_flags);
SDL_SetWindowMinimumSize(window, window_width, window_height); SDL_SetWindowMinimumSize(window, window_width, window_height);
if (enable_kms) { if (enable_kms) {
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
} }
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
const vector<unsigned char> icon_data = DecodeBase85(icon_compressed_data_base85); const vector<unsigned char> icon_data = DecodeBase85(icon_compressed_data_base85);
SDL_Surface* icon = IMG_Load_RW(SDL_RWFromConstMem(icon_data.data(), icon_data.size()), 1); SDL_Surface* icon = IMG_Load_RW(SDL_RWFromConstMem(icon_data.data(), icon_data.size()), 1);
SDL_SetWindowIcon(window, icon); SDL_SetWindowIcon(window, icon);
@ -218,6 +219,7 @@ int RendererBackend::Run() {
SDL_GL_SetSwapInterval(vsync ? 1 : 0); SDL_GL_SetSwapInterval(vsync ? 1 : 0);
theme->Apply(accent_color); theme->Apply(accent_color);
Init(); Init();
SDL_ShowWindow(window);
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
// For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file.
// You may manually call LoadIniSettingsFromMemory() to load settings from your own storage. // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage.
@ -252,6 +254,11 @@ int RendererBackend::Run() {
done = true; done = true;
} }
} }
if (event.type == SDL_DROPFILE) {
if (event.drop.file != NULL) {
Drop(std::string(event.drop.file));
}
}
} }
// Start the Dear ImGui frame // Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
@ -312,3 +319,6 @@ void RendererBackend::Init() {
void RendererBackend::Deinit() { void RendererBackend::Deinit() {
// Do nothing by default. // Do nothing by default.
} }
void RendererBackend::Drop(std::string file) {
// Do nothing by default.
}

View file

@ -26,12 +26,13 @@ class RendererBackend {
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
ImFont *title; ImFont *title;
const char *prefPath; const char *prefPath;
float accent_color = 280.0; ImVec4 accent_color = ImVec4(280.0, 1.0, 1.0, 1.0);
int Run(); int Run();
void SetWindowTitle(const char *title); void SetWindowTitle(const char *title);
virtual void Init(); virtual void Init();
virtual void GuiFunction(); virtual void GuiFunction();
virtual void Deinit(); virtual void Deinit();
virtual void Drop(std::string file);
void UpdateScale(); void UpdateScale();
void AddFonts(); void AddFonts();
void SetWindowSize(int w, int h); void SetWindowSize(int w, int h);

25
assets/licenses/cli11.txt Normal file
View file

@ -0,0 +1,25 @@
CLI11 2.2 Copyright (c) 2017-2023 University of Cincinnati, developed by Henry
Schreiner under NSF AWARD 1414736. All rights reserved.
Redistribution and use in source and binary forms of CLI11, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -3,7 +3,8 @@ Type=Application
Name=Neko Player Name=Neko Player
Comment=An audio player that can properly loop audio files Comment=An audio player that can properly loop audio files
GenericName=Looping audio player GenericName=Looping audio player
Exec=neko-player Exec=neko-player %f
Icon=neko-player Icon=neko-player
MimeType=audio/x-wav;audio/ogg;audio/x-vorbis+ogg;audio/x-opus+ogg;audio/mpeg;audio/flac;audio/xm;audio/x-mod;
StartupWMClass=neko-player StartupWMClass=neko-player
Categories=Audio;AudioVideo; Categories=Audio;AudioVideo;

View file

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

View file

@ -1,5 +1,6 @@
#include "main.h" #include "main.h"
#include "assets.h" #include "assets.h"
#include "thirdparty/CLI11.hpp"
string PadZeros(string input, size_t required_length) { string PadZeros(string input, size_t required_length) {
return std::string(required_length - std::min(required_length, input.length()), '0') + input; return std::string(required_length - std::min(required_length, input.length()), '0') + input;
@ -66,7 +67,12 @@ void MainLoop::Init() {
} }
} }
if (config.isMember("accent_color")) { if (config.isMember("accent_color")) {
accent_color = config["accent_color"].asFloat(); if (config["accent_color"].isNumeric()) {
accent_color.x = config["accent_color"].asFloat() / 360.0;
} else {
Json::Value accentColor = config["accent_color"];
accent_color = ImVec4(accentColor["h"].asFloat(), accentColor["s"].asFloat(), accentColor["v"].asFloat(), accentColor["a"].asFloat());
}
} }
if (config.isMember("demo_window")) { if (config.isMember("demo_window")) {
show_demo_window = config["demo_window"].asBool(); show_demo_window = config["demo_window"].asBool();
@ -112,6 +118,31 @@ void MainLoop::Init() {
theme = new Theme(darkPath); theme = new Theme(darkPath);
} }
} }
theme->Apply(accent_color);
CLI::App app{"An audio player that can play files that need special handling to loop seamlessly."};
std::string filename = "";
app.allow_extras();
double new_speed = 1.0;
double new_tempo = 1.0;
double new_pitch = 1.0;
app.add_option("-s,--speed", new_speed, "Set the initial speed of the playback.")->default_val(1.0);
app.add_option("-t,--tempo", new_tempo, "Set the initial tempo of the playback.")->default_val(1.0);
app.add_option("-p,--pitch", new_pitch, "Set the initial pitch of the playback.")->default_val(1.0);
try {
app.parse(args);
} catch (const CLI::ParseError &e) {
exit(app.exit(e));
}
playback->speed = new_speed;
playback->tempo = new_tempo;
playback->pitch = new_pitch;
args = app.remaining();
if (args.size() > 0) {
LoadFile(args[0]);
}
}
void MainLoop::Drop(std::string file) {
LoadFile(file);
} }
void MainLoop::GuiFunction() { void MainLoop::GuiFunction() {
position = playback->GetPosition(); position = playback->GetPosition();
@ -268,9 +299,8 @@ void MainLoop::GuiFunction() {
} }
ImGui::EndChildFrame(); ImGui::EndChildFrame();
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2)); ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2));
if (ImGui::SliderFloat("##AccentColor", &accent_color, 0.0, 360.0, _TR_CTX("Preference | Accent hue slider, range 0-360 from HSV algorithm hue component", "Accent color hue: %.0f°"), ImGuiSliderFlags_NoRoundToFormat)) { ImGui::ColorEdit4("##AccentColor", &accent_color.x, ImGuiColorEditFlags_InputHSV|ImGuiColorEditFlags_DisplayHSV|ImGuiColorEditFlags_Float);
theme->Apply(accent_color); theme->Apply(accent_color);
}
} }
ImGui::End(); ImGui::End();
} }
@ -294,6 +324,7 @@ void MainLoop::GuiFunction() {
static vector<LicenseData> projects = { static vector<LicenseData> projects = {
LicenseData(APP_NAME_STR, "MIT"), LicenseData(APP_NAME_STR, "MIT"),
LicenseData(_TR_CTX("Library name", "SDL Mixer X"), "Zlib"), LicenseData(_TR_CTX("Library name", "SDL Mixer X"), "Zlib"),
LicenseData(_TR_CTX("Library name", "CLI11"), "BSD-3-Clause"),
LicenseData(_TR_CTX("Library name", "JsonCpp"), "MIT"), LicenseData(_TR_CTX("Library name", "JsonCpp"), "MIT"),
LicenseData(_TR_CTX("Library name", "SoundTouch"), "LGPL-2.1-only"), LicenseData(_TR_CTX("Library name", "SoundTouch"), "LGPL-2.1-only"),
LicenseData(_TR_CTX("Library name", "libintl"), "LGPL-2.1-only"), LicenseData(_TR_CTX("Library name", "libintl"), "LGPL-2.1-only"),
@ -313,6 +344,7 @@ void MainLoop::GuiFunction() {
// Use a variable instead of hardcoding so that a #ifdef can change the indices later on. // Use a variable instead of hardcoding so that a #ifdef can change the indices later on.
LOAD_LICENSE(projects[i], nekoplayer); i++; LOAD_LICENSE(projects[i], nekoplayer); i++;
LOAD_LICENSE(projects[i], sdl_mixer_x); i++; LOAD_LICENSE(projects[i], sdl_mixer_x); i++;
LOAD_LICENSE(projects[i], cli11); i++;
LOAD_LICENSE(projects[i], jsoncpp); i++; LOAD_LICENSE(projects[i], jsoncpp); i++;
LOAD_LICENSE(projects[i], soundtouch); i++; LOAD_LICENSE(projects[i], soundtouch); i++;
LOAD_LICENSE(projects[i], libintl); i++; LOAD_LICENSE(projects[i], libintl); i++;
@ -372,9 +404,7 @@ void MainLoop::GuiFunction() {
// Load a new file when it has been selected. // Load a new file when it has been selected.
if (fileDialog.HasSelected()) { if (fileDialog.HasSelected()) {
playback->Start(fileDialog.GetSelected().string()); LoadFile(fileDialog.GetSelected().string());
// Update the window title.
SetWindowTitle((fileDialog.GetSelected().filename().string() + std::string(" - ") + std::string(NAME)).c_str());
// Make sure to not load the file unnecessarily. // Make sure to not load the file unnecessarily.
fileDialog.ClearSelected(); fileDialog.ClearSelected();
} }
@ -383,6 +413,11 @@ void MainLoop::GuiFunction() {
SetWindowTitle(NAME); SetWindowTitle(NAME);
} }
} }
void MainLoop::LoadFile(std::string file) {
playback->Start(file);
// Update the window title.
SetWindowTitle((file + std::string(" - ") + std::string(NAME)).c_str());
}
void MainLoop::Deinit() { void MainLoop::Deinit() {
delete playback; delete playback;
@ -395,7 +430,14 @@ void MainLoop::Deinit() {
if (!themePath.empty()) { if (!themePath.empty()) {
config["theme_name"] = themePath.filename().string(); config["theme_name"] = themePath.filename().string();
} }
config["accent_color"] = accent_color; {
Json::Value accentColor;
accentColor["h"] = accent_color.x;
accentColor["s"] = accent_color.y;
accentColor["v"] = accent_color.z;
accentColor["a"] = accent_color.w;
config["accent_color"] = accentColor;
}
config["demo_window"] = show_demo_window; config["demo_window"] = show_demo_window;
config["vsync"] = vsync; config["vsync"] = vsync;
config["framerate"] = framerate; config["framerate"] = framerate;
@ -412,8 +454,13 @@ MainLoop::MainLoop() : RendererBackend() {
} }
// Main code // Main code
int main(int, char**) int main(int argc, char** argv)
{ {
MainLoop loop; MainLoop loop;
vector<std::string> args;
for (int i = 1; i < argc; i++) {
args.push_back(std::string(argv[i]));
}
loop.args = args;
return loop.Run(); return loop.Run();
} }

3
main.h
View file

@ -46,8 +46,11 @@ class MainLoop : public RendererBackend {
bool about_window = false; bool about_window = false;
bool stopped = true; bool stopped = true;
public: public:
vector<std::string> args;
void LoadFile(std::string file);
void Init() override; void Init() override;
void GuiFunction() override; void GuiFunction() override;
void Deinit() override; void Deinit() override;
void Drop(std::string file) override;
MainLoop(); MainLoop();
}; };

View file

@ -93,7 +93,7 @@ if ascli_exe.found()
metainfo_file] metainfo_file]
) )
endif endif
install_data('assets/icon.svg', rename: 'neko-player.svg', install_dir: 'share/icons/hicolor/scalable/apps/') install_data('assets/icon.svg', rename: 'neko-player', install_dir: 'share/icons/hicolor/scalable/apps/')
install_data('assets/neko-player.desktop', install_dir: 'share/applications') install_data('assets/neko-player.desktop', install_dir: 'share/applications')
install_data('assets/com.experimentalcraft.NekoPlayer.metainfo.xml', install_dir: 'share/metainfo') install_data('assets/com.experimentalcraft.NekoPlayer.metainfo.xml', install_dir: 'share/metainfo')
install_subdir('assets/translations/', exclude_files: 'neko_player.pot', strip_directory: true, install_dir: get_option('localedir')) install_subdir('assets/translations/', exclude_files: 'neko_player.pot', strip_directory: true, install_dir: get_option('localedir'))

View file

@ -20,7 +20,7 @@ void Playback::SDLCallbackInner(Uint8 *stream, int len) {
size_t i = 0; size_t i = 0;
size_t max = 0; size_t max = 0;
size_t unit = sizeof(SAMPLETYPE) * spec.channels; size_t unit = sizeof(SAMPLETYPE) * spec.channels;
size_t bytes_per_iter = std::min(((bufsize / unit)) * unit, (size_t)len); size_t bytes_per_iter = std::min(((bufsize / unit)) * unit, (size_t)fakespec.size);
while (st->numSamples() <= (uint)len) { while (st->numSamples() <= (uint)len) {
if (general_mixer == nullptr) { if (general_mixer == nullptr) {
return; return;
@ -110,6 +110,10 @@ void Playback::ThreadFunc() {
st->setChannels(spec.channels); st->setChannels(spec.channels);
UpdateST(); UpdateST();
bufsize = 0; bufsize = 0;
fakespec = spec;
double maxSeconds = GetMaxSeconds();
fakespec.size *= maxSeconds;
fakespec.samples *= maxSeconds;
size_t new_bufsize = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds); size_t new_bufsize = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds);
buf = (Uint8*)malloc(new_bufsize); buf = (Uint8*)malloc(new_bufsize);
if (buf == NULL) { if (buf == NULL) {
@ -117,7 +121,7 @@ void Playback::ThreadFunc() {
} }
bufsize = new_bufsize; bufsize = new_bufsize;
general_mixer = Mix_GetGeneralMixer(); general_mixer = Mix_GetGeneralMixer();
Mix_InitMixer(&spec, SDL_TRUE); Mix_InitMixer(&fakespec, SDL_TRUE);
SDL_PauseAudioDevice(device, 0); SDL_PauseAudioDevice(device, 0);
Mix_Music *music = Load(filePath.c_str()); Mix_Music *music = Load(filePath.c_str());
if (reload) { if (reload) {

View file

@ -33,6 +33,8 @@ private:
SDL_AudioDeviceID device; SDL_AudioDeviceID device;
SoundTouch *st; SoundTouch *st;
SDL_AudioSpec spec; SDL_AudioSpec spec;
/// @brief A fake SDL_AudioSpec used to trick SDL Mixer X into allocating a bigger buffer.
SDL_AudioSpec fakespec;
void SDLCallbackInner(Uint8 *stream, int len); void SDLCallbackInner(Uint8 *stream, int len);
static void SDLCallback(void *userdata, Uint8 *stream, int len); static void SDLCallback(void *userdata, Uint8 *stream, int len);
Mix_Music *Load(const char* file); Mix_Music *Load(const char* file);

View file

@ -189,15 +189,12 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid
if (!filter.PassFilter(name)) if (!filter.PassFilter(name))
continue; continue;
ImGui::PushID(i); ImGui::PushID(i);
bool hueEnabled = theme->HueEnabledColors.contains(i); AccentColorizer &colorizer = theme->AccentColorizers[i];
if (ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox", "Match hue preference"), &hueEnabled)) { ImGui::Text(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring label", "Match accent color preference: "));
if (hueEnabled) { ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Hue", "H: "), &colorizer.Hue); ImGui::SameLine();
theme->HueEnabledColors.insert(i); ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Saturation", "S: "), &colorizer.Saturation); ImGui::SameLine();
} else { ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Value", "V: "), &colorizer.Value); ImGui::SameLine();
theme->HueEnabledColors.erase(i); ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Alpha (opacity)", "A: "), &colorizer.Alpha); ImGui::SameLine();
}
}
ImGui::SameLine();
ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags | ImGuiColorEditFlags_DisplayHSV); ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags | ImGuiColorEditFlags_DisplayHSV);
ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);
ImGui::TextUnformatted(name); ImGui::TextUnformatted(name);
@ -321,14 +318,52 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid
} }
return false; return false;
} }
void Theme::Apply(float hue) { AccentColorizer::AccentColorizer() {
Hue = false;
Saturation = false;
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();
}
}
Json::Value AccentColorizer::Serialize() {
Json::Value output;
output["hue"] = Hue;
output["saturation"] = Saturation;
output["value"] = Value;
output["alpha"] = Alpha;
return output;
}
void AccentColorizer::Colorize(ImVec4 accent, ImVec4 &color) {
ImVec4 hsv = color;
ImGui::ColorConvertRGBtoHSV(color.x, color.y, color.z, hsv.x, hsv.y, hsv.z);
if (Saturation)
hsv.y = accent.y;
if (Value)
hsv.z = accent.z;
if (Hue)
hsv.x = accent.x;
ImGui::ColorConvertHSVtoRGB(hsv.x, hsv.y, hsv.z, color.x, color.y, color.z);
if (Alpha)
color.w *= accent.w;
}
void Theme::Apply(ImVec4 accent) {
ImGuiStyle& actual_style = ImGui::GetStyle(); ImGuiStyle& actual_style = ImGui::GetStyle();
actual_style = style; actual_style = style;
for (int i = 0; i < ImGuiCol_COUNT; i++) for (int i = 0; i < ImGuiCol_COUNT; i++)
{ {
if (HueEnabledColors.contains(i)) { AccentColorizers[i].Colorize(accent, actual_style.Colors[i]);
actual_style.Colors[i] = change_accent_color(style.Colors[i], hue);
}
} }
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
@ -345,7 +380,7 @@ void Theme::Save(string path) {
stream.open(path); stream.open(path);
{ {
Json::Value metadata; Json::Value metadata;
metadata["SchemaVersion"] = 1; metadata["SchemaVersion"] = 2;
{ {
Json::Value stringsList; Json::Value stringsList;
for (auto kv : strings) { for (auto kv : strings) {
@ -409,7 +444,7 @@ void Theme::Save(string path) {
colorValue["g"] = color.y; colorValue["g"] = color.y;
colorValue["b"] = color.z; colorValue["b"] = color.z;
colorValue["a"] = color.w; colorValue["a"] = color.w;
colorValue["ConvertToAccent"] = HueEnabledColors.contains(i); colorValue["ConvertToAccent"] = AccentColorizers[i].Serialize();
colors[name] = colorValue; colors[name] = colorValue;
} }
config["colors"] = colors; config["colors"] = colors;
@ -461,9 +496,13 @@ Theme::Theme(bool dark) : Theme() {
for (int i = 0; i < ImGuiCol_COUNT; i++) for (int i = 0; i < ImGuiCol_COUNT; i++)
{ {
ImVec4 color = style.Colors[i]; ImVec4 color = style.Colors[i];
auto colorizer = AccentColorizer();
if (color.x != color.y || color.y != color.z) { if (color.x != color.y || color.y != color.z) {
HueEnabledColors.insert(i); colorizer.Hue = true;
colorizer.Saturation = true;
colorizer.Alpha = true;
} }
AccentColorizers[i] = colorizer;
} }
style.FrameRounding = 12; style.FrameRounding = 12;
style.GrabRounding = 12; style.GrabRounding = 12;
@ -581,11 +620,8 @@ Theme::Theme(string path) : Theme() {
if (colors.isMember(name)) { if (colors.isMember(name)) {
Json::Value colorValue = colors[name]; Json::Value colorValue = colors[name];
ImVec4 color = ImVec4(colorValue["r"].asFloat(), colorValue["g"].asFloat(), colorValue["b"].asFloat(), colorValue["a"].asFloat()); ImVec4 color = ImVec4(colorValue["r"].asFloat(), colorValue["g"].asFloat(), colorValue["b"].asFloat(), colorValue["a"].asFloat());
bool hueShifted = colorValue["ConvertToAccent"].asBool(); AccentColorizers[i] = AccentColorizer(colorValue["ConvertToAccent"]);
style.Colors[i] = color; style.Colors[i] = color;
if (hueShifted) {
HueEnabledColors.insert(i);
}
} }
} }
} }

25
theme.h
View file

@ -17,6 +17,27 @@ struct ThemeStrings {
ThemeStrings(Json::Value config); ThemeStrings(Json::Value config);
}; };
struct AccentColorizer {
/// @brief Whether or not to change the hue.
bool Hue;
/// @brief Whether or not to change the saturation.
bool Saturation;
/// @brief Whether or not to change the value.
bool Value;
/// @brief Whether or not to multiply the alpha.
bool Alpha;
/// @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();
/// @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);
};
class Theme { class Theme {
ImGuiStyle style; ImGuiStyle style;
@ -30,10 +51,10 @@ class Theme {
static const int MaxSchemaVersion = 1; static const int MaxSchemaVersion = 1;
string file_path; string file_path;
std::map<string, ThemeStrings> strings; std::map<string, ThemeStrings> strings;
std::set<int> HueEnabledColors; std::map<int, AccentColorizer> AccentColorizers;
ThemeStrings GetStrings(); ThemeStrings GetStrings();
static bool ShowEditor(bool *open, Theme* &theme, ImGuiID dockid, int window_width, int window_height); static bool ShowEditor(bool *open, Theme* &theme, ImGuiID dockid, int window_width, int window_height);
void Apply(float hue); void Apply(ImVec4 accent);
void Save(string path); void Save(string path);
void Save(path path); void Save(path path);
Theme(); Theme();

9686
thirdparty/CLI11.hpp vendored Normal file

File diff suppressed because it is too large Load diff