| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| #include "common.hpp" | |||
| #include "widget/Widget.hpp" | |||
| #include "widget/FramebufferWidget.hpp" | |||
| #include "widget/SvgWidget.hpp" | |||
| @@ -9,10 +10,12 @@ namespace app { | |||
| /** If you don't add these to your ModuleWidget, they will fall out of the rack... */ | |||
| struct SvgScrew : widget::FramebufferWidget { | |||
| struct SvgScrew : widget::Widget { | |||
| widget::FramebufferWidget *fb; | |||
| widget::SvgWidget *sw; | |||
| SvgScrew(); | |||
| void setSvg(std::shared_ptr<Svg> svg); | |||
| }; | |||
| @@ -21,6 +21,7 @@ namespace rack { | |||
| /** Deprecation notice for functions | |||
| E.g. | |||
| DEPRECATED void foo(); | |||
| */ | |||
| #if defined(__GNUC__) || defined(__clang__) | |||
| @@ -33,9 +34,12 @@ E.g. | |||
| /** Concatenates two literals or two macros | |||
| Example: | |||
| #define COUNT 42 | |||
| CONCAT(myVariable, COUNT) | |||
| expands to | |||
| myVariable42 | |||
| */ | |||
| #define CONCAT_LITERAL(x, y) x ## y | |||
| @@ -44,10 +48,14 @@ expands to | |||
| /** Surrounds raw text with quotes | |||
| Example: | |||
| #define NAME "world" | |||
| printf("Hello " TOSTRING(NAME)) | |||
| expands to | |||
| printf("Hello " "world") | |||
| and of course the C++ lexer/parser then concatenates the string literals. | |||
| */ | |||
| #define TOSTRING_LITERAL(x) #x | |||
| @@ -60,26 +68,35 @@ and of course the C++ lexer/parser then concatenates the string literals. | |||
| /** Reserve space for `count` enums starting with `name`. | |||
| Example: | |||
| enum Foo { | |||
| ENUMS(BAR, 14), | |||
| BAZ | |||
| }; | |||
| BAR + 0 to BAR + 13 is reserved. BAZ has a value of 14. | |||
| `BAR + 0` to `BAR + 13` is reserved. `BAZ` has a value of 14. | |||
| */ | |||
| #define ENUMS(name, count) name, name ## _LAST = name + (count) - 1 | |||
| /** References binary files compiled into the program. | |||
| For example, to include a file "Test.dat" directly into your program binary, add | |||
| BINARIES += Test.dat | |||
| to your Makefile and declare | |||
| BINARY(Test_dat); | |||
| at the root of a .c or .cpp source file. Note that special characters are replaced with "_". Then use | |||
| BINARY_START(Test_dat) | |||
| BINARY_END(Test_dat) | |||
| to reference the data beginning and end as a void* array, and | |||
| BINARY_SIZE(Test_dat) | |||
| to get its size in bytes. | |||
| */ | |||
| #if defined ARCH_MAC | |||
| @@ -99,14 +116,15 @@ to get its size in bytes. | |||
| /** C#-style property constructor | |||
| Example: | |||
| Foo *foo = construct<Foo>(&Foo::greeting, "Hello world", &Foo::legs, 2); | |||
| */ | |||
| template<typename T> | |||
| template <typename T> | |||
| T *construct() { | |||
| return new T; | |||
| } | |||
| template<typename T, typename F, typename V, typename... Args> | |||
| template <typename T, typename F, typename V, typename... Args> | |||
| T *construct(F f, V v, Args... args) { | |||
| T *o = construct<T>(args...); | |||
| o->*f = v; | |||
| @@ -115,21 +133,21 @@ T *construct(F f, V v, Args... args) { | |||
| /** Defers code until the scope is destructed | |||
| From http://www.gingerbill.org/article/defer-in-cpp.html | |||
| Example: | |||
| file = fopen(...); | |||
| DEFER({ | |||
| fclose(file); | |||
| }); | |||
| */ | |||
| template<typename F> | |||
| template <typename F> | |||
| struct DeferWrapper { | |||
| F f; | |||
| DeferWrapper(F f) : f(f) {} | |||
| ~DeferWrapper() { f(); } | |||
| }; | |||
| template<typename F> | |||
| template <typename F> | |||
| DeferWrapper<F> deferWrapper(F f) { | |||
| return DeferWrapper<F>(f); | |||
| } | |||
| @@ -137,7 +155,7 @@ DeferWrapper<F> deferWrapper(F f) { | |||
| #define DEFER(code) auto CONCAT(_defer_, __COUNTER__) = rack::deferWrapper([&]() code) | |||
| /** Reports a string to the user */ | |||
| /** An exception meant to be shown to the user */ | |||
| struct UserException : std::runtime_error { | |||
| UserException(const std::string &msg) : std::runtime_error(msg) {} | |||
| }; | |||
| @@ -300,9 +300,12 @@ struct SynthTechAlco : app::SvgKnob { | |||
| minAngle = -0.82*M_PI; | |||
| maxAngle = 0.82*M_PI; | |||
| setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/SynthTechAlco.svg"))); | |||
| // Add cap | |||
| widget::FramebufferWidget *capFb = new widget::FramebufferWidget; | |||
| widget::SvgWidget *cap = new widget::SvgWidget; | |||
| cap->setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/SynthTechAlco_cap.svg"))); | |||
| addChild(cap); | |||
| capFb->addChild(cap); | |||
| addChild(capFb); | |||
| } | |||
| }; | |||
| @@ -460,15 +463,6 @@ struct RedGreenBlueLight : GrayModuleLightWidget { | |||
| } | |||
| }; | |||
| struct RGBLight : app::ModuleLightWidget { | |||
| RGBLight() { | |||
| addBaseColor(nvgRGBf(1, 0, 0)); | |||
| addBaseColor(nvgRGBf(0, 1, 0)); | |||
| addBaseColor(nvgRGBf(0, 0, 1)); | |||
| } | |||
| }; | |||
| /** Based on the size of 5mm LEDs */ | |||
| template <typename BASE> | |||
| struct LargeLight : BASE { | |||
| @@ -501,7 +495,7 @@ struct TinyLight : BASE { | |||
| } | |||
| }; | |||
| /** A light to displayed over PB61303. Must add a color by subclassing or templating. */ | |||
| /** A light for displaying on top of PB61303. Must add a color by subclassing or templating. */ | |||
| template <typename BASE> | |||
| struct LEDBezelLight : BASE { | |||
| LEDBezelLight() { | |||
| @@ -608,15 +602,13 @@ struct PB61303 : app::SvgSwitch { | |||
| struct ScrewSilver : app::SvgScrew { | |||
| ScrewSilver() { | |||
| sw->setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/ScrewSilver.svg"))); | |||
| box.size = sw->box.size; | |||
| setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/ScrewSilver.svg"))); | |||
| } | |||
| }; | |||
| struct ScrewBlack : app::SvgScrew { | |||
| ScrewBlack() { | |||
| sw->setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/ScrewBlack.svg"))); | |||
| box.size = sw->box.size; | |||
| setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/ScrewBlack.svg"))); | |||
| } | |||
| }; | |||
| @@ -5,6 +5,28 @@ | |||
| #include <set> | |||
| /** Remaps Ctrl to Cmd on Mac | |||
| Use this instead of GLFW_MOD_CONTROL, since Cmd should be used on Mac in place of Ctrl on Linux/Windows. | |||
| */ | |||
| #if defined ARCH_MAC | |||
| #define RACK_MOD_CTRL GLFW_MOD_SUPER | |||
| #define RACK_MOD_CTRL_NAME "Cmd" | |||
| #else | |||
| #define RACK_MOD_CTRL GLFW_MOD_CONTROL | |||
| #define RACK_MOD_CTRL_NAME "Ctrl" | |||
| #endif | |||
| #define RACK_MOD_SHIFT_NAME "Shift" | |||
| #define RACK_MOD_ALT_NAME "Alt" | |||
| /** Filters actual mod keys from the mod flags. | |||
| Use this if you don't care about GLFW_MOD_CAPS_LOCK and GLFW_MOD_NUM_LOCK. | |||
| Example usage: | |||
| if ((e.mod & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) ... | |||
| */ | |||
| #define RACK_MOD_MASK (GLFW_MOD_SHIFT | GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) | |||
| namespace rack { | |||
| @@ -52,8 +52,8 @@ TWidget *createWidget(math::Vec pos) { | |||
| template <class TWidget> | |||
| TWidget *createWidgetCentered(math::Vec pos) { | |||
| TWidget *o = new TWidget; | |||
| o->box.pos = pos.minus(o->box.size.div(2));; | |||
| TWidget *o = createWidget<TWidget>(pos); | |||
| o->box.pos = o->box.pos.minus(o->box.size.div(2)); | |||
| return o; | |||
| } | |||
| @@ -2,14 +2,17 @@ | |||
| /** Example usage: | |||
| DEBUG("error: %d", errno); | |||
| will print something like | |||
| [0.123 debug myfile.cpp:45] error: 67 | |||
| */ | |||
| #define DEBUG(format, ...) rack::logger::log(rack::logger::DEBUG_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) | |||
| #define INFO(format, ...) rack::logger::log(rack::logger::INFO_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) | |||
| #define WARN(format, ...) rack::logger::log(rack::logger::WARN_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) | |||
| #define FATAL(format, ...) rack::logger::log(rack::logger::FATAL_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) | |||
| #define DEBUG(format, ...) logger::log(rack::logger::DEBUG_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) | |||
| #define INFO(format, ...) logger::log(rack::logger::INFO_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) | |||
| #define WARN(format, ...) logger::log(rack::logger::WARN_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) | |||
| #define FATAL(format, ...) logger::log(rack::logger::FATAL_LEVEL, __FILE__, __LINE__, format, ##__VA_ARGS__) | |||
| namespace rack { | |||
| @@ -29,8 +32,8 @@ enum Level { | |||
| void init(); | |||
| void destroy(); | |||
| /** Do not use this function directly. Use the macros below. | |||
| Thread-safe. | |||
| /** Do not use this function directly. Use the macros above. | |||
| Thread-safe, meaning messages cannot overlap each other in the log. | |||
| */ | |||
| void log(Level level, const char *filename, int line, const char *format, ...); | |||
| @@ -101,6 +101,7 @@ namespace rack { | |||
| /** Define this macro before including this header to prevent common namespaces from being included in the main `rack::` namespace. */ | |||
| #ifndef RACK_FLATTEN_NAMESPACES | |||
| // Import some namespaces for convenience | |||
| using namespace logger; | |||
| using namespace math; | |||
| using namespace widget; | |||
| using namespace ui; | |||
| @@ -13,14 +13,15 @@ namespace string { | |||
| /** Converts a UTF-16/32 string (depending on the size of wchar_t) to a UTF-8 string. */ | |||
| std::string fromWstring(const std::wstring &s); | |||
| std::wstring toWstring(const std::string &s); | |||
| /** Converts a `printf()` format string and optional arguments into a std::string | |||
| Remember that "%s" must reference a `char *`, so use `.c_str()` for `std::string`s. | |||
| /** Converts a `printf()` format string and optional arguments into a std::string. | |||
| Remember that "%s" must reference a `char *`, so use `.c_str()` for `std::string`s, otherwise you might get binary garbage. | |||
| */ | |||
| std::string f(const char *format, ...); | |||
| /** Replaces all characters to lowercase letters */ | |||
| std::string lowercase(const std::string &s); | |||
| /** Replaces all characters to uppercase letters */ | |||
| std::string uppercase(const std::string &s); | |||
| /** Removes whitespace from beginning and end of string. */ | |||
| std::string trim(const std::string &s); | |||
| /** Truncates and adds "..." to a string, not exceeding `len` characters */ | |||
| std::string ellipsize(const std::string &s, size_t len); | |||
| @@ -12,11 +12,13 @@ namespace system { | |||
| /** Returns a list of all entries (directories, files, symbols) in a directory. */ | |||
| std::list<std::string> listEntries(const std::string &path); | |||
| std::list<std::string> getEntries(const std::string &path); | |||
| /** Returns whether the given path is a file. */ | |||
| bool isFile(const std::string &path); | |||
| /** Returns whether the given path is a directory. */ | |||
| bool isDirectory(const std::string &path); | |||
| /** Moves a file. */ | |||
| void moveFile(const std::string &srcPath, const std::string &destPath); | |||
| /** Copies a file. */ | |||
| void copyFile(const std::string &srcPath, const std::string &destPath); | |||
| /** Creates a directory. | |||
| @@ -36,12 +38,12 @@ Shell injection is possible, so make sure the URL is trusted or hard coded. | |||
| May block, so open in a new thread. | |||
| */ | |||
| void openBrowser(const std::string &url); | |||
| /** Opens Explorer, Finder, etc at the folder location. */ | |||
| /** Opens Windows Explorer, Finder, etc at the folder location. */ | |||
| void openFolder(const std::string &path); | |||
| /** Runs an executable without blocking. | |||
| The launched process will continue running if the current process is closed. | |||
| */ | |||
| void runProcessAsync(const std::string &path); | |||
| void runProcessDetached(const std::string &path); | |||
| std::string getOperatingSystemInfo(); | |||
| @@ -14,27 +14,6 @@ | |||
| #include <nanosvg.h> | |||
| /** Remaps Ctrl to Cmd on Mac | |||
| Use this instead of GLFW_MOD_CONTROL, since Cmd should be used on Mac in place of Ctrl on Linux/Windows. | |||
| */ | |||
| #if defined ARCH_MAC | |||
| #define RACK_MOD_CTRL GLFW_MOD_SUPER | |||
| #define RACK_MOD_CTRL_NAME "Cmd" | |||
| #else | |||
| #define RACK_MOD_CTRL GLFW_MOD_CONTROL | |||
| #define RACK_MOD_CTRL_NAME "Ctrl" | |||
| #endif | |||
| #define RACK_MOD_SHIFT_NAME "Shift" | |||
| #define RACK_MOD_ALT_NAME "Alt" | |||
| /** Filters actual mod keys from the mod flags. | |||
| Use this if you don't care about GLFW_MOD_CAPS_LOCK and GLFW_MOD_NUM_LOCK. | |||
| Example usage: | |||
| if ((e.mod & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) ... | |||
| */ | |||
| #define RACK_MOD_MASK (GLFW_MOD_SHIFT | GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) | |||
| namespace rack { | |||
| @@ -6,8 +6,18 @@ namespace app { | |||
| SvgScrew::SvgScrew() { | |||
| fb = new widget::FramebufferWidget; | |||
| addChild(fb); | |||
| sw = new widget::SvgWidget; | |||
| addChild(sw); | |||
| fb->addChild(sw); | |||
| } | |||
| void SvgScrew::setSvg(std::shared_ptr<Svg> svg) { | |||
| sw->setSvg(svg); | |||
| fb->box.size = sw->box.size; | |||
| box.size = sw->box.size; | |||
| } | |||
| @@ -91,8 +91,7 @@ void PatchManager::save(std::string path) { | |||
| json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||
| std::fclose(file); | |||
| std::remove(path.c_str()); | |||
| std::rename(tmpPath.c_str(), path.c_str()); | |||
| system::moveFile(tmpPath, path); | |||
| } | |||
| void PatchManager::saveDialog() { | |||
| @@ -149,7 +149,7 @@ static bool loadPlugin(std::string path) { | |||
| // Search for presets | |||
| for (Model *model : plugin->models) { | |||
| std::string presetDir = asset::plugin(plugin, "presets/" + model->slug); | |||
| for (const std::string &presetPath : system::listEntries(presetDir)) { | |||
| for (const std::string &presetPath : system::getEntries(presetDir)) { | |||
| model->presetPaths.push_back(presetPath); | |||
| } | |||
| } | |||
| @@ -190,7 +190,7 @@ static bool syncUpdate(const Update &update) { | |||
| static void loadPlugins(std::string path) { | |||
| std::string message; | |||
| for (std::string pluginPath : system::listEntries(path)) { | |||
| for (std::string pluginPath : system::getEntries(path)) { | |||
| if (!system::isDirectory(pluginPath)) | |||
| continue; | |||
| if (!loadPlugin(pluginPath)) { | |||
| @@ -271,7 +271,7 @@ static int extractZip(const char *filename, const char *path) { | |||
| static void extractPackages(const std::string &path) { | |||
| std::string message; | |||
| for (std::string packagePath : system::listEntries(path)) { | |||
| for (std::string packagePath : system::getEntries(path)) { | |||
| if (string::filenameExtension(packagePath) != "zip") | |||
| continue; | |||
| INFO("Extracting package %s", packagePath.c_str()); | |||
| @@ -25,7 +25,7 @@ namespace rack { | |||
| namespace system { | |||
| std::list<std::string> listEntries(const std::string &path) { | |||
| std::list<std::string> getEntries(const std::string &path) { | |||
| std::list<std::string> filenames; | |||
| DIR *dir = opendir(path.c_str()); | |||
| if (dir) { | |||
| @@ -56,14 +56,23 @@ bool isDirectory(const std::string &path) { | |||
| return S_ISDIR(statbuf.st_mode); | |||
| } | |||
| void moveFile(const std::string &srcPath, const std::string &destPath) { | |||
| std::remove(destPath.c_str()); | |||
| // Whether this overwrites existing files is implementation-defined. | |||
| // i.e. Mingw64 fails to overwrite. | |||
| // This is why we remove the file above. | |||
| std::rename(srcPath.c_str(), destPath.c_str()); | |||
| } | |||
| void copyFile(const std::string &srcPath, const std::string &destPath) { | |||
| // Open files | |||
| // Open source | |||
| FILE *source = fopen(srcPath.c_str(), "rb"); | |||
| if (!source) | |||
| return; | |||
| DEFER({ | |||
| fclose(source); | |||
| }); | |||
| // Open destination | |||
| FILE *dest = fopen(destPath.c_str(), "wb"); | |||
| if (!dest) | |||
| return; | |||
| @@ -93,7 +102,6 @@ void createDirectory(const std::string &path) { | |||
| } | |||
| int getLogicalCoreCount() { | |||
| // TODO Return the physical cores, not logical cores. | |||
| return std::thread::hardware_concurrency(); | |||
| } | |||
| @@ -199,7 +207,7 @@ void openFolder(const std::string &path) { | |||
| } | |||
| void runProcessAsync(const std::string &path) { | |||
| void runProcessDetached(const std::string &path) { | |||
| #if defined ARCH_WIN | |||
| STARTUPINFOW startupInfo; | |||
| PROCESS_INFORMATION processInfo; | |||
| @@ -212,6 +220,9 @@ void runProcessAsync(const std::string &path) { | |||
| CreateProcessW(pathW.c_str(), NULL, | |||
| NULL, NULL, false, 0, NULL, NULL, | |||
| &startupInfo, &processInfo); | |||
| #else | |||
| // Not implemented on Linux or Mac | |||
| assert(0); | |||
| #endif | |||
| } | |||
| @@ -226,6 +237,7 @@ std::string getOperatingSystemInfo() { | |||
| ZeroMemory(&info, sizeof(info)); | |||
| info.dwOSVersionInfoSize = sizeof(info); | |||
| GetVersionExW(&info); | |||
| // See https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoa for a list of Windows version numbers. | |||
| return string::f("Windows %u.%u", info.dwMajorVersion, info.dwMinorVersion); | |||
| #endif | |||
| } | |||