723 lines
36 KiB
C++
723 lines
36 KiB
C++
#include "theme.h"
|
|
#include "imgui.h"
|
|
#include "json/value.h"
|
|
#include "thirdparty/toml.hpp"
|
|
#include "translation.h"
|
|
#include <cmath>
|
|
#include <exception>
|
|
#include <numbers>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <filesystem>
|
|
#include <functional>
|
|
#include "IconsForkAwesome.h"
|
|
#include "imgui_stdlib.h"
|
|
#include <log.hpp>
|
|
|
|
using namespace std::filesystem;
|
|
using namespace std::numbers;
|
|
const char* Theme::prefPath = NULL;
|
|
path Theme::themeDir = path();
|
|
std::set<path> Theme::availableThemes = std::set<path>();
|
|
std::map<path, ThemeStrings> Theme::themeStrings = std::map<path, ThemeStrings>();
|
|
|
|
ImVec4 change_accent_color(ImVec4 in, float hue) {
|
|
if (in.x == in.y && in.y == in.z) {
|
|
return in;
|
|
}
|
|
ImVec4 hsv = in;
|
|
ImVec4 out = in;
|
|
ImGui::ColorConvertRGBtoHSV(in.x, in.y, in.z, hsv.x, hsv.y, hsv.z);
|
|
hsv.x = hue / 360.0f;
|
|
ImGui::ColorConvertHSVtoRGB(hsv.x, hsv.y, hsv.z, out.x, out.y, out.z);
|
|
return out;
|
|
}
|
|
bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_width, int window_height) {
|
|
static FileBrowser importDialog(false, ImGuiFileBrowserFlags_NoTitleBar|ImGuiFileBrowserFlags_NoMove|ImGuiFileBrowserFlags_NoResize);
|
|
static FileBrowser exportDialog(true, ImGuiFileBrowserFlags_NoTitleBar|ImGuiFileBrowserFlags_NoMove|ImGuiFileBrowserFlags_NoResize);
|
|
static bool loadOpen = false;
|
|
static bool saveAsOpen = false;
|
|
ImGui::SetNextWindowDockID(dockid);
|
|
ImGui::Begin(_TRI_CTX(ICON_FK_MAGIC, "Window title", "Theme Editor"), open);
|
|
ImGuiStyle& style = theme->style;
|
|
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f);
|
|
ImVec2 buttonSize = ImVec2((ImGui::GetWindowWidth() * 0.50f) - (ImGui::GetStyle().WindowPadding.x) - (ImGui::GetStyle().ItemSpacing.x * 0.5f), 0);
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | preset button", "Create light"), buttonSize)) {
|
|
delete theme;
|
|
theme = new Theme(false);
|
|
|
|
ImGui::PopItemWidth();
|
|
ImGui::End();
|
|
return true;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | preset button", "Create dark"), buttonSize)) {
|
|
delete theme;
|
|
theme = new Theme(true);
|
|
|
|
ImGui::PopItemWidth();
|
|
ImGui::End();
|
|
return true;
|
|
}
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | import button. Opens the theme import file dialog", "Import..."), buttonSize)) {
|
|
importDialog.SetTitle(_TR_CTX("Theme Editor file dialog title", "Import theme..."));
|
|
importDialog.SetTypeFilters(_TR_CTX("Theme Editor file dialog filter name", "Theme JSON files"), { ".toml"});
|
|
std::string userdir = std::getenv(
|
|
#ifdef _WIN32
|
|
"UserProfile"
|
|
#else
|
|
"HOME"
|
|
#endif
|
|
);
|
|
importDialog.SetPwd(userdir);
|
|
importDialog.Open();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | export button. Opens the theme export file dialog", "Export..."), buttonSize)) {
|
|
exportDialog.SetTitle(_TR_CTX("Theme Editor file dialog title", "Export theme..."));
|
|
exportDialog.SetTypeFilters(_TR_CTX("Theme Editor file dialog filter name", "Theme JSON files"), { ".toml"});
|
|
std::string userdir = std::getenv(
|
|
#ifdef _WIN32
|
|
"UserProfile"
|
|
#else
|
|
"HOME"
|
|
#endif
|
|
);
|
|
exportDialog.SetPwd(userdir);
|
|
exportDialog.Open();
|
|
}
|
|
if (importDialog.IsOpened()) {
|
|
importDialog.SetWindowSize(window_width, window_height);
|
|
importDialog.SetWindowPos(0, 0);
|
|
}
|
|
if (exportDialog.IsOpened()) {
|
|
exportDialog.SetWindowSize(window_width, window_height);
|
|
exportDialog.SetWindowPos(0, 0);
|
|
}
|
|
importDialog.Display();
|
|
exportDialog.Display();
|
|
if (!theme->file_path.empty()) {
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | button. Reverts to saved file.", "Revert"), buttonSize)) {
|
|
string file_path_backup = theme->file_path;
|
|
delete theme;
|
|
theme = new Theme(file_path_backup);
|
|
ImGui::PopItemWidth();
|
|
ImGui::End();
|
|
return true;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | button. Saves the theme to it's current location. Not shown when it doesn't already have one.", "Save"), buttonSize)) {
|
|
theme->Save(theme->file_path);
|
|
}
|
|
}
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | button. Opens the theme loading dialog for themes created or imported by the user.", "Load..."), buttonSize)) {
|
|
loadOpen = true;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | button. Opens the theme saving dialog for themes created or imported by the user.", "Save as..."), buttonSize)) {
|
|
saveAsOpen = true;
|
|
}
|
|
|
|
// Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f)
|
|
if (ImGui::SliderFloat(_TR_CTX("Theme Editor | slider. Simplified frame rounding.", "Frame Rounding"), &style.FrameRounding, 0.0f, 12.0f, "%.0f"))
|
|
style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding
|
|
{ bool border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox(_TR_CTX("Theme Editor | checkbox", "Window Border"), &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } }
|
|
ImGui::SameLine();
|
|
{ bool border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox(_TR_CTX("Theme Editor | checkbox", "Frame Border"), &border)) { style.FrameBorderSize = border ? 1.0f : 0.0f; } }
|
|
ImGui::SameLine();
|
|
{ bool border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox(_TR_CTX("Theme Editor | checkbox", "Popup Border"), &border)) { style.PopupBorderSize = border ? 1.0f : 0.0f; } }
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None))
|
|
{
|
|
if (ImGui::BeginTabItem(_TR_CTX("Theme Editor | Tab label", "Strings")))
|
|
{
|
|
ImGui::InputText(_TR_CTX("Theme Editor | Strings | Name input label", "Name"), &theme->strings["fallback"].name);
|
|
ImGui::InputText(_TR_CTX("Theme Editor | Strings | Description input label", "Description"), &theme->strings["fallback"].description);
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem(_TR_CTX("Theme Editor | Tab label", "Sizes")))
|
|
{
|
|
ImGui::SeparatorText(_TR_CTX("Theme Editor | Sizes | Section label", "Sizing"));
|
|
ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Window Padding"), (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f");
|
|
ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Frame Padding"), (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f");
|
|
ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Cell Padding"), (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f");
|
|
ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Item Spacing"), (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f");
|
|
ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Inner Item Spacing"), (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f");
|
|
//ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f");
|
|
//ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Sizing | slider label", "IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Sizing | slider label", "Scrollbar Size"), &style.ScrollbarSize, 1.0f, 20.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Sizing | slider label", "Minimum Grabber Size"), &style.GrabMinSize, 1.0f, 20.0f, "%.0f");
|
|
ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Separator Text Padding"), (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%.0f");
|
|
|
|
ImGui::SeparatorText(_TR_CTX("Theme Editor | Sizes | Section label", "Borders"));
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Window Border Size"), &style.WindowBorderSize, 0.0f, 1.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Child Border Size"), &style.ChildBorderSize, 0.0f, 1.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Popup Border Size"), &style.PopupBorderSize, 0.0f, 1.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Frame Border Size"), &style.FrameBorderSize, 0.0f, 1.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Tab Border Size"), &style.TabBorderSize, 0.0f, 1.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Separator Text Border Size"), &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f");
|
|
|
|
ImGui::SeparatorText(_TR_CTX("Theme Editor | Sizes | Section label", "Rounding"));
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Window Rounding"), &style.WindowRounding, 0.0f, 12.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Child Rounding"), &style.ChildRounding, 0.0f, 12.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Frame Rounding"), &style.FrameRounding, 0.0f, 12.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Popup Rounding"), &style.PopupRounding, 0.0f, 12.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Scrollbar Rounding"), &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Grabber Rounding"), &style.GrabRounding, 0.0f, 12.0f, "%.0f");
|
|
ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Tab Rounding"), &style.TabRounding, 0.0f, 12.0f, "%.0f");
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
if (ImGui::BeginTabItem(_TR_CTX("Theme Editor | Tab label", "Colors")))
|
|
{
|
|
|
|
static ImGuiTextFilter filter;
|
|
filter.Draw(_TR_CTX("Theme Editor | Colors | text filter", "Filter colors"), ImGui::GetFontSize() * 16);
|
|
|
|
static ImGuiColorEditFlags alpha_flags = 0;
|
|
if (ImGui::RadioButton(_TR_CTX("Theme Editor | Colors | preview settings radio button", "Opaque"), alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine();
|
|
if (ImGui::RadioButton(_TR_CTX("Theme Editor | Colors | preview settings radio button", "Alpha"), alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { alpha_flags = ImGuiColorEditFlags_AlphaPreview; } ImGui::SameLine();
|
|
if (ImGui::RadioButton(_TR_CTX("Theme Editor | Colors | preview settings radio button", "Both"), alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; }
|
|
|
|
ImGui::BeginChild("##colors", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened);
|
|
ImGui::PushItemWidth(-160);
|
|
for (int i = 0; i < ImGuiCol_COUNT; i++)
|
|
{
|
|
const char* name = ImGui::GetStyleColorName(i);
|
|
if (!filter.PassFilter(name))
|
|
continue;
|
|
ImGui::PushID(i);
|
|
AccentColorizer &colorizer = theme->AccentColorizers[i];
|
|
ImGui::Text(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring label", "Match accent color preference: "));
|
|
ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Hue", "H: "), &colorizer.Hue); ImGui::SameLine();
|
|
ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Saturation", "S: "), &colorizer.Saturation); ImGui::SameLine();
|
|
ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Value", "V: "), &colorizer.Value); ImGui::SameLine();
|
|
ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Alpha (opacity)", "A: "), &colorizer.Alpha); ImGui::SameLine();
|
|
ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags | ImGuiColorEditFlags_DisplayHSV);
|
|
ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);
|
|
ImGui::TextUnformatted(name);
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::PopItemWidth();
|
|
ImGui::EndChild();
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
ImGui::EndTabBar();
|
|
}
|
|
|
|
ImGui::PopItemWidth();
|
|
ImGui::End();
|
|
if (importDialog.HasSelected()) {
|
|
path selected_path = importDialog.GetSelected();
|
|
path filename = selected_path.filename();
|
|
copy_file(selected_path, theme->themeDir / filename);
|
|
Theme::updateAvailableThemes();
|
|
importDialog.ClearSelected();
|
|
}
|
|
if (exportDialog.HasSelected()) {
|
|
path selected_path = exportDialog.GetSelected();
|
|
theme->Save(selected_path);
|
|
exportDialog.ClearSelected();
|
|
}
|
|
if (loadOpen) {
|
|
ImGui::OpenPopup(_TR_CTX("Theme Editor | Custom modal dialog title", "Load..."));
|
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
|
ImGui::SetNextWindowSize(ImVec2(window_width, window_height));
|
|
if (ImGui::BeginPopupModal(_TR_CTX("Theme Editor | Custom modal dialog title", "Load..."), nullptr, ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize)) {
|
|
static path selectedThemePath;
|
|
static string filter = "";
|
|
ImGui::Text(_TR_CTX("Theme Editor | Load dialog | Theme selector | filter label", "Filter:")); ImGui::SameLine();
|
|
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - ImGui::GetStyle().WindowPadding.x);
|
|
ImGui::InputText("##FilterInput", &filter);
|
|
ImGui::Text(_TR_CTX("Theme Editor | Load dialog | Theme selector | label", "Available themes..."));
|
|
if (ImGui::BeginListBox("##Themes", ImVec2(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f), -ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().WindowPadding.y))) {
|
|
for (auto themePath : Theme::availableThemes) {
|
|
string themeStem = themePath.stem().string();
|
|
if (themeStem.starts_with(filter)) {
|
|
const bool is_selected = themePath == selectedThemePath;
|
|
if (ImGui::Selectable((theme->themeStrings[themePath].name + string(" (") + string(themeStem) + string(")")).c_str(), is_selected)) {
|
|
selectedThemePath = themePath;
|
|
}
|
|
if (is_selected) {
|
|
ImGui::SetItemDefaultFocus();
|
|
}
|
|
}
|
|
}
|
|
ImGui::EndListBox();
|
|
}
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | Load dialog | Load button", "Load"))) {
|
|
if (!selectedThemePath.empty()) {
|
|
filter = "";
|
|
loadOpen = false;
|
|
delete theme;
|
|
theme = new Theme(selectedThemePath);
|
|
selectedThemePath = path();
|
|
ImGui::EndPopup();
|
|
return true;
|
|
}
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | Load dialog | Cancel button", "Cancel"))) {
|
|
selectedThemePath = path();
|
|
filter = "";
|
|
loadOpen = false;
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
if (saveAsOpen) {
|
|
ImGui::OpenPopup(_TR_CTX("Theme Editor | Custom modal dialog title", "Save as..."));
|
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
|
ImGui::SetNextWindowSize(ImVec2(window_width, window_height));
|
|
if (ImGui::BeginPopupModal(_TR_CTX("Theme Editor | Custom modal dialog title", "Save as..."), nullptr, ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize)) {
|
|
static string selectedThemeName = "";
|
|
static string filter = "";
|
|
ImGui::Text(_TR_CTX("Theme Editor | Save as dialog | Theme selector | filter label", "Filter:")); ImGui::SameLine();
|
|
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - ImGui::GetStyle().WindowPadding.x);
|
|
ImGui::InputText("##FilterInput", &filter, 1024);
|
|
ImGui::Text(_TR_CTX("Theme Editor | Save as dialog | Theme selector | label", "Available themes..."));
|
|
if (ImGui::BeginListBox("##Themes", ImVec2(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f), -ImGui::GetFrameHeightWithSpacing() - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().WindowPadding.y))) {
|
|
for (auto themePath : Theme::availableThemes) {
|
|
string themeStem = themePath.stem().string();
|
|
if (themeStem.starts_with(filter)) {
|
|
const bool is_selected = themeStem == selectedThemeName;
|
|
if (ImGui::Selectable((theme->themeStrings[themePath].name + string(" (") + string(themeStem) + string(")")).c_str(), is_selected)) {
|
|
selectedThemeName = themeStem;
|
|
}
|
|
if (is_selected) {
|
|
ImGui::SetItemDefaultFocus();
|
|
}
|
|
}
|
|
}
|
|
ImGui::EndListBox();
|
|
}
|
|
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f));
|
|
ImGui::InputText(_TR_CTX("Theme Editor | Save as dialog | Theme name input label", "Theme name: "), &selectedThemeName);
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | Save as dialog | Save button", "Save"))) {
|
|
path selectedThemePath(selectedThemeName);
|
|
if (!selectedThemePath.empty() && !selectedThemePath.is_absolute()) {
|
|
selectedThemeName = "";
|
|
filter = "";
|
|
saveAsOpen = false;
|
|
theme->Save(Theme::themeDir / selectedThemePath.replace_extension(".toml"));
|
|
theme->file_path = selectedThemePath.generic_string();
|
|
}
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button(_TR_CTX("Theme Editor | Save as dialog | Cancel button", "Cancel"))) {
|
|
selectedThemeName = "";
|
|
filter = "";
|
|
saveAsOpen = false;
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
AccentColorizer::AccentColorizer() {
|
|
Hue = false;
|
|
Saturation = false;
|
|
Value = false;
|
|
Alpha = false;
|
|
}
|
|
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();
|
|
}
|
|
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) {
|
|
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();
|
|
actual_style = style;
|
|
for (int i = 0; i < ImGuiCol_COUNT; i++)
|
|
{
|
|
AccentColorizers[i].Colorize(accent, actual_style.Colors[i]);
|
|
}
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
|
{
|
|
actual_style.WindowRounding = 0.0f;
|
|
actual_style.Colors[ImGuiCol_WindowBg].w = 1.0f;
|
|
}
|
|
}
|
|
void Theme::Save(string path) {
|
|
INFO.writefln("Saving theme to %s...", path.c_str());
|
|
{
|
|
toml::table config;
|
|
std::ofstream stream;
|
|
stream.open(path);
|
|
{
|
|
toml::table metadata;
|
|
metadata.insert("SchemaVersion", 3);
|
|
{
|
|
toml::table stringsList;
|
|
for (auto kv : strings) {
|
|
toml::table stringsEntryJson;
|
|
string language = kv.first;
|
|
ThemeStrings stringsEntry = kv.second;
|
|
stringsEntryJson.insert("name", stringsEntry.name);
|
|
stringsEntryJson.insert("desc", stringsEntry.description);
|
|
stringsList.insert(language, stringsEntryJson);
|
|
}
|
|
metadata.insert("Strings", stringsList);
|
|
}
|
|
config.insert("meta", metadata);
|
|
}
|
|
{
|
|
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);
|
|
}
|
|
{
|
|
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);
|
|
}
|
|
{
|
|
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);
|
|
}
|
|
{
|
|
toml::table colors;
|
|
for (int i = 0; i < ImGuiCol_COUNT; i++)
|
|
{
|
|
const char* name = ImGui::GetStyleColorName(i);
|
|
ImVec4 color = style.Colors[i];
|
|
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.insert("colors", colors);
|
|
}
|
|
stream << config;
|
|
stream.close();
|
|
}
|
|
updateAvailableThemes();
|
|
}
|
|
void Theme::Save(path path) {
|
|
Save((string)path.string());
|
|
}
|
|
void Theme::updateAvailableThemes() {
|
|
themeDir = path(prefPath) / path("themes");
|
|
create_directories(themeDir);
|
|
availableThemes.clear();
|
|
themeStrings.clear();
|
|
for (auto const& dir_entry : directory_iterator(themeDir)) {
|
|
if (dir_entry.is_regular_file()) {
|
|
if (dir_entry.path().extension().string() == ".json") {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Theme::Theme() {
|
|
if (prefPath == NULL) {
|
|
throw std::exception();
|
|
}
|
|
updateAvailableThemes();
|
|
strings["fallback"] = ThemeStrings();
|
|
}
|
|
|
|
Theme::Theme(bool dark) : Theme() {
|
|
if (dark) {
|
|
ImGui::StyleColorsDark(&style);
|
|
} else {
|
|
ImGui::StyleColorsLight(&style);
|
|
style.FrameBorderSize = 1;
|
|
}
|
|
for (int i = 0; i < ImGuiCol_COUNT; i++)
|
|
{
|
|
ImVec4 color = style.Colors[i];
|
|
auto colorizer = AccentColorizer();
|
|
if (color.x != color.y || color.y != color.z) {
|
|
colorizer.Hue = true;
|
|
colorizer.Saturation = true;
|
|
colorizer.Alpha = true;
|
|
}
|
|
AccentColorizers[i] = colorizer;
|
|
}
|
|
style.FrameRounding = 12;
|
|
style.GrabRounding = 12;
|
|
style.WindowRounding = 12;
|
|
}
|
|
ThemeStrings::ThemeStrings() {
|
|
name = _TRS_CTX("Theme default strings | name", "A theme");
|
|
description = _TRS_CTX("Theme default strings | description", "(No description)");
|
|
}
|
|
ThemeStrings Theme::GetStrings() {
|
|
char *language_c = CURRENT_LANGUAGE;
|
|
string language = language_c;
|
|
if (strings.contains(language)) {
|
|
return strings[language];
|
|
} else {
|
|
if (!strings.contains("fallback")) {
|
|
strings["fallback"] = ThemeStrings();
|
|
}
|
|
return strings["fallback"];
|
|
}
|
|
}
|
|
ThemeStrings::ThemeStrings(toml::table config) : ThemeStrings() {
|
|
char *language_c = CURRENT_LANGUAGE;
|
|
string language = language_c;
|
|
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();
|
|
if (stringsList.contains(language)) {
|
|
toml::table stringsEntryJson = *stringsList[language].as_table();
|
|
if (stringsEntryJson.contains("name")) {
|
|
name = **stringsEntryJson["name"].as_string();
|
|
}
|
|
if (stringsEntryJson.contains("desc")) {
|
|
description = **stringsEntryJson["desc"].as_string();
|
|
}
|
|
} else if (metadata.contains("fallback")) {
|
|
toml::table stringsEntryJson = *stringsList["fallback"].as_table();
|
|
if (stringsEntryJson.contains("name")) {
|
|
name = **stringsEntryJson["name"].as_string();
|
|
}
|
|
if (stringsEntryJson.contains("desc")) {
|
|
description = **stringsEntryJson["desc"].as_string();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string Theme::Migrate(std::string path) {
|
|
if (path.ends_with(".json")) {
|
|
INFO.writefln("Migrating theme file '%s'...", path.c_str());
|
|
std::ifstream stream(path);
|
|
Json::Value config;
|
|
stream >> config;
|
|
toml::table newConfig;
|
|
if (config.isMember("meta")) {
|
|
Json::Value metadata = config["meta"];
|
|
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];
|
|
toml::table newStringsEntry;
|
|
if (stringsEntryJson.isMember("Name")) {
|
|
string value = stringsEntryJson["Name"].asString();
|
|
newStringsEntry.insert("name", value);
|
|
}
|
|
if (stringsEntryJson.isMember("Description")) {
|
|
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);
|
|
INFO.writefln("Loading theme '%s'...", path.c_str());
|
|
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.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.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.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.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.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;
|
|
}
|
|
}
|
|
}
|
|
file_path = path;
|
|
}
|
|
Theme::Theme(path path) : Theme(path.string()) {
|
|
|
|
}
|