diff --git a/Documentation/DexedTheme.md b/Documentation/DexedTheme.md index 427dd102..5dcf714c 100644 --- a/Documentation/DexedTheme.md +++ b/Documentation/DexedTheme.md @@ -1,11 +1,14 @@ Dexed Theme =========== +Last update: 26 July 2024 + Dexed UI can be themed if you want a different colour combination or background images. -You need need to create a file named "DexedTheme.xml" and it must be placed in the same directory where "Dexed.xml" is created. On Windows, this is C:\Users\\\AppData\Roaming\DigitalSuburban" and on Mac it is "~/Library/Application Support/DigitalSuburban" +You need need to create a file named "DexedTheme.xml" and it must be placed in the same directory where "Dexed.xml" is created. On Windows, this is "C:\Users\<your user name>\AppData\Roaming\DigitalSuburban\Dexed" and on Mac it is "~/Library/Application Support/DigitalSuburban/Dexed" (FIXME!), and in Linux it is "~/.local/share/DigitalSuburban/Dexed". Colour id are analogous to JUCE color ID; see [DXLookNFeel() class](../Source/DXLookNFeel.cpp) for a complete list a defined colours id. Colour value is the hexa decimal values (from 0 to 0xFF) for ALPHA RED BLUE GREEN. +For ALPHA, 0x00 is completely transparent, 0xFF is opaque. Known colour keys; simply override those you want different. @@ -28,18 +31,47 @@ PopupMenu::backgroundColourId PopupMenu::textColourId PopupMenu::highlightedTextColourId PopupMenu::highlightedBackgroundColourId +TreeView::backgroundColourId +DirectoryContentsDisplayComponent::highlightColourId +DirectoryContentsDisplayComponent::textColourId +ScrollBar::thumbColourId Dexed::backgroundId Dexed::fillColourId ``` Image id are the file name defined in [ui folder](../Resources/ui). If it cannot find the file, the image will no longer be rendered. The image path is relative to the path where "DexedTheme.xml" is defined. +Note that sizes in pixels and structures of the images should be same as original ones, because the layout of GUI based on these hardcoded fix sizes. Recently, the following image ids could be specified: + +Knob_68x68.png +Switch_96x52.png +SwitchLighted_48x26.png +Switch_64x64.png +ButtonUnlabeled_50x30.png +Slider_52x52.png +Scaling_36_26.png +Light_28x28.png +LFO_36_26.png +OperatorEditor_574x436.png +GlobalEditor_1728x288.png + -Example configuration ---------------------- +Example ``DexedTheme.xml`` configuration +---------------------------------------- ```xml - - + + + + + + -``` \ No newline at end of file +``` + +As the result: +- the colors of the background of popup menus, the text of normal menu items, and the text and background of highlighted menu items are changed to opaque blue, white, yellow, and cyan, +- the color of thumbnail of scrollbars is changed to opaque lightgray, +- the color of bright LEDs are changed to green from red (if there is a folder named ``myTheme`` within the subdirectory where your ``Dexed.xml`` and ``DexedTheme.xml`` files are located, and there is the file ``GreenLight_28x28.png`` containing the image of a red LED inside that ``myTheme`` subdirectory). + +Note that ``DexedTheme.xml``are loaded only once during the initialization when Dexed (either the Standalone or the plugin version) is launched. \ No newline at end of file diff --git a/Documentation/GreenLight_28x28.png b/Documentation/GreenLight_28x28.png new file mode 100644 index 00000000..07d305b6 Binary files /dev/null and b/Documentation/GreenLight_28x28.png differ diff --git a/Source/DXLookNFeel.cpp b/Source/DXLookNFeel.cpp index 85d70bd2..88bfa2a6 100644 --- a/Source/DXLookNFeel.cpp +++ b/Source/DXLookNFeel.cpp @@ -18,6 +18,9 @@ * */ +#include +#include + #include "DXLookNFeel.h" #include "DXComponents.h" #include "Dexed.h" @@ -29,17 +32,24 @@ Image findImage(String path) { Image img; if ( path.length() <= 3 ) return img; - File imgFile(path); + //File imgFile(path); // it might cause an assertion in juce_File.cpp + File imgFile = File::getCurrentWorkingDirectory().getChildFile(path); img = ImageCache::getFromFile(imgFile); + if (img.isNull()) { + TRACE("img.isNull() == true, path=%s", path.toRawUTF8()); + } return img; } DXLookNFeel::DXLookNFeel() { Colour ctrlBackground; - + DexedAudioProcessor::dexedAppDir.setAsCurrentWorkingDirectory(); ctrlBackground = Colour(20,18,18); + // WARNING! If you modify the colour IDs here, please actualize the file ''DexedTheme.md'' + // in the subdirectory ``~/dexed/Documentation/`` + REG_COLOUR(TextButton::buttonColourId,Colour(0xFF0FC00F)); REG_COLOUR(TextButton::textColourOnId, Colours::white); REG_COLOUR(TextButton::textColourOffId, Colours::white); @@ -61,7 +71,12 @@ DXLookNFeel::DXLookNFeel() { REG_COLOUR(TreeView::backgroundColourId, background); REG_COLOUR(DirectoryContentsDisplayComponent::highlightColourId, fillColour); REG_COLOUR(DirectoryContentsDisplayComponent::textColourId, Colours::white); - + + // Register ``Scrollbar::thumbColourId`` to allow its redefinion in ``DexedTheme.xml``. + REG_COLOUR(ScrollBar::thumbColourId, background.darker()); + + // WARNING! If you modify the images here, please actualize the file ''DexedTheme.md'' + // in the subdirectory ``~/dexed/Documentation/`` imageKnob = ImageCache::getFromMemory(BinaryData::Knob_68x68_png, BinaryData::Knob_68x68_pngSize); // 2x imageSwitch = ImageCache::getFromMemory(BinaryData::Switch_96x52_png, BinaryData::Switch_96x52_pngSize); // 2x imageSwitchLighted = ImageCache::getFromMemory(BinaryData::SwitchLighted_48x26_png, BinaryData::SwitchLighted_48x26_pngSize); @@ -74,14 +89,28 @@ DXLookNFeel::DXLookNFeel() { imageOperator = ImageCache::getFromMemory(BinaryData::OperatorEditor_574x436_png, BinaryData::OperatorEditor_574x436_pngSize); // 2x imageGlobal = ImageCache::getFromMemory (BinaryData::GlobalEditor_1728x288_png, BinaryData::GlobalEditor_1728x288_pngSize); // 2x + //--- + // load and parse the file ``DexedTheme.xml`` + //--- + File dexedTheme = DexedAudioProcessor::dexedAppDir.getChildFile("DexedTheme.xml"); - if ( ! dexedTheme.existsAsFile() ) + if ( !dexedTheme.existsAsFile() ) { + TRACE("file \"%s\" does not exists", dexedTheme.getFullPathName()); return; - + } + std::unique_ptr root = XmlDocument::parse(dexedTheme); if ( root == NULL ) + { + TRACE("ERROR: XmlDocument::parse(): failed"); return; + } + + //--- + // get custom colors from ``DexedTheme.xml`` + // specified by the ``colour id`` / ``value`` pairs + //--- forEachXmlChildElementWithTagName(*root, colour, "colour") { String name = colour->getStringAttribute("id", ""); @@ -90,32 +119,51 @@ DXLookNFeel::DXLookNFeel() { String value = colour->getStringAttribute("value", ""); if ( value == "" ) continue; - if ( value.length() < 8 ) + if ( value.length() < 8 ) { + TRACE("ERROR: illegal value=\"%s\" at color id=\"%s\"; skipped", value.toRawUTF8(), name.toRawUTF8()); + continue; + } + //int conv = strtol(value.toRawUTF8(), NULL, 16); // as alpha (MSB) could be above 0x7F, hence ``strtol()`` is inappropiate to convert values exceeding ``0x7FFFFFFF`` + char* endptr = NULL; + uint64_t conv = strtoull(value.toRawUTF8(), &endptr, 16); + TRACE("color id=\"%s\" value=\"%s\": conv=0x%" PRIx64 "", name.toRawUTF8(), value.toRawUTF8(), conv); + if (endptr != nullptr && *endptr != '\0') { + TRACE("ERROR: illegal char #%d in value=\"%s\" at color id=\"%s\"; skipped", (int)(*endptr), value.toRawUTF8(), name.toRawUTF8()); + continue; + } + if (conv > 0xFFFFFFFFULL) { + TRACE("ERROR: value 0x%" PRIx64 " exceeded the limit at color id=\"%s\"; skipped", conv, name.toRawUTF8()); continue; - int conv = strtol(value.toRawUTF8(), NULL, 16); + } if ( colourMap.contains(name) ) { - setColour(colourMap[name], Colour(conv)); + setColour(colourMap[name], Colour((uint32_t)conv)); } else { if ( name == "Dexed::backgroundId" ) { - background = Colour(conv); + background = Colour((uint32_t)conv); continue; } - if ( name == "Dexed::fillColourId" ) { - fillColour = Colour(conv); + else if ( name == "Dexed::fillColourId" ) { + fillColour = Colour((uint32_t)conv); continue; } + TRACE("ERROR: color id=\"%s\" not found in colourMap; skipped.", name.toRawUTF8()); } } - // TODO: THIS IS DEAD CODE. NOBODY IS USING THIS. + //--- + // get custom images from ``DexedTheme.xml`` + // specified by the ``image id`` / ``path`` pairs + //--- + forEachXmlChildElementWithTagName(*root, image, "image") { String name = image->getStringAttribute("id", ""); String path = image->getStringAttribute("path", ""); - if ( name == "Knob_34x34.png" ) { + //TRACE("image id=\'%s\' path=\'%s\'", name.toRawUTF8(), path.toRawUTF8()); + if (name == /*"Knob_34x34.png"*/"Knob_68x68.png") { // 2x imageKnob = findImage(path); continue; } - if ( name == "Switch_48x26.png" ) { + if (name == /*"Switch_48x26.png"*/ "Switch_96x52.png") { // 2x imageSwitch = findImage(path); continue; } @@ -123,7 +171,7 @@ DXLookNFeel::DXLookNFeel() { imageSwitchLighted = findImage(path); continue; } - if ( name == "Switch_32x64.png" ) { + if (name == /*"Switch_32x64.png"*/ "Switch_64x64.png") { // 2x imageSwitchOperator = findImage(path); continue; } @@ -131,7 +179,7 @@ DXLookNFeel::DXLookNFeel() { imageButton = findImage(path); continue; } - if ( name == "Slider_26x26.png" ) { + if (name == /*"Slider_26x26.png"*/ "Slider_52x52.png") { // 2x imageSlider = findImage(path); continue; } @@ -139,7 +187,7 @@ DXLookNFeel::DXLookNFeel() { imageScaling = findImage(path); continue; } - if ( name == "Light_14x14.png" ) { + if (name == /*"Light_14x14.png"*/ "Light_28x28.png") { // 2x imageLight = findImage(path); continue; } @@ -147,14 +195,15 @@ DXLookNFeel::DXLookNFeel() { imageLFO = findImage(path); continue; } - if ( name == "OperatorEditor_287x218.png" ) { + if (name == /*"OperatorEditor_287x218.png"*/ "OperatorEditor_574x436_png") { // 2x imageOperator = findImage(path); continue; } - if ( name == "GlobalEditor_864x144.png" ) { + if (name == /*"GlobalEditor_864x144.png"*/ "GlobalEditor_1728x288_png") { // 2x imageGlobal = findImage(path); continue; } + TRACE("ERROR: unknown image id=\"%s\"; skipped", name.toRawUTF8()); } } @@ -173,7 +222,8 @@ void DXLookNFeel::drawRotarySlider( Graphics &g, int x, int y, int width, int he const int nFrames = imageKnob.getHeight()/imageKnob.getWidth(); // number of frames for vertical film strip const int frameIdx = (int)ceil(fractRotation * ((double)nFrames-1.0) ); // current index from 0 --> nFrames-1 - const float radius = jmin (width / 2.0f, height / 2.0f) ; + //const float radius = jmin (width / 2.0f, height / 2.0f) ; // float multiplication by 0.5 is faster than division by 2.0 + const float radius = jmin(width * 0.5f, height * 0.5f); const float centreX = x + width * 0.5f; const float centreY = y + height * 0.5f; const float rx = centreX - radius - 1.0f;