BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

skins.cpp (6123B)


      1 
      2 #include "skins.hpp"
      3 #include "bogaudio.hpp"
      4 #include <unistd.h>
      5 #include <fstream>
      6 #include <cstdio>
      7 
      8 Skins globalSkins;
      9 std::mutex globalSkinsLock;
     10 
     11 Skins& Skins::skins() {
     12 	// Hacky/blind attempt to fix https://github.com/bogaudio/BogaudioModules/issues/195.
     13 	// Replace this totally correct scoped static instance with a global,
     14 	// on the chance it will be handled more simply by the compiler.
     15 	// static Skins instance;
     16 	// std::lock_guard<std::mutex> lock(instance._instanceLock);
     17 	// if (!instance._loaded) {
     18 	// 	instance.loadSkins();
     19 	// 	instance.loadCssValues();
     20 	// 	instance._loaded = true;
     21 	// }
     22 	// return instance;
     23 
     24 	std::lock_guard<std::mutex> lock(globalSkins._instanceLock);
     25 	if (!globalSkins._loaded) {
     26 		globalSkins.loadSkins();
     27 		globalSkins.loadCssValues();
     28 		globalSkins._loaded = true;
     29 	}
     30 	return globalSkins;
     31 }
     32 
     33 bool Skins::validKey(const std::string& key) const {
     34 	for (auto s : _available) {
     35 		if (s.key == key) {
     36 			return true;
     37 		}
     38 	}
     39 	return false;
     40 }
     41 
     42 const char* Skins::skinCssValue(const std::string& skinKey, const std::string& valueKey) const {
     43 	std::string sk = skinKey;
     44 	if (sk == "default") {
     45 		sk = defaultKey();
     46 	}
     47 	if (!validKey(sk)) {
     48 		return NULL;
     49 	}
     50 
     51 	auto values = _skinCssValues.find(sk);
     52 	if (values == _skinCssValues.end()) {
     53 		return NULL;
     54 	}
     55 	auto value = values->second.find(valueKey);
     56 	if (value == values->second.end()) {
     57 		return NULL;
     58 	}
     59 	return value->second.c_str();
     60 }
     61 
     62 NVGcolor Skins::cssColorToNVGColor(const char* color, const NVGcolor& ifError) {
     63 	auto h2i = [](char h) {
     64 		switch (h) {
     65 			case '0': return 0;
     66 			case '1': return 1;
     67 			case '2': return 2;
     68 			case '3': return 3;
     69 			case '4': return 4;
     70 			case '5': return 5;
     71 			case '6': return 6;
     72 			case '7': return 7;
     73 			case '8': return 8;
     74 			case '9': return 9;
     75 			case 'A': return 10;
     76 			case 'B': return 11;
     77 			case 'C': return 12;
     78 			case 'D': return 13;
     79 			case 'E': return 14;
     80 			case 'F': return 15;
     81 			case 'a': return 10;
     82 			case 'b': return 11;
     83 			case 'c': return 12;
     84 			case 'd': return 13;
     85 			case 'e': return 14;
     86 			case 'f': return 15;
     87 			default: return -1;
     88 		}
     89 	};
     90 	if (color[0] == '#') {
     91 		if (strlen(color) == 4) {
     92 			int i1 = h2i(color[1]);
     93 			int i2 = h2i(color[2]);
     94 			int i3 = h2i(color[3]);
     95 			if (i1 >= 0 && i2 >= 0 && i3 >= 0) {
     96 				return nvgRGBA(16*i1 + i1, 16*i2 + i2, 16*i3 + i3, 0xff);
     97 			}
     98 		}
     99 		else if (strlen(color) == 7) {
    100 			int i11 = h2i(color[1]);
    101 			int i12 = h2i(color[1]);
    102 			int i21 = h2i(color[3]);
    103 			int i22 = h2i(color[4]);
    104 			int i31 = h2i(color[5]);
    105 			int i32 = h2i(color[6]);
    106 			if (i11 >= 0 && i12 >= 0 && i21 >= 0 && i22 >= 0 && i31 >= 0 && i32 >= 0) {
    107 				return nvgRGBA(16*i11 + i12, 16*i21 + i22, 16*i31 + i32, 0xff);
    108 			}
    109 		}
    110 	}
    111 	return ifError;
    112 }
    113 
    114 void Skins::setDefaultSkin(std::string skinKey) {
    115 	if (skinKey == "default") {
    116 		skinKey = "light";
    117 	}
    118 	std::string path = rack::asset::user("Bogaudio.json");
    119 	std::string error;
    120 	if (!validKey(skinKey)) {
    121 		error = "invalid key: " + skinKey;
    122 	}
    123 	else {
    124 		std::ofstream f(path);
    125 		f << "{\n  \"skins\": {\n    \"default\": \"";
    126 		f << skinKey;
    127 		f << "\"\n  }\n}\n";
    128 		if (!f) {
    129 			error = "error writing \"" + path + "\": " + strerror(errno);
    130 		}
    131 	}
    132 
    133 	if (error.size() > 0) {
    134 		WARN("Bogaudio: setting default skin: %s\n", error.c_str());
    135 	}
    136 	else {
    137 		_default = skinKey;
    138 		INFO("Bogaudio: skin information written to %s\n", path.c_str());
    139 
    140 		std::lock_guard<std::mutex> lock(_defaultSkinListenersLock);
    141 		for (auto listener : _defaultSkinListeners) {
    142 			listener->defaultSkinChanged(_default);
    143 		}
    144 	}
    145 }
    146 
    147 void Skins::registerDefaultSkinChangeListener(DefaultSkinChangeListener* listener) {
    148 	std::lock_guard<std::mutex> lock(_defaultSkinListenersLock);
    149 	_defaultSkinListeners.insert(listener);
    150 }
    151 
    152 void Skins::deregisterDefaultSkinChangeListener(DefaultSkinChangeListener* listener) {
    153 	std::lock_guard<std::mutex> lock(_defaultSkinListenersLock);
    154 	_defaultSkinListeners.erase(listener);
    155 }
    156 
    157 void Skins::loadSkins() {
    158 	_available.push_back(Skin("light", "Light"));
    159 	_available.push_back(Skin("dark", "Dark"));
    160 	_available.push_back(Skin("lowcontrast", "Dark (low-contrast)"));
    161 	_default = "light";
    162 
    163 	std::string path = rack::asset::user("Bogaudio.json");
    164 	if (access(path.c_str(), R_OK) != 0) {
    165 		return;
    166 	}
    167 
    168 	json_error_t error;
    169 	json_t* root = json_load_file(path.c_str(), 0, &error);
    170 	if (!root) {
    171 		WARN("Bogaudio: JSON error loading skins data from %s: %s\n", path.c_str(), error.text);
    172 		return;
    173 	}
    174 
    175 	json_t* skins = json_object_get(root, "skins");
    176 	if (!skins) {
    177 		WARN("Bogaudio: no \"skins\" section found in %s\n", path.c_str());
    178 	}
    179 	else {
    180 		json_t* d = json_object_get(skins, "default");
    181 		if (!d) {
    182 			WARN("Bogaudio: \"skins\" section has no key \"default\" in %s\n", path.c_str());
    183 		}
    184 		else {
    185 			std::string dk = json_string_value(d);
    186 			if (!validKey(dk)) {
    187 				WARN("Bogaudio: \"skins\" \"default\" value \"%s\" is invalid in %s\n", dk.c_str(), path.c_str());
    188 				WARN("Bogaudio: available skins are:\n");
    189 				for (auto s : _available) {
    190 					WARN("Bogaudio:   %s\n", s.key.c_str());
    191 				}
    192 			}
    193 			else {
    194 				_default = dk;
    195 				INFO("Bogaudio: skin information loaded successfully from %s\n", path.c_str());
    196 			}
    197 		}
    198 	}
    199 
    200 	json_decref(root);
    201 }
    202 
    203 void Skins::loadCssValues() {
    204 	std::string path = rack::asset::plugin(pluginInstance, "res/skin_css_values.json");
    205 	if (access(path.c_str(), R_OK) != 0) {
    206 		return;
    207 	}
    208 
    209 	json_error_t error;
    210 	json_t* root = json_load_file(path.c_str(), 0, &error);
    211 	if (!root) {
    212 		WARN("Bogaudio: JSON error loading CSS values data from %s: %s\n", path.c_str(), error.text);
    213 		return;
    214 	}
    215 
    216 	void* i = json_object_iter(root);
    217 	while (i) {
    218 		const char* skin = json_object_iter_key(i);
    219 		json_t* values = json_object_iter_value(i);
    220 		void* j = json_object_iter(values);
    221 		css_values_map valuesMap;
    222 		while (j) {
    223 			const char* k = json_object_iter_key(j);
    224 			const char* v = json_string_value(json_object_iter_value(j));
    225 			valuesMap.insert(css_values_map::value_type(k, v));
    226 			j = json_object_iter_next(values, j);
    227 		}
    228 		_skinCssValues.insert(skin_css_values_map::value_type(skin, valuesMap));
    229 		i = json_object_iter_next(root, i);
    230 	}
    231 
    232 	json_decref(root);
    233 }