From e6bb118d09a614ad4bef3c55ec9453df5ee4c888 Mon Sep 17 00:00:00 2001 From: redtide Date: Tue, 6 Apr 2021 22:49:04 +0200 Subject: [PATCH] UI: Theme selector Fixed conflict with upstream --- plugins/editor/CMakeLists.txt | 6 +- plugins/editor/layout/main.fl | 11 ++ .../editor/resources/Themes/Default/theme.xml | 34 ++++ plugins/editor/src/editor/ColorHelpers.cpp | 35 ++++ plugins/editor/src/editor/ColorHelpers.h | 3 + plugins/editor/src/editor/Editor.cpp | 107 ++++++------ plugins/editor/src/editor/Theme.cpp | 164 ++++++++++++++++++ plugins/editor/src/editor/Theme.h | 51 ++++++ plugins/editor/src/editor/layout/main.hpp | 12 +- 9 files changed, 366 insertions(+), 57 deletions(-) create mode 100644 plugins/editor/resources/Themes/Default/theme.xml create mode 100644 plugins/editor/src/editor/Theme.cpp create mode 100644 plugins/editor/src/editor/Theme.h diff --git a/plugins/editor/CMakeLists.txt b/plugins/editor/CMakeLists.txt index 62522b4e..c500e469 100644 --- a/plugins/editor/CMakeLists.txt +++ b/plugins/editor/CMakeLists.txt @@ -20,6 +20,7 @@ set(EDITOR_RESOURCES Fonts/sfizz-fluentui-system-f20.ttf Fonts/sfizz-misc-icons.ttf Fonts/Roboto-Regular.ttf + Themes/Default/theme.xml PARENT_SCOPE) function(copy_editor_resources SOURCE_DIR DESTINATION_DIR) @@ -50,6 +51,8 @@ add_library(sfizz_editor STATIC EXCLUDE_FROM_ALL src/editor/GUIPiano.cpp src/editor/DlgAbout.h src/editor/DlgAbout.cpp + src/editor/Theme.h + src/editor/Theme.cpp src/editor/ColorHelpers.h src/editor/ColorHelpers.cpp src/editor/ImageHelpers.h @@ -104,7 +107,8 @@ else() target_include_directories(sfizz_editor PRIVATE ${sfizz-gio_INCLUDE_DIRS}) target_link_libraries(sfizz_editor PRIVATE ${sfizz-gio_LIBRARIES}) endif() -target_link_libraries(sfizz_editor PRIVATE sfizz::colorspaces sfizz::stb_image sfizz::bit_array sfizz::filesystem) +target_link_libraries(sfizz_editor PRIVATE sfizz::colorspaces sfizz::stb_image + sfizz::bit_array sfizz::filesystem sfizz::pugixml) # layout tool if(NOT CMAKE_CROSSCOMPILING) diff --git a/plugins/editor/layout/main.fl b/plugins/editor/layout/main.fl index 7cbdde11..0429d773 100644 --- a/plugins/editor/layout/main.fl +++ b/plugins/editor/layout/main.fl @@ -356,6 +356,17 @@ widget_class mainView {open class ValueMenu } } + Fl_Group {} { + label Theme open + xywh {40 270 105 100} box ROUNDED_BOX labelsize 12 align 17 hide + class TitleGroup + } { + Fl_Spinner themeMenu_ { + comment {tag=kTagThemeMenu} + xywh {60 330 65 25} labelsize 12 textsize 12 + class OptionMenu + } + } } Fl_Box piano_ { xywh {5 400 790 70} labelsize 12 diff --git a/plugins/editor/resources/Themes/Default/theme.xml b/plugins/editor/resources/Themes/Default/theme.xml new file mode 100644 index 00000000..03bd2484 --- /dev/null +++ b/plugins/editor/resources/Themes/Default/theme.xml @@ -0,0 +1,34 @@ + + + #d3d7cf + + #babdb6 + #000000 + #b2b2b2 + #fd9800 + #ffffff + #2e3436 + #000000 + #fd9800 + #ffffff + #2e3436 + #00b62a + #303030 + #000000 + + + #2e3436 + #ffffff + #b2b2b2 + #fd9800 + #000000 + #babdb6 + #b2b2b2 + #fd9800 + #000000 + #9a9a9a + #00b62a + #606060 + #ffffff + + diff --git a/plugins/editor/src/editor/ColorHelpers.cpp b/plugins/editor/src/editor/ColorHelpers.cpp index 0d92ce27..5ce3314d 100644 --- a/plugins/editor/src/editor/ColorHelpers.cpp +++ b/plugins/editor/src/editor/ColorHelpers.cpp @@ -44,3 +44,38 @@ SColorHCY::SColorHCY(const SColorRGB &rgb) y = vhcy[2]; a = rgb.a; } + +static int hexDigitFromChar(char c) +{ + return (c >= '0' && c <= '9') ? (c - '0') : + (c >= 'a' && c <= 'z') ? (c - 'a' + 10) : + (c >= 'A' && c <= 'Z') ? (c - 'A' + 10) : -1; +} + +bool colorFromHex(absl::string_view hex, CColor& color) +{ + if (hex.empty() || hex[0] != '#') + return false; + + hex = hex.substr(1, hex.size()); + size_t length = hex.size(); + uint32_t rgba = 0; + if (length == 6 || length == 8) { + for (size_t i = 0; i < length; ++i) { + int d = hexDigitFromChar(hex[i]); + if (d == -1) + return false; + + rgba = (rgba << 4) | d; + } + } + if (length == 6) + rgba = (rgba << 8) | 0xff; + + color.red = rgba >> 24; + color.green = (rgba >> 16) & 0xff; + color.blue = (rgba >> 8) & 0xff; + color.alpha = rgba & 0xff; + + return true; +} diff --git a/plugins/editor/src/editor/ColorHelpers.h b/plugins/editor/src/editor/ColorHelpers.h index ca1885f8..c79501cd 100644 --- a/plugins/editor/src/editor/ColorHelpers.h +++ b/plugins/editor/src/editor/ColorHelpers.h @@ -8,6 +8,7 @@ #include "utility/vstgui_before.h" #include "vstgui/lib/ccolor.h" #include "utility/vstgui_after.h" +#include using namespace VSTGUI; @@ -33,3 +34,5 @@ struct SColorHCY { float h {}, c {}, y {}, a { 1.0 }; }; + +bool colorFromHex(absl::string_view hex, CColor& color); diff --git a/plugins/editor/src/editor/Editor.cpp b/plugins/editor/src/editor/Editor.cpp index 939b979e..eed83010 100644 --- a/plugins/editor/src/editor/Editor.cpp +++ b/plugins/editor/src/editor/Editor.cpp @@ -15,6 +15,7 @@ #include "NativeHelpers.h" #include "VSTGUIHelpers.h" #include "BitArray.h" +#include "Theme.h" #include "plugin/MessageUtils.h" #include #include @@ -32,6 +33,7 @@ #include #include #include +#include #include "utility/vstgui_before.h" #include "vstgui/vstgui.h" @@ -50,6 +52,7 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { std::string currentSfzFile_; std::string currentScalaFile_; + std::string currentThemeName_; std::string userFilesDir_; std::string fallbackFilesDir_; @@ -91,6 +94,7 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { kTagSetCCPan, kTagChooseUserFilesDir, kTagAbout, + kTagThemeMenu, kTagFirstChangePanel, kTagLastChangePanel = kTagFirstChangePanel + kNumPanels - 1, }; @@ -119,6 +123,8 @@ struct Editor::Impl : EditorController::Receiver, IControlListener { CTextLabel* keyswitchLabel_ = nullptr; CTextLabel* keyswitchInactiveLabel_ = nullptr; CTextLabel* keyswitchBadge_ = nullptr; + COptionMenu* themeMenu_ = nullptr; + std::unique_ptr theme_; STitleContainer* userFilesGroup_ = nullptr; STextButton* userFilesDirButton_ = nullptr; @@ -590,54 +596,14 @@ void Editor::Impl::createFrameContents() backgroundBitmap_ = background; { - const CColor frameBackground = { 0xd3, 0xd7, 0xcf }; - - struct Palette { - CColor boxBackground; - CColor text; - CColor inactiveText; - CColor highlightedText; - CColor titleBoxText; - CColor titleBoxBackground; - CColor icon; - CColor iconHighlight; - CColor valueText; - CColor valueBackground; - CColor knobActiveTrackColor; - CColor knobInactiveTrackColor; - CColor knobLineIndicatorColor; - }; - - Palette normalPalette; - normalPalette.boxBackground = { 0xba, 0xbd, 0xb6 }; - normalPalette.text = { 0x00, 0x00, 0x00 }; - normalPalette.inactiveText = { 0xb2, 0xb2, 0xb2 }; - normalPalette.highlightedText = { 0xfd, 0x98, 0x00 }; - normalPalette.titleBoxText = { 0xff, 0xff, 0xff }; - normalPalette.titleBoxBackground = { 0x2e, 0x34, 0x36 }; - normalPalette.icon = normalPalette.text; - normalPalette.iconHighlight = { 0xfd, 0x98, 0x00 }; - normalPalette.valueText = { 0xff, 0xff, 0xff }; - normalPalette.valueBackground = { 0x2e, 0x34, 0x36 }; - normalPalette.knobActiveTrackColor = { 0x00, 0xb6, 0x2a }; - normalPalette.knobInactiveTrackColor = { 0x30, 0x30, 0x30 }; - normalPalette.knobLineIndicatorColor = { 0x00, 0x00, 0x00 }; - Palette invertedPalette; - invertedPalette.boxBackground = { 0x2e, 0x34, 0x36 }; - invertedPalette.text = { 0xff, 0xff, 0xff }; - invertedPalette.inactiveText = { 0xb2, 0xb2, 0xb2 }; - invertedPalette.highlightedText = { 0xfd, 0x98, 0x00 }; - invertedPalette.titleBoxText = { 0x00, 0x00, 0x00 }; - invertedPalette.titleBoxBackground = { 0xba, 0xbd, 0xb6 }; - invertedPalette.icon = { 0xb2, 0xb2, 0xb2 }; - invertedPalette.iconHighlight = { 0xfd, 0x98, 0x00 }; - invertedPalette.valueText = { 0x00, 0x00, 0x00 }; - invertedPalette.valueBackground = { 0x9a, 0x9a, 0x9a }; - invertedPalette.knobActiveTrackColor = { 0x00, 0xb6, 0x2a }; - invertedPalette.knobInactiveTrackColor = { 0x60, 0x60, 0x60 }; - invertedPalette.knobLineIndicatorColor = { 0xff, 0xff, 0xff }; - Palette& defaultPalette = normalPalette; + // Try to load the Default theme from disk or hardcoded one as fallback + Theme* theme = new Theme; + theme_.reset(theme); + currentThemeName_ = theme->loadCurrentName(); + theme->load(currentThemeName_); + Palette& invertedPalette = theme->invertedPalette; + Palette& defaultPalette = theme->normalPalette; Palette* palette = &defaultPalette; auto enterPalette = [&palette](Palette& p) { palette = &p; }; @@ -697,9 +663,9 @@ void Editor::Impl::createFrameContents() }; auto createStyledKnob = [this, &palette](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int) { SStyledKnob* knob = new SStyledKnob(bounds, this, tag); - knob->setActiveTrackColor(palette->knobActiveTrackColor); - knob->setInactiveTrackColor(palette->knobInactiveTrackColor); - knob->setLineIndicatorColor(palette->knobLineIndicatorColor); + knob->setActiveTrackColor(palette->knobActiveTrack); + knob->setInactiveTrackColor(palette->knobInactiveTrack); + knob->setLineIndicatorColor(palette->knobLineIndicator); return knob; }; auto createValueLabel = [&palette](const CRect& bounds, int, const char* label, CHoriTxtAlign align, int fontsize) { @@ -781,6 +747,18 @@ void Editor::Impl::createFrameContents() vm->setRoundRectRadius(5.0); return vm; }; + auto createOptionMenu = [this, &palette](const CRect& bounds, int tag, const char*, CHoriTxtAlign align, int fontsize) { + auto* cb = new COptionMenu(bounds, this, tag); + cb->setHoriAlign(align); + auto font = makeOwned("Roboto", fontsize); + cb->setFont(font); + cb->setFontColor(palette->valueText); + cb->setBackColor(palette->valueBackground); + cb->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + cb->setStyle(CParamDisplay::kRoundRectStyle); + cb->setRoundRectRadius(5.0); + return cb; + }; auto createGlyphButton = [this, &palette](UTF8StringPtr glyph, const CRect& bounds, int tag, int fontsize) { STextButton* btn = new STextButton(bounds, this, tag, glyph); btn->setFont(makeOwned("Sfizz Fluent System F20", fontsize)); @@ -821,10 +799,12 @@ void Editor::Impl::createFrameContents() btn->setFont(makeOwned("Sfizz Fluent System F20", fontsize)); return btn; }; - auto createPiano = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int fontsize) { + auto createPiano = [&palette](const CRect& bounds, int, const char*, CHoriTxtAlign, int fontsize) { SPiano* piano = new SPiano(bounds); auto font = makeOwned("Roboto", fontsize); piano->setFont(font); + piano->setFontColor(palette->text); + piano->setBackColor(palette->boxBackground); return piano; }; auto createChevronDropDown = [this, &palette](const CRect& bounds, int, const char*, CHoriTxtAlign, int fontsize) { @@ -858,7 +838,7 @@ void Editor::Impl::createFrameContents() box->setNameLabelFontColor(palette->text); box->setKnobFont(font); box->setKnobFontColor(palette->text); - box->setKnobLineIndicatorColor(palette->knobLineIndicatorColor); + box->setKnobLineIndicatorColor(palette->knobLineIndicator); box->setValueToStringFunction([](float value, std::string& text) -> bool { text = std::to_string(std::lround(value * 127)); return true; @@ -877,7 +857,7 @@ void Editor::Impl::createFrameContents() #include "layout/main.hpp" - mainView->setBackgroundColor(frameBackground); + mainView->setBackgroundColor(theme->frameBackground); #if LINUX if (!isZenityAvailable()) { @@ -1111,6 +1091,19 @@ void Editor::Impl::createFrameContents() } applyBackgroundForCurrentPanel(); + + if (COptionMenu* menu = themeMenu_) { + const std::vector& names = Theme::getAvailableNames(); + size_t index = ~size_t(0); + for (size_t i = 0, n = names.size(); i < n; ++i) { + const std::string& name = names[i]; + menu->addEntry(UTF8String(name)); + if (name == currentThemeName_) + index = i; + } + if (index != ~size_t(0)) + menu->setCurrent(index); + } } void Editor::Impl::chooseSfzFile() @@ -1847,6 +1840,14 @@ void Editor::Impl::valueChanged(CControl* ctl) Call::later([this]() { aboutDialog_->setVisible(true); }); break; + case kTagThemeMenu: + { + currentThemeName_ = Theme::getAvailableNames()[int(value)]; + Theme::storeCurrentName(currentThemeName_); + // TODO: live reload theme + } + break; + default: if (tag >= kTagFirstChangePanel && tag <= kTagLastChangePanel) { int panelId = tag - kTagFirstChangePanel; diff --git a/plugins/editor/src/editor/Theme.cpp b/plugins/editor/src/editor/Theme.cpp new file mode 100644 index 00000000..44057862 --- /dev/null +++ b/plugins/editor/src/editor/Theme.cpp @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "Theme.h" +#include "ColorHelpers.h" +#include "VSTGUIHelpers.h" +#include "plugin/SfizzSettings.h" +#include "sfizz/utility/StringViewHelpers.h" +#include +#include +#include +#include + +void Theme::clear() { + frameBackground = {}; + normalPalette = {}; + invertedPalette = {}; +} + +void Theme::load(const std::string& name) +{ + fs::path resPath = getResourceBasePath(); + fs::path themePath = resPath / "Themes" / fs::u8path(name) / "theme.xml"; + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(themePath.c_str()); + if (!result) { + std::cerr << "[sfizz] cannot load theme from " << resPath << '\n'; + return; + } + + loadDocument(doc); +} + +void Theme::loadDocument(const pugi::xml_document& doc) +{ + pugi::xml_node rootNode(doc.child("sfizz-theme")); + if (!rootNode) { + std::cerr << "[sfizz] trying to load an invalid theme\n"; + return; + } + + /// + auto loadChildColorNodes = [this](pugi::xml_node topNode, bool inverted) { + for (pugi::xml_node colorNode : topNode.children("color")) { + absl::string_view name = colorNode.attribute("name").as_string(); + CColor* slot = getColorFromName(name, inverted); + if (!slot) { + std::cerr << "[sfizz] color not recognized: " << name << "\n"; + continue; + } + + *slot = {}; + + absl::string_view colorText = colorNode.text().as_string(); + if (!colorFromHex(colorText, *slot)) + std::cerr << "[sfizz] invalid color value: " << colorText << "\n"; + } + }; + + /// + loadChildColorNodes(rootNode, false); + + for (pugi::xml_node paletteNode : rootNode.children("palette")) { + absl::string_view paletteName = paletteNode.attribute("name").as_string(); + + bool inverted; + if (paletteName == "normal") + inverted = false; + else if (paletteName == "inverted") + inverted = true; + else { + std::cerr << "[sfizz] palette not recognized: " << paletteName << "\n"; + continue; + } + + loadChildColorNodes(paletteNode, inverted); + } +} + +void Theme::storeCurrentName(absl::string_view name) +{ + SfizzSettings settings; + settings.store("current_theme", name); +} + +std::string Theme::loadCurrentName() +{ + SfizzSettings settings; + return settings.load_or("current_theme", "Default"); +} + +const std::vector& Theme::getAvailableNames() +{ + static const std::vector names = extractAvailableNames(); + return names; +} + +std::vector Theme::extractAvailableNames() +{ + fs::path themesPath = getResourceBasePath() / "Themes"; + + std::error_code ec; + fs::directory_iterator it(themesPath, ec); + if (ec) { + std::cerr << "[sfizz] error reading the theme directory: " << ec.message() << '\n'; + return {}; + } + + std::vector names; + for (; !ec && it != fs::directory_iterator(); it.increment(ec)) { + const fs::directory_entry& entry = *it; + if (entry.is_directory()) + names.emplace_back(entry.path().filename().u8string()); + } + + std::sort( + names.begin(), names.end(), + [](const std::string& a, const std::string& b) -> bool { + return (a == "Default") ? true : (b == "Default") ? false : a < b; + }); + + return names; +} + +CColor* Theme::getColorFromName(absl::string_view name, bool fromInvertedPalette) +{ + CColor* c = nullptr; + switch (hash(name)) { + #define COLOR_CASE(X) \ + case hash(#X): \ + c = &X; \ + break + #define PALETTE_COLOR_CASE(X) \ + case hash(#X): \ + c = fromInvertedPalette ? \ + &invertedPalette.X : &normalPalette.X; \ + break + + COLOR_CASE(frameBackground); + PALETTE_COLOR_CASE(boxBackground); + PALETTE_COLOR_CASE(highlightedText); + PALETTE_COLOR_CASE(icon); + PALETTE_COLOR_CASE(iconHighlight); + PALETTE_COLOR_CASE(inactiveText); + PALETTE_COLOR_CASE(knobActiveTrack); + PALETTE_COLOR_CASE(knobInactiveTrack); + PALETTE_COLOR_CASE(knobLineIndicator); + PALETTE_COLOR_CASE(text); + PALETTE_COLOR_CASE(titleBoxBackground); + PALETTE_COLOR_CASE(titleBoxText); + PALETTE_COLOR_CASE(valueBackground); + PALETTE_COLOR_CASE(valueText); + + #undef COLOR_CASE + #undef PALETTE_COLOR_CASE + + default: break; + } + return c; +} diff --git a/plugins/editor/src/editor/Theme.h b/plugins/editor/src/editor/Theme.h new file mode 100644 index 00000000..b4693833 --- /dev/null +++ b/plugins/editor/src/editor/Theme.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "utility/vstgui_before.h" +#include "vstgui/vstgui.h" +#include "utility/vstgui_after.h" +#include +#include +#include +namespace pugi { class xml_document; } + +using namespace VSTGUI; + +struct Palette { + CColor boxBackground; + CColor text; + CColor inactiveText; + CColor highlightedText; + CColor titleBoxText; + CColor titleBoxBackground; + CColor icon; + CColor iconHighlight; + CColor valueText; + CColor valueBackground; + CColor knobActiveTrack; + CColor knobInactiveTrack; + CColor knobLineIndicator; +}; + +struct Theme { + CColor frameBackground; + Palette normalPalette {}; + Palette invertedPalette {}; + + void clear(); + void load(const std::string& name); + void loadDocument(const pugi::xml_document& doc); + + static void storeCurrentName(absl::string_view name); + static std::string loadCurrentName(); + static const std::vector& getAvailableNames(); + + CColor* getColorFromName(absl::string_view name, bool fromInvertedPalette); + +private: + static std::vector extractAvailableNames(); +}; diff --git a/plugins/editor/src/editor/layout/main.hpp b/plugins/editor/src/editor/layout/main.hpp index e7234625..ea9dfa3f 100644 --- a/plugins/editor/src/editor/layout/main.hpp +++ b/plugins/editor/src/editor/layout/main.hpp @@ -181,6 +181,12 @@ view__66->addView(view__69); auto* const view__70 = createValueMenu(CRect(100, 60, 180, 85), kTagSetOscillatorQuality, "", kCenterText, 12); oscillatorQualitySlider_ = view__70; view__66->addView(view__70); -auto* const view__71 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12); -piano_ = view__71; -view__0->addView(view__71); +auto* const view__71 = createTitleGroup(CRect(35, 161, 140, 261), -1, "Theme", kCenterText, 12); +view__46->addView(view__71); +view__71->setVisible(false); +auto* const view__72 = createOptionMenu(CRect(20, 60, 85, 85), kTagThemeMenu, "", kCenterText, 12); +themeMenu_ = view__72; +view__71->addView(view__72); +auto* const view__73 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12); +piano_ = view__73; +view__0->addView(view__73);