| @@ -75,7 +75,7 @@ include compile.mk | |||||
| dist: all | dist: all | ||||
| ifndef VERSION | ifndef VERSION | ||||
| $(error VERSION must be defined when calling make) | |||||
| $(error VERSION must be defined when making distributables) | |||||
| endif | endif | ||||
| rm -rf dist | rm -rf dist | ||||
| $(MAKE) -C plugins/Fundamental dist | $(MAKE) -C plugins/Fundamental dist | ||||
| @@ -9,7 +9,7 @@ This README includes instructions for building Rack from source. For information | |||||
| ## The [Issue Tracker](https://github.com/VCVRack/Rack/issues) *is* the official developer's forum | ## The [Issue Tracker](https://github.com/VCVRack/Rack/issues) *is* the official developer's forum | ||||
| Bug reports, feature requests, and even *questions/discussions* are welcome on the GitHub Issue Tracker for all VCVRack repos. | Bug reports, feature requests, and even *questions/discussions* are welcome on the GitHub Issue Tracker for all VCVRack repos. | ||||
| However, please search before posting to avoid duplicates. | |||||
| However, please search before posting to avoid duplicates, and limit to one issue per post. | |||||
| You may vote on feature requests by using the Thumbs Up/Down reaction on the first post. | You may vote on feature requests by using the Thumbs Up/Down reaction on the first post. | ||||
| @@ -42,7 +42,6 @@ DEPS = $(patsubst %, build/%.d, $(SOURCES)) | |||||
| $(TARGET): $(OBJECTS) | $(TARGET): $(OBJECTS) | ||||
| $(CXX) -o $@ $^ $(LDFLAGS) | $(CXX) -o $@ $^ $(LDFLAGS) | ||||
| # Object targets | |||||
| -include $(DEPS) | -include $(DEPS) | ||||
| @@ -7,13 +7,13 @@ | |||||
| namespace rack { | namespace rack { | ||||
| /** Searches for a global read-only resource and returns its path, or "" if not found | |||||
| /** Returns the path of a global resource. Read-only | |||||
| */ | */ | ||||
| std::string assetGlobal(std::string filename); | std::string assetGlobal(std::string filename); | ||||
| /** Searches for a local resource | |||||
| /** Returns the path of a local resource. Read/write | |||||
| */ | */ | ||||
| std::string assetLocal(std::string filename); | std::string assetLocal(std::string filename); | ||||
| /** Searches for a plugin resource, given a Plugin object | |||||
| /** Returns the path of a resource in the plugin's folder. Read-only | |||||
| */ | */ | ||||
| std::string assetPlugin(Plugin *plugin, std::string filename); | std::string assetPlugin(Plugin *plugin, std::string filename); | ||||
| @@ -11,32 +11,37 @@ struct Model; | |||||
| // Subclass this and return a pointer to a new one when init() is called | // Subclass this and return a pointer to a new one when init() is called | ||||
| struct Plugin { | struct Plugin { | ||||
| virtual ~Plugin(); | |||||
| /** A unique identifier for your plugin, e.g. "foo" */ | |||||
| std::string slug; | |||||
| /** Human readable name for your plugin, e.g. "Foo Modular" */ | |||||
| std::string name; | |||||
| /** A list of the models made available by this plugin */ | |||||
| /** A list of the models available by this plugin, add with addModel() */ | |||||
| std::list<Model*> models; | std::list<Model*> models; | ||||
| /** The file path of the plugins directory */ | |||||
| /** The file path of the plugin's directory */ | |||||
| std::string path; | std::string path; | ||||
| /** OS-dependent library handle */ | /** OS-dependent library handle */ | ||||
| void *handle = NULL; | void *handle = NULL; | ||||
| /** Optional metadata for the Add Module context menu */ | |||||
| std::string homepageUrl; | |||||
| std::string manualUrl; | |||||
| /** Used when syncing plugins with the API */ | |||||
| std::string slug; | |||||
| /** The version of your plugin | |||||
| Plugins should follow the versioning scheme described at https://github.com/VCVRack/Rack/issues/266 | |||||
| Do not include the "v" in "v1.0" for example. | |||||
| */ | |||||
| std::string version; | std::string version; | ||||
| virtual ~Plugin(); | |||||
| void addModel(Model *model); | |||||
| }; | }; | ||||
| struct Model { | struct Model { | ||||
| virtual ~Model() {} | |||||
| Plugin *plugin; | |||||
| /** A unique identifier for the model in this plugin, e.g. "VCO" */ | |||||
| Plugin *plugin = NULL; | |||||
| /** An identifier for the model, e.g. "VCO". Used for saving patches. The slug, manufacturerSlug pair must be unique. */ | |||||
| std::string slug; | std::string slug; | ||||
| /** Human readable name for your model, e.g. "Voltage Controlled Oscillator" */ | /** Human readable name for your model, e.g. "Voltage Controlled Oscillator" */ | ||||
| std::string name; | std::string name; | ||||
| /** An identifier for the manufacturer, e.g. "foo". Used for saving patches. */ | |||||
| std::string manufacturerSlug; | |||||
| /** Human readable name for the manufacturer, e.g. "Foo Modular" */ | |||||
| std::string manufacturerName; | |||||
| virtual ~Model() {} | |||||
| virtual ModuleWidget *createModuleWidget() { return NULL; } | virtual ModuleWidget *createModuleWidget() { return NULL; } | ||||
| }; | }; | ||||
| @@ -63,6 +68,7 @@ std::string pluginGetLoginStatus(); | |||||
| //////////////////// | //////////////////// | ||||
| /** Called once to initialize and return the Plugin instance. | /** Called once to initialize and return the Plugin instance. | ||||
| You must implement this in your plugin | |||||
| */ | */ | ||||
| extern "C" | extern "C" | ||||
| void init(rack::Plugin *plugin); | void init(rack::Plugin *plugin); | ||||
| @@ -18,7 +18,7 @@ namespace rack { | |||||
| //////////////////// | //////////////////// | ||||
| template <class TModuleWidget> | template <class TModuleWidget> | ||||
| Model *createModel(Plugin *plugin, std::string slug, std::string name) { | |||||
| Model *createModel(std::string manufacturerSlug, std::string manufacturerName, std::string slug, std::string name) { | |||||
| struct TModel : Model { | struct TModel : Model { | ||||
| ModuleWidget *createModuleWidget() override { | ModuleWidget *createModuleWidget() override { | ||||
| ModuleWidget *moduleWidget = new TModuleWidget(); | ModuleWidget *moduleWidget = new TModuleWidget(); | ||||
| @@ -27,13 +27,10 @@ Model *createModel(Plugin *plugin, std::string slug, std::string name) { | |||||
| } | } | ||||
| }; | }; | ||||
| Model *model = new TModel(); | Model *model = new TModel(); | ||||
| model->plugin = plugin; | |||||
| model->slug = slug; | model->slug = slug; | ||||
| model->name = name; | model->name = name; | ||||
| // Create bi-directional association between the Plugin and Model | |||||
| if (plugin) { | |||||
| plugin->models.push_back(model); | |||||
| } | |||||
| model->manufacturerSlug = manufacturerSlug; | |||||
| model->manufacturerName = manufacturerName; | |||||
| return model; | return model; | ||||
| } | } | ||||
| @@ -399,6 +399,7 @@ struct ZoomWidget : Widget { | |||||
| struct TextField : OpaqueWidget { | struct TextField : OpaqueWidget { | ||||
| std::string text; | std::string text; | ||||
| std::string placeholder; | std::string placeholder; | ||||
| bool multiline = false; | |||||
| int begin = 0; | int begin = 0; | ||||
| int end = 0; | int end = 0; | ||||
| @@ -24,7 +24,7 @@ endif | |||||
| all: $(TARGET) | all: $(TARGET) | ||||
| include ../../compile.mk | |||||
| clean: | clean: | ||||
| rm -rfv build $(TARGET) dist | rm -rfv build $(TARGET) dist | ||||
| include ../../compile.mk | |||||
| @@ -45,8 +45,8 @@ void ModuleWidget::addParam(ParamWidget *param) { | |||||
| json_t *ModuleWidget::toJson() { | json_t *ModuleWidget::toJson() { | ||||
| json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
| // plugin | |||||
| json_object_set_new(rootJ, "plugin", json_string(model->plugin->slug.c_str())); | |||||
| // manufacturer | |||||
| json_object_set_new(rootJ, "manufacturer", json_string(model->manufacturerSlug.c_str())); | |||||
| // model | // model | ||||
| json_object_set_new(rootJ, "model", json_string(model->slug.c_str())); | json_object_set_new(rootJ, "model", json_string(model->slug.c_str())); | ||||
| // pos | // pos | ||||
| @@ -245,7 +245,7 @@ Menu *ModuleWidget::createContextMenu() { | |||||
| Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
| MenuLabel *menuLabel = new MenuLabel(); | MenuLabel *menuLabel = new MenuLabel(); | ||||
| menuLabel->text = model->plugin->name + ": " + model->name; | |||||
| menuLabel->text = model->manufacturerName + " " + model->name; | |||||
| menu->pushChild(menuLabel); | menu->pushChild(menuLabel); | ||||
| ResetMenuItem *resetItem = new ResetMenuItem(); | ResetMenuItem *resetItem = new ResetMenuItem(); | ||||
| @@ -2,6 +2,7 @@ | |||||
| #include "gui.hpp" | #include "gui.hpp" | ||||
| #include "util/request.hpp" | #include "util/request.hpp" | ||||
| #include "../ext/osdialog/osdialog.h" | #include "../ext/osdialog/osdialog.h" | ||||
| #include <string.h> | |||||
| #include <thread> | #include <thread> | ||||
| @@ -17,7 +18,7 @@ static void checkVersion() { | |||||
| json_t *versionJ = json_object_get(resJ, "version"); | json_t *versionJ = json_object_get(resJ, "version"); | ||||
| if (versionJ) { | if (versionJ) { | ||||
| const char *version = json_string_value(versionJ); | const char *version = json_string_value(versionJ); | ||||
| if (version && version != gApplicationVersion) { | |||||
| if (version && strlen(version) > 0 && version != gApplicationVersion) { | |||||
| newVersion = version; | newVersion = version; | ||||
| } | } | ||||
| } | } | ||||
| @@ -45,7 +46,7 @@ RackScene::RackScene() { | |||||
| scrollWidget->box.pos.y = gToolbar->box.size.y; | scrollWidget->box.pos.y = gToolbar->box.size.y; | ||||
| // Check for new version | // Check for new version | ||||
| if (gApplicationVersion != "dev") { | |||||
| if (!gApplicationVersion.empty()) { | |||||
| std::thread versionThread(checkVersion); | std::thread versionThread(checkVersion); | ||||
| versionThread.detach(); | versionThread.detach(); | ||||
| } | } | ||||
| @@ -1,12 +1,13 @@ | |||||
| #include <map> | |||||
| #include <algorithm> | |||||
| #include <thread> | |||||
| #include "app.hpp" | #include "app.hpp" | ||||
| #include "engine.hpp" | #include "engine.hpp" | ||||
| #include "plugin.hpp" | #include "plugin.hpp" | ||||
| #include "gui.hpp" | #include "gui.hpp" | ||||
| #include "settings.hpp" | #include "settings.hpp" | ||||
| #include "asset.hpp" | #include "asset.hpp" | ||||
| #include <map> | |||||
| #include <algorithm> | |||||
| #include <thread> | |||||
| #include <set> | |||||
| #include "../ext/osdialog/osdialog.h" | #include "../ext/osdialog/osdialog.h" | ||||
| @@ -200,36 +201,29 @@ void RackWidget::fromJson(json_t *rootJ) { | |||||
| size_t moduleId; | size_t moduleId; | ||||
| json_t *moduleJ; | json_t *moduleJ; | ||||
| json_array_foreach(modulesJ, moduleId, moduleJ) { | json_array_foreach(modulesJ, moduleId, moduleJ) { | ||||
| json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); | |||||
| if (!pluginSlugJ) continue; | |||||
| json_t *manufacturerSlugJ = json_object_get(moduleJ, "manufacturer"); | |||||
| if (!manufacturerSlugJ) { | |||||
| // Backward compatibility with Rack v0.4 and lower | |||||
| manufacturerSlugJ = json_object_get(moduleJ, "plugin"); | |||||
| if (!manufacturerSlugJ) continue; | |||||
| } | |||||
| json_t *modelSlugJ = json_object_get(moduleJ, "model"); | json_t *modelSlugJ = json_object_get(moduleJ, "model"); | ||||
| if (!modelSlugJ) continue; | if (!modelSlugJ) continue; | ||||
| const char *pluginSlug = json_string_value(pluginSlugJ); | |||||
| const char *modelSlug = json_string_value(modelSlugJ); | |||||
| // Search for plugin | |||||
| Plugin *plugin = NULL; | |||||
| for (Plugin *p : gPlugins) { | |||||
| if (p->slug == pluginSlug) { | |||||
| plugin = p; | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (!plugin) { | |||||
| message += stringf("Could not find plugin \"%s\" for module \"%s\".\n", pluginSlug, modelSlug); | |||||
| continue; | |||||
| } | |||||
| std::string manufacturerSlug = json_string_value(manufacturerSlugJ); | |||||
| std::string modelSlug = json_string_value(modelSlugJ); | |||||
| // Get for model | |||||
| // Search for model | |||||
| Model *model = NULL; | Model *model = NULL; | ||||
| for (Model *m : plugin->models) { | |||||
| if (m->slug == modelSlug) { | |||||
| model = m; | |||||
| break; | |||||
| for (Plugin *plugin : gPlugins) { | |||||
| for (Model *m : plugin->models) { | |||||
| if (m->manufacturerSlug == manufacturerSlug && m->slug == modelSlug) { | |||||
| model = m; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| if (!model) { | if (!model) { | ||||
| message += stringf("Could not find module \"%s\" in plugin \"%s\".\n", modelSlug, pluginSlug); | |||||
| message += stringf("Could not find \"%s %s\" module\n", manufacturerSlug.c_str(), modelSlug.c_str()); | |||||
| continue; | continue; | ||||
| } | } | ||||
| @@ -388,13 +382,23 @@ struct UrlItem : MenuItem { | |||||
| } | } | ||||
| }; | }; | ||||
| struct AddPluginMenuItem : MenuItem { | |||||
| Plugin *plugin; | |||||
| struct AddManufacturerMenuItem : MenuItem { | |||||
| std::string manufacturerName; | |||||
| Vec modulePos; | Vec modulePos; | ||||
| Menu *createChildMenu() override { | Menu *createChildMenu() override { | ||||
| // Collect models which have this manufacturer name | |||||
| std::set<Model*> models; | |||||
| for (Plugin *plugin : gPlugins) { | |||||
| for (Model *model : plugin->models) { | |||||
| if (model->manufacturerName == manufacturerName) { | |||||
| models.insert(model); | |||||
| } | |||||
| } | |||||
| } | |||||
| // Model items | // Model items | ||||
| Menu *menu = new Menu(); | Menu *menu = new Menu(); | ||||
| for (Model *model : plugin->models) { | |||||
| for (Model *model : models) { | |||||
| AddModuleMenuItem *item = new AddModuleMenuItem(); | AddModuleMenuItem *item = new AddModuleMenuItem(); | ||||
| item->text = model->name; | item->text = model->name; | ||||
| item->model = model; | item->model = model; | ||||
| @@ -403,6 +407,7 @@ struct AddPluginMenuItem : MenuItem { | |||||
| } | } | ||||
| // Metadata items | // Metadata items | ||||
| /* | |||||
| { | { | ||||
| MenuLabel *label = new MenuLabel(); | MenuLabel *label = new MenuLabel(); | ||||
| menu->pushChild(label); | menu->pushChild(label); | ||||
| @@ -439,6 +444,7 @@ struct AddPluginMenuItem : MenuItem { | |||||
| item->text = "Version: v" + plugin->version; | item->text = "Version: v" + plugin->version; | ||||
| menu->pushChild(item); | menu->pushChild(item); | ||||
| } | } | ||||
| */ | |||||
| return menu; | return menu; | ||||
| } | } | ||||
| @@ -452,10 +458,18 @@ void RackWidget::onMouseDownOpaque(int button) { | |||||
| MenuLabel *menuLabel = new MenuLabel(); | MenuLabel *menuLabel = new MenuLabel(); | ||||
| menuLabel->text = "Add module"; | menuLabel->text = "Add module"; | ||||
| menu->pushChild(menuLabel); | menu->pushChild(menuLabel); | ||||
| // Collect manufacturer names | |||||
| std::set<std::string> manufacturerNames; | |||||
| for (Plugin *plugin : gPlugins) { | for (Plugin *plugin : gPlugins) { | ||||
| AddPluginMenuItem *item = new AddPluginMenuItem(); | |||||
| item->text = plugin->name; | |||||
| item->plugin = plugin; | |||||
| for (Model *model : plugin->models) { | |||||
| manufacturerNames.insert(model->manufacturerName); | |||||
| } | |||||
| } | |||||
| // Add menu item for each manufacturer name | |||||
| for (std::string manufacturerName : manufacturerNames) { | |||||
| AddManufacturerMenuItem *item = new AddManufacturerMenuItem(); | |||||
| item->text = manufacturerName; | |||||
| item->manufacturerName = manufacturerName; | |||||
| item->modulePos = modulePos; | item->modulePos = modulePos; | ||||
| menu->pushChild(item); | menu->pushChild(item); | ||||
| } | } | ||||
| @@ -241,7 +241,7 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() { | |||||
| { | { | ||||
| Label *label = new Label(); | Label *label = new Label(); | ||||
| label->box.pos = Vec(box.size.x - margin - 7 * 15, margin); | label->box.pos = Vec(box.size.x - margin - 7 * 15, margin); | ||||
| label->text = "MIDI Clock to CV"; | |||||
| label->text = "MIDI Clk-CV"; | |||||
| addChild(label); | addChild(label); | ||||
| yPos = labelHeight * 2; | yPos = labelHeight * 2; | ||||
| } | } | ||||
| @@ -0,0 +1,44 @@ | |||||
| #include "core.hpp" | |||||
| using namespace rack; | |||||
| NotesWidget::NotesWidget() { | |||||
| box.size = Vec(RACK_GRID_WIDTH * 18, RACK_GRID_HEIGHT); | |||||
| { | |||||
| Panel *panel = new LightPanel(); | |||||
| panel->box.size = box.size; | |||||
| addChild(panel); | |||||
| } | |||||
| addChild(createScrew<ScrewSilver>(Vec(15, 0))); | |||||
| addChild(createScrew<ScrewSilver>(Vec(15, 365))); | |||||
| addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 0))); | |||||
| addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 365))); | |||||
| textField = new TextField(); | |||||
| textField->box.pos = Vec(15, 15); | |||||
| textField->box.size = box.size.minus(Vec(30, 30)); | |||||
| textField->multiline = true; | |||||
| addChild(textField); | |||||
| } | |||||
| json_t *NotesWidget::toJson() { | |||||
| json_t *rootJ = ModuleWidget::toJson(); | |||||
| // text | |||||
| json_object_set_new(rootJ, "text", json_string(textField->text.c_str())); | |||||
| return rootJ; | |||||
| } | |||||
| void NotesWidget::fromJson(json_t *rootJ) { | |||||
| ModuleWidget::fromJson(rootJ); | |||||
| // text | |||||
| json_t *textJ = json_object_get(rootJ, "text"); | |||||
| if (textJ) | |||||
| textField->text = json_string_value(textJ); | |||||
| } | |||||
| @@ -2,15 +2,14 @@ | |||||
| #include "MidiIO.hpp" | #include "MidiIO.hpp" | ||||
| void init(rack::Plugin *plugin) { | |||||
| plugin->slug = "Core"; | |||||
| plugin->name = "Core"; | |||||
| plugin->homepageUrl = "https://vcvrack.com/"; | |||||
| createModel<AudioInterfaceWidget>(plugin, "AudioInterface", "Audio Interface"); | |||||
| createModel<MidiToCVWidget>(plugin, "MIDIToCVInterface", "MIDI-to-CV Interface"); | |||||
| createModel<MIDICCToCVWidget>(plugin, "MIDICCToCVInterface", "MIDI CC-to-CV Interface"); | |||||
| createModel<MIDIClockToCVWidget>(plugin, "MIDIClockToCVInterface", "MIDI Clock-to-CV Interface"); | |||||
| createModel<MIDITriggerToCVWidget>(plugin, "MIDITriggerToCVInterface", "MIDI Trigger-to-CV Interface"); | |||||
| // createModel<BridgeWidget>(plugin, "Bridge", "Bridge"); | |||||
| createModel<BlankWidget>(plugin, "Blank", "Blank"); | |||||
| void init(rack::Plugin *p) { | |||||
| p->slug = "Core"; | |||||
| p->addModel(createModel<AudioInterfaceWidget>("Core", "Core", "AudioInterface", "Audio Interface")); | |||||
| p->addModel(createModel<MidiToCVWidget>("Core", "Core", "MIDIToCVInterface", "MIDI-to-CV Interface")); | |||||
| p->addModel(createModel<MIDICCToCVWidget>("Core", "Core", "MIDICCToCVInterface", "MIDI CC-to-CV Interface")); | |||||
| p->addModel(createModel<MIDIClockToCVWidget>("Core", "Core", "MIDIClockToCVInterface", "MIDI Clock-to-CV Interface")); | |||||
| p->addModel(createModel<MIDITriggerToCVWidget>("Core", "Core", "MIDITriggerToCVInterface", "MIDI Trigger-to-CV Interface")); | |||||
| // p->addModel(createModel<BridgeWidget>("Core", "Core", "Bridge", "Bridge")); | |||||
| p->addModel(createModel<BlankWidget>("Core", "Core", "Blank", "Blank")); | |||||
| p->addModel(createModel<NotesWidget>("Core", "Core", "Notes", "Notes")); | |||||
| } | } | ||||
| @@ -26,3 +26,10 @@ struct BlankWidget : ModuleWidget { | |||||
| json_t *toJson() override; | json_t *toJson() override; | ||||
| void fromJson(json_t *rootJ) override; | void fromJson(json_t *rootJ) override; | ||||
| }; | }; | ||||
| struct NotesWidget : ModuleWidget { | |||||
| TextField *textField; | |||||
| NotesWidget(); | |||||
| json_t *toJson() override; | |||||
| void fromJson(json_t *rootJ) override; | |||||
| }; | |||||
| @@ -44,6 +44,13 @@ Plugin::~Plugin() { | |||||
| } | } | ||||
| } | } | ||||
| void Plugin::addModel(Model *model) { | |||||
| assert(!model->plugin); | |||||
| model->plugin = this; | |||||
| models.push_back(model); | |||||
| } | |||||
| static int loadPlugin(std::string path) { | static int loadPlugin(std::string path) { | ||||
| std::string libraryFilename; | std::string libraryFilename; | ||||
| #if ARCH_LIN | #if ARCH_LIN | ||||
| @@ -70,7 +77,7 @@ static int loadPlugin(std::string path) { | |||||
| } | } | ||||
| #endif | #endif | ||||
| // Call plugin init() function | |||||
| // Call plugin's init() function | |||||
| typedef void (*InitCallback)(Plugin *); | typedef void (*InitCallback)(Plugin *); | ||||
| InitCallback initCallback; | InitCallback initCallback; | ||||
| #if ARCH_WIN | #if ARCH_WIN | ||||
| @@ -89,21 +96,12 @@ static int loadPlugin(std::string path) { | |||||
| plugin->handle = handle; | plugin->handle = handle; | ||||
| initCallback(plugin); | initCallback(plugin); | ||||
| // Check that this is a unique slug | |||||
| for (Plugin *otherPlugin : gPlugins) { | |||||
| assert(plugin->slug != otherPlugin->slug); | |||||
| } | |||||
| // Add plugin to list | // Add plugin to list | ||||
| gPlugins.push_back(plugin); | gPlugins.push_back(plugin); | ||||
| fprintf(stderr, "Loaded plugin %s\n", libraryFilename.c_str()); | fprintf(stderr, "Loaded plugin %s\n", libraryFilename.c_str()); | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| static bool comparePlugins(Plugin *a, Plugin *b) { | |||||
| return a->slug < b->slug; | |||||
| } | |||||
| static void loadPlugins(std::string path) { | static void loadPlugins(std::string path) { | ||||
| DIR *dir = opendir(path.c_str()); | DIR *dir = opendir(path.c_str()); | ||||
| if (dir) { | if (dir) { | ||||
| @@ -115,9 +113,6 @@ static void loadPlugins(std::string path) { | |||||
| } | } | ||||
| closedir(dir); | closedir(dir); | ||||
| } | } | ||||
| // Sort plugins | |||||
| gPlugins.sort(comparePlugins); | |||||
| } | } | ||||
| //////////////////// | //////////////////// | ||||
| @@ -200,13 +195,6 @@ static void refreshPurchase(json_t *pluginJ) { | |||||
| url += "&token="; | url += "&token="; | ||||
| url += gToken; | url += gToken; | ||||
| // Find slug in plugins list, and return silently if slug already exists | |||||
| for (Plugin *p : gPlugins) { | |||||
| if (p->slug == slug) { | |||||
| return; | |||||
| } | |||||
| } | |||||
| // If plugin is not loaded, download the zip file to /plugins | // If plugin is not loaded, download the zip file to /plugins | ||||
| downloadName = name; | downloadName = name; | ||||
| downloadProgress = 0.0; | downloadProgress = 0.0; | ||||
| @@ -237,9 +225,9 @@ static void refreshPurchase(json_t *pluginJ) { | |||||
| void pluginInit() { | void pluginInit() { | ||||
| // Load core | // Load core | ||||
| // This function is defined in core.cpp | // This function is defined in core.cpp | ||||
| Plugin *corePlugin = new Plugin(); | |||||
| init(corePlugin); | |||||
| gPlugins.push_back(corePlugin); | |||||
| Plugin *coreManufacturer = new Plugin(); | |||||
| init(coreManufacturer); | |||||
| gPlugins.push_back(coreManufacturer); | |||||
| // Load plugins from global directory | // Load plugins from global directory | ||||
| std::string globalPlugins = assetGlobal("plugins"); | std::string globalPlugins = assetGlobal("plugins"); | ||||
| @@ -248,10 +236,11 @@ void pluginInit() { | |||||
| // Load plugins from local directory | // Load plugins from local directory | ||||
| std::string localPlugins = assetLocal("plugins"); | std::string localPlugins = assetLocal("plugins"); | ||||
| mkdir(localPlugins.c_str(), 0755); | |||||
| printf("Loading plugins from %s\n", localPlugins.c_str()); | |||||
| if (globalPlugins != localPlugins) | |||||
| if (globalPlugins != localPlugins) { | |||||
| mkdir(localPlugins.c_str(), 0755); | |||||
| printf("Loading plugins from %s\n", localPlugins.c_str()); | |||||
| loadPlugins(localPlugins); | loadPlugins(localPlugins); | ||||
| } | |||||
| } | } | ||||
| void pluginDestroy() { | void pluginDestroy() { | ||||
| @@ -265,7 +254,8 @@ void pluginDestroy() { | |||||
| dlclose(plugin->handle); | dlclose(plugin->handle); | ||||
| #endif | #endif | ||||
| // For some reason this segfaults | |||||
| // For some reason this segfaults. | |||||
| // It might be best to let them leak anyway, because "crash on exit" issues would occur with badly-written plugins. | |||||
| // delete plugin; | // delete plugin; | ||||
| } | } | ||||
| gPlugins.clear(); | gPlugins.clear(); | ||||
| @@ -42,6 +42,7 @@ void FramebufferWidget::step() { | |||||
| internal->box.size = box.size; | internal->box.size = box.size; | ||||
| internal->box.size = Vec(ceilf(internal->box.size.x), ceilf(internal->box.size.y)); | internal->box.size = Vec(ceilf(internal->box.size.x), ceilf(internal->box.size.y)); | ||||
| Vec fbSize = internal->box.size.mult(gPixelRatio * oversample); | Vec fbSize = internal->box.size.mult(gPixelRatio * oversample); | ||||
| // assert(fbSize.isFinite()); | // assert(fbSize.isFinite()); | ||||
| // Reject zero area size | // Reject zero area size | ||||
| if (fbSize.x <= 0.0 || fbSize.y <= 0.0) | if (fbSize.x <= 0.0 || fbSize.y <= 0.0) | ||||
| @@ -71,6 +72,20 @@ void FramebufferWidget::step() { | |||||
| } | } | ||||
| void FramebufferWidget::draw(NVGcontext *vg) { | void FramebufferWidget::draw(NVGcontext *vg) { | ||||
| // { | |||||
| // float xform[6]; | |||||
| // nvgCurrentTransform(vg, xform); | |||||
| // printf("%f %f %f %f %f %f\n", xform[0], xform[1], xform[2], xform[3], xform[4], xform[5]); | |||||
| // nvgSave(vg); | |||||
| // nvgResetTransform(vg); | |||||
| // nvgTranslate(vg, xform[5], xform[6]); | |||||
| // nvgBeginPath(vg); | |||||
| // nvgRect(vg, 0, 0, 50, 50); | |||||
| // nvgFillColor(vg, nvgRGBf(1.0, 0.0, 0.0)); | |||||
| // nvgFill(vg); | |||||
| // nvgRestore(vg); | |||||
| // } | |||||
| if (!internal->fb) { | if (!internal->fb) { | ||||
| // Bypass framebuffer cache entirely | // Bypass framebuffer cache entirely | ||||
| // Widget::draw(vg); | // Widget::draw(vg); | ||||
| @@ -101,6 +101,14 @@ bool TextField::onFocusKey(int key) { | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case GLFW_KEY_ENTER: | |||||
| if (multiline) { | |||||
| insertText("\n"); | |||||
| } | |||||
| else { | |||||
| onAction(); | |||||
| } | |||||
| break; | |||||
| } | } | ||||
| begin = mini(maxi(begin, 0), text.size()); | begin = mini(maxi(begin, 0), text.size()); | ||||