Introduce dark/light mode switch, EXPERIMENTAL
This commit is contained in:
parent
dd185edf96
commit
187b1c72dd
11 changed files with 202 additions and 49 deletions
|
@ -30,6 +30,7 @@
|
|||
#include "AsyncDialog.hpp"
|
||||
#include "PluginContext.hpp"
|
||||
#include "DistrhoPluginUtils.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
#include <asset.hpp>
|
||||
#include <context.hpp>
|
||||
|
@ -61,6 +62,7 @@ const std::string CARDINAL_VERSION = "22.07";
|
|||
namespace rack {
|
||||
|
||||
namespace settings {
|
||||
bool darkMode = true;
|
||||
int rateLimit = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,10 +33,6 @@ extern const std::string CARDINAL_VERSION;
|
|||
|
||||
namespace rack {
|
||||
|
||||
namespace settings {
|
||||
extern int rateLimit;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
struct Menu;
|
||||
}
|
||||
|
|
|
@ -114,6 +114,7 @@ BUILD_CXX_FLAGS += -faligned-new -Wno-abi
|
|||
endif
|
||||
|
||||
# use our custom function to invert some colors
|
||||
BUILD_CXX_FLAGS += -DnsvgDelete=nsvgDeleteCardinal
|
||||
BUILD_CXX_FLAGS += -DnsvgParseFromFile=nsvgParseFromFileCardinal
|
||||
|
||||
# Rack code is not tested for this flag, unset it
|
||||
|
|
|
@ -19,6 +19,13 @@
|
|||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <list>
|
||||
|
||||
namespace rack {
|
||||
namespace settings {
|
||||
extern bool darkMode;
|
||||
}
|
||||
}
|
||||
|
||||
#include "nanovg.h"
|
||||
|
||||
|
@ -43,6 +50,7 @@ NVGcolor nvgRGBblank(unsigned char, unsigned char, unsigned char)
|
|||
// Compile those nice implementation-in-header little libraries
|
||||
#define NANOSVG_IMPLEMENTATION
|
||||
#define NANOSVG_ALL_COLOR_KEYWORDS
|
||||
#undef nsvgDelete
|
||||
#undef nsvgParseFromFile
|
||||
#include <nanosvg.h>
|
||||
|
||||
|
@ -327,6 +335,7 @@ static inline bool invertPaint(NSVGshape* const shape, NSVGpaint& paint, const c
|
|||
// Special case for DrumKit background gradient
|
||||
if (std::strncmp(svgFileToInvert, "/DrumKit/", 9) == 0)
|
||||
{
|
||||
std::free(paint.gradient);
|
||||
paint.type = NSVG_PAINT_COLOR;
|
||||
paint.color = 0xff191919;
|
||||
return true;
|
||||
|
@ -593,12 +602,33 @@ static inline bool invertPaint(NSVGshape* const shape, NSVGpaint& paint, const c
|
|||
|
||||
extern "C" {
|
||||
NSVGimage* nsvgParseFromFileCardinal(const char* filename, const char* units, float dpi);
|
||||
void nsvgDeleteCardinal(NSVGimage*);
|
||||
}
|
||||
|
||||
struct ExtendedNSVGimage {
|
||||
NSVGimage* handle;
|
||||
NSVGshape* shapesOrig;
|
||||
NSVGshape* shapesDark;
|
||||
};
|
||||
static std::list<ExtendedNSVGimage> loadedSVGs;
|
||||
|
||||
static void nsvg__duplicatePaint(NSVGpaint& dst, NSVGpaint& src)
|
||||
{
|
||||
if (dst.type == NSVG_PAINT_LINEAR_GRADIENT || dst.type == NSVG_PAINT_RADIAL_GRADIENT)
|
||||
{
|
||||
dst.gradient = static_cast<NSVGgradient*>(malloc(sizeof(NSVGgradient)));
|
||||
std::memcpy(dst.gradient, src.gradient, sizeof(NSVGgradient));
|
||||
}
|
||||
}
|
||||
|
||||
NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* const units, const float dpi)
|
||||
{
|
||||
if (NSVGimage* const handle = nsvgParseFromFile(filename, units, dpi))
|
||||
{
|
||||
bool hasDarkMode = false;
|
||||
NSVGshape* shapesOrig;
|
||||
NSVGshape* shapesDark;
|
||||
|
||||
for (size_t i = 0; i < sizeof(svgFilesToInvert)/sizeof(svgFilesToInvert[0]); ++i)
|
||||
{
|
||||
const char* const svgFileToInvert = svgFilesToInvert[i].filename;
|
||||
|
@ -614,7 +644,30 @@ NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* con
|
|||
const int shapeNumberToIgnore = svgFilesToInvert[i].shapeNumberToIgnore;
|
||||
int shapeCounter = 0;
|
||||
|
||||
for (NSVGshape* shape = handle->shapes; shape != nullptr; shape = shape->next, ++shapeCounter)
|
||||
hasDarkMode = true;
|
||||
shapesOrig = handle->shapes;
|
||||
|
||||
// duplicate all shapes, so we can swap between original and dark mode at will
|
||||
shapesDark = static_cast<NSVGshape*>(malloc(sizeof(NSVGshape)));
|
||||
std::memcpy(shapesDark, shapesOrig, sizeof(NSVGshape));
|
||||
nsvg__duplicatePaint(shapesDark->fill, shapesOrig->fill);
|
||||
nsvg__duplicatePaint(shapesDark->stroke, shapesOrig->stroke);
|
||||
|
||||
for (NSVGshape* shape2 = shapesDark;;)
|
||||
{
|
||||
if (shape2->next == nullptr)
|
||||
break;
|
||||
|
||||
NSVGshape* const shapedup = static_cast<NSVGshape*>(malloc(sizeof(NSVGshape)));
|
||||
std::memcpy(shapedup, shape2->next, sizeof(NSVGshape));
|
||||
nsvg__duplicatePaint(shapedup->fill, shape2->next->fill);
|
||||
nsvg__duplicatePaint(shapedup->stroke, shape2->next->stroke);
|
||||
shape2->next = shapedup;
|
||||
shape2 = shapedup;
|
||||
}
|
||||
|
||||
// shape paint inversion
|
||||
for (NSVGshape* shape = shapesDark; shape != nullptr; shape = shape->next, ++shapeCounter)
|
||||
{
|
||||
if (shapeNumberToIgnore == shapeCounter)
|
||||
continue;
|
||||
|
@ -635,7 +688,7 @@ NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* con
|
|||
invertPaint(shape, shape->stroke, svgFileToInvert);
|
||||
}
|
||||
|
||||
return handle;
|
||||
break;
|
||||
}
|
||||
|
||||
// Special case for AmalgamatedHarmonics background color
|
||||
|
@ -643,8 +696,62 @@ NSVGimage* nsvgParseFromFileCardinal(const char* const filename, const char* con
|
|||
if (std::strstr(filename, "/AmalgamatedHarmonics/") != nullptr)
|
||||
handle->shapes->fill.color = 0xff191919;
|
||||
|
||||
if (hasDarkMode)
|
||||
{
|
||||
const ExtendedNSVGimage ext = { handle, shapesOrig, shapesDark };
|
||||
loadedSVGs.push_back(ext);
|
||||
|
||||
if (rack::settings::darkMode)
|
||||
handle->shapes = shapesDark;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void nsvgDeleteCardinal(NSVGimage* const handle)
|
||||
{
|
||||
for (auto it = loadedSVGs.cbegin(), end = loadedSVGs.cend(); it != end; ++it)
|
||||
{
|
||||
const ExtendedNSVGimage& ext(*it);
|
||||
|
||||
if (ext.handle != handle)
|
||||
continue;
|
||||
|
||||
// delete duplicated resources
|
||||
for (NSVGshape *next, *shape = ext.shapesDark;;)
|
||||
{
|
||||
next = shape->next;
|
||||
|
||||
nsvg__deletePaint(&shape->fill);
|
||||
nsvg__deletePaint(&shape->stroke);
|
||||
std::free(shape);
|
||||
|
||||
if (next == nullptr)
|
||||
break;
|
||||
|
||||
shape = next;
|
||||
}
|
||||
|
||||
// revert shapes back to original
|
||||
handle->shapes = ext.shapesOrig;
|
||||
|
||||
loadedSVGs.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
nsvgDelete(handle);
|
||||
}
|
||||
|
||||
void switchDarkMode(bool darkMode)
|
||||
{
|
||||
if (rack::settings::darkMode == darkMode)
|
||||
return;
|
||||
|
||||
rack::settings::darkMode = darkMode;
|
||||
|
||||
for (ExtendedNSVGimage& ext : loadedSVGs)
|
||||
ext.handle->shapes = darkMode ? ext.shapesDark : ext.shapesOrig;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include <ui/ProgressBar.hpp>
|
||||
#include <ui/Label.hpp>
|
||||
#include <engine/Engine.hpp>
|
||||
#include <widget/FramebufferWidget.hpp>
|
||||
#include <window/Window.hpp>
|
||||
#include <asset.hpp>
|
||||
#include <context.hpp>
|
||||
|
@ -60,6 +61,8 @@
|
|||
# include "DistrhoStandaloneUtils.hpp"
|
||||
#endif
|
||||
|
||||
void switchDarkMode(bool darkMode);
|
||||
|
||||
namespace rack {
|
||||
namespace asset {
|
||||
std::string patchesPath();
|
||||
|
@ -492,6 +495,20 @@ struct KnobScrollSensitivitySlider : ui::Slider {
|
|||
};
|
||||
|
||||
|
||||
static void setAllFramebufferWidgetsDirty(widget::Widget* const widget)
|
||||
{
|
||||
for (widget::Widget* child : widget->children)
|
||||
{
|
||||
if (widget::FramebufferWidget* const fbw = dynamic_cast<widget::FramebufferWidget*>(child))
|
||||
{
|
||||
fbw->setDirty();
|
||||
break;
|
||||
}
|
||||
setAllFramebufferWidgetsDirty(child);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ViewButton : MenuButton {
|
||||
void onAction(const ActionEvent& e) override {
|
||||
ui::Menu* menu = createMenu();
|
||||
|
@ -500,6 +517,14 @@ struct ViewButton : MenuButton {
|
|||
|
||||
menu->addChild(createMenuLabel("Appearance"));
|
||||
|
||||
std::string darkModeText;
|
||||
if (settings::darkMode)
|
||||
darkModeText = CHECKMARK_STRING;
|
||||
menu->addChild(createMenuItem("Dark Mode", darkModeText, []() {
|
||||
switchDarkMode(!settings::darkMode);
|
||||
setAllFramebufferWidgetsDirty(APP->scene);
|
||||
}));
|
||||
|
||||
menu->addChild(createBoolPtrMenuItem("Show tooltips", "", &settings::tooltips));
|
||||
|
||||
ZoomSlider* zoomSlider = new ZoomSlider;
|
||||
|
@ -563,10 +588,10 @@ struct ViewButton : MenuButton {
|
|||
|
||||
#ifdef DISTRHO_OS_WASM
|
||||
const bool fullscreen = APP->window->isFullScreen();
|
||||
std::string fullscreenText = "F11";
|
||||
if (fullscreen)
|
||||
fullscreenText += " " CHECKMARK_STRING;
|
||||
menu->addChild(createMenuItem("Fullscreen", fullscreenText, [=]() {
|
||||
std::string rightText = "F11";
|
||||
if (rightText)
|
||||
rightText += " " CHECKMARK_STRING;
|
||||
menu->addChild(createMenuItem("Fullscreen", rightText, [=]() {
|
||||
APP->window->setFullScreen(!fullscreen);
|
||||
}));
|
||||
#endif
|
||||
|
@ -612,10 +637,10 @@ struct EngineButton : MenuButton {
|
|||
#ifdef DISTRHO_OS_WASM
|
||||
if (supportsAudioInput()) {
|
||||
const bool enabled = isAudioInputEnabled();
|
||||
std::string text = "Enable Audio Input";
|
||||
std::string rightText;
|
||||
if (enabled)
|
||||
text += " " CHECKMARK_STRING;
|
||||
menu->addChild(createMenuItem(text, "", [enabled]() {
|
||||
rightText = CHECKMARK_STRING;
|
||||
menu->addChild(createMenuItem("Enable Audio Input", rightText, [enabled]() {
|
||||
if (!enabled)
|
||||
requestAudioInput();
|
||||
}));
|
||||
|
@ -623,10 +648,10 @@ struct EngineButton : MenuButton {
|
|||
|
||||
if (supportsMIDI()) {
|
||||
const bool enabled = isMIDIEnabled();
|
||||
std::string text = "Enable MIDI";
|
||||
std::string rightText;
|
||||
if (enabled)
|
||||
text += " " CHECKMARK_STRING;
|
||||
menu->addChild(createMenuItem(text, "", [enabled]() {
|
||||
rightText = CHECKMARK_STRING;
|
||||
menu->addChild(createMenuItem("Enable MIDI", rightText, [enabled]() {
|
||||
if (!enabled)
|
||||
requestMIDI();
|
||||
}));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue