Browse Source

Renamed Plugin to Manufacturer, added multiline to TextField, added

Notes to Core, changed plugin build system to support multiple targets
per manufacturer
tags/v0.5.0
Andrew Belt 7 years ago
parent
commit
0c4cf64726
17 changed files with 143 additions and 103 deletions
  1. +1
    -1
      README.md
  2. +9
    -0
      compile.mk
  3. +2
    -2
      include/asset.hpp
  4. +20
    -15
      include/plugin.hpp
  5. +1
    -6
      include/rack.hpp
  6. +1
    -0
      include/widgets.hpp
  7. +4
    -6
      plugin.mk
  8. +2
    -2
      src/app/ModuleWidget.cpp
  9. +3
    -2
      src/app/RackScene.cpp
  10. +22
    -22
      src/app/RackWidget.cpp
  11. +3
    -3
      src/asset.cpp
  12. +1
    -1
      src/core/MidiClockToCV.cpp
  13. +12
    -11
      src/core/core.cpp
  14. +7
    -0
      src/core/core.hpp
  15. +32
    -32
      src/plugin.cpp
  16. +15
    -0
      src/widgets/FramebufferWidget.cpp
  17. +8
    -0
      src/widgets/TextField.cpp

+ 1
- 1
README.md View File

@@ -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.




+ 9
- 0
compile.mk View File

@@ -42,6 +42,15 @@ DEPS = $(patsubst %, build/%.d, $(SOURCES))
$(TARGET): $(OBJECTS) $(TARGET): $(OBJECTS)
$(CXX) -o $@ $^ $(LDFLAGS) $(CXX) -o $@ $^ $(LDFLAGS)


%.so:
$(CXX) -o $@ $^ $(LDFLAGS)

%.dylib:
$(CXX) -o $@ $^ $(LDFLAGS)

%.dll:
$(CXX) -o $@ $^ $(LDFLAGS)

# Object targets # Object targets


-include $(DEPS) -include $(DEPS)


+ 2
- 2
include/asset.hpp View File

@@ -13,9 +13,9 @@ std::string assetGlobal(std::string filename);
/** Searches for a local resource /** Searches for a local resource
*/ */
std::string assetLocal(std::string filename); std::string assetLocal(std::string filename);
/** Searches for a plugin resource, given a Plugin object
/** Searches for a manufacturer resource, given a Manufacturer object
*/ */
std::string assetPlugin(Plugin *plugin, std::string filename);
std::string assetManufacturer(Manufacturer *manufacturer, std::string filename);




} // namespace rack } // namespace rack

+ 20
- 15
include/plugin.hpp View File

@@ -10,37 +10,41 @@ struct ModuleWidget;
struct Model; 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 {
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 */
struct Manufacturer {
/** A list of the models available by this manufacturer, add with addModel() */
std::list<Model*> models; std::list<Model*> models;
/** The file path of the plugins directory */ /** The file path of the plugins directory */
std::string path; std::string path;
/** OS-dependent library handle */ /** OS-dependent library handle */
void *handle = NULL; void *handle = NULL;

// You may set everything below this point in your plugin

/** A unique identifier for the manufacturer, e.g. "foo" */
std::string slug;
/** Human readable name for the manufacturer, e.g. "Foo Modular" */
std::string name;
/** Optional metadata for the Add Module context menu */ /** Optional metadata for the Add Module context menu */
std::string homepageUrl; std::string homepageUrl;
std::string manualUrl; std::string manualUrl;
std::string version; std::string version;

virtual ~Manufacturer();
void addModel(Model *model);
}; };


struct Model { struct Model {
virtual ~Model() {}

Plugin *plugin;
/** A unique identifier for the model in this plugin, e.g. "VCO" */
Manufacturer *manufacturer = NULL;
/** A unique identifier for the model, e.g. "VCO" */
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;

virtual ~Model() {}
virtual ModuleWidget *createModuleWidget() { return NULL; } virtual ModuleWidget *createModuleWidget() { return NULL; }
}; };


extern std::list<Plugin*> gPlugins;
extern std::list<Manufacturer*> gManufacturers;
extern std::string gToken; extern std::string gToken;


void pluginInit(); void pluginInit();
@@ -62,7 +66,8 @@ std::string pluginGetLoginStatus();
// Implemented by plugin // Implemented by plugin
//////////////////// ////////////////////


/** Called once to initialize and return the Plugin instance.
/** Called once to initialize and return the Manufacturer instance.
You must implement this in your plugin
*/ */
extern "C" extern "C"
void init(rack::Plugin *plugin);
void init(rack::Manufacturer *manufacturer);

+ 1
- 6
include/rack.hpp View File

@@ -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 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,8 @@ 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);
}
return model; return model;
} }




+ 1
- 0
include/widgets.hpp View File

@@ -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;




+ 4
- 6
plugin.mk View File

@@ -8,23 +8,21 @@ include ../../arch.mk


ifeq ($(ARCH), lin) ifeq ($(ARCH), lin)
LDFLAGS += -shared LDFLAGS += -shared
TARGET = plugin.so
PLUGIN_EXTENSION = so
endif endif


ifeq ($(ARCH), mac) ifeq ($(ARCH), mac)
LDFLAGS += -shared -undefined dynamic_lookup LDFLAGS += -shared -undefined dynamic_lookup
TARGET = plugin.dylib
PLUGIN_EXTENSION = dylib
endif endif


ifeq ($(ARCH), win) ifeq ($(ARCH), win)
LDFLAGS += -shared -L../../ -lRack LDFLAGS += -shared -L../../ -lRack
TARGET = plugin.dll
PLUGIN_EXTENSION = dll
endif endif




all: $(TARGET)

clean: clean:
rm -rfv build $(TARGET) dist
rm -rfv build *.$(PLUGIN_EXTENSION) dist


include ../../compile.mk include ../../compile.mk

+ 2
- 2
src/app/ModuleWidget.cpp View File

@@ -46,7 +46,7 @@ json_t *ModuleWidget::toJson() {
json_t *rootJ = json_object(); json_t *rootJ = json_object();


// plugin // plugin
json_object_set_new(rootJ, "plugin", json_string(model->plugin->slug.c_str()));
json_object_set_new(rootJ, "plugin", json_string(model->manufacturer->slug.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->manufacturer->name + ": " + model->name;
menu->pushChild(menuLabel); menu->pushChild(menuLabel);


ResetMenuItem *resetItem = new ResetMenuItem(); ResetMenuItem *resetItem = new ResetMenuItem();


+ 3
- 2
src/app/RackScene.cpp View File

@@ -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();
} }


+ 22
- 22
src/app/RackWidget.cpp View File

@@ -208,21 +208,21 @@ void RackWidget::fromJson(json_t *rootJ) {
const char *modelSlug = json_string_value(modelSlugJ); const char *modelSlug = json_string_value(modelSlugJ);


// Search for plugin // Search for plugin
Plugin *plugin = NULL;
for (Plugin *p : gPlugins) {
if (p->slug == pluginSlug) {
plugin = p;
Manufacturer *manufacturer = NULL;
for (Manufacturer *m : gManufacturers) {
if (m->slug == pluginSlug) {
manufacturer = m;
break; break;
} }
} }
if (!plugin) {
if (!manufacturer) {
message += stringf("Could not find plugin \"%s\" for module \"%s\".\n", pluginSlug, modelSlug); message += stringf("Could not find plugin \"%s\" for module \"%s\".\n", pluginSlug, modelSlug);
continue; continue;
} }


// Get for model // Get for model
Model *model = NULL; Model *model = NULL;
for (Model *m : plugin->models) {
for (Model *m : manufacturer->models) {
if (m->slug == modelSlug) { if (m->slug == modelSlug) {
model = m; model = m;
break; break;
@@ -388,13 +388,13 @@ struct UrlItem : MenuItem {
} }
}; };


struct AddPluginMenuItem : MenuItem {
Plugin *plugin;
struct AddManufacturerMenuItem : MenuItem {
Manufacturer *manufacturer;
Vec modulePos; Vec modulePos;
Menu *createChildMenu() override { Menu *createChildMenu() override {
// Model items // Model items
Menu *menu = new Menu(); Menu *menu = new Menu();
for (Model *model : plugin->models) {
for (Model *model : manufacturer->models) {
AddModuleMenuItem *item = new AddModuleMenuItem(); AddModuleMenuItem *item = new AddModuleMenuItem();
item->text = model->name; item->text = model->name;
item->model = model; item->model = model;
@@ -409,34 +409,34 @@ struct AddPluginMenuItem : MenuItem {
} }
{ {
MenuLabel *label = new MenuLabel(); MenuLabel *label = new MenuLabel();
label->text = plugin->name;
label->text = manufacturer->name;
menu->pushChild(label); menu->pushChild(label);
} }


if (!plugin->homepageUrl.empty()) {
if (!manufacturer->homepageUrl.empty()) {
UrlItem *item = new UrlItem(); UrlItem *item = new UrlItem();
item->text = "Homepage"; item->text = "Homepage";
item->url = plugin->homepageUrl;
item->url = manufacturer->homepageUrl;
menu->pushChild(item); menu->pushChild(item);
} }


if (!plugin->manualUrl.empty()) {
if (!manufacturer->manualUrl.empty()) {
UrlItem *item = new UrlItem(); UrlItem *item = new UrlItem();
item->text = "Manual"; item->text = "Manual";
item->url = plugin->manualUrl;
item->url = manufacturer->manualUrl;
menu->pushChild(item); menu->pushChild(item);
} }


if (!plugin->path.empty()) {
if (!manufacturer->path.empty()) {
UrlItem *item = new UrlItem(); UrlItem *item = new UrlItem();
item->text = "Browse directory"; item->text = "Browse directory";
item->url = plugin->path;
item->url = manufacturer->path;
menu->pushChild(item); menu->pushChild(item);
} }


if (!plugin->version.empty()) {
if (!manufacturer->version.empty()) {
MenuLabel *item = new MenuLabel(); MenuLabel *item = new MenuLabel();
item->text = "Version: v" + plugin->version;
item->text = "Version: v" + manufacturer->version;
menu->pushChild(item); menu->pushChild(item);
} }


@@ -452,10 +452,10 @@ 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);
for (Plugin *plugin : gPlugins) {
AddPluginMenuItem *item = new AddPluginMenuItem();
item->text = plugin->name;
item->plugin = plugin;
for (Manufacturer *manufacturer : gManufacturers) {
AddManufacturerMenuItem *item = new AddManufacturerMenuItem();
item->text = manufacturer->name;
item->manufacturer = manufacturer;
item->modulePos = modulePos; item->modulePos = modulePos;
menu->pushChild(item); menu->pushChild(item);
} }


+ 3
- 3
src/asset.cpp View File

@@ -88,10 +88,10 @@ std::string assetLocal(std::string filename) {
return path; return path;
} }


std::string assetPlugin(Plugin *plugin, std::string filename) {
assert(plugin);
std::string assetManufacturer(Manufacturer *manufacturer, std::string filename) {
assert(manufacturer);
std::string path; std::string path;
path = plugin->path + "/" + filename;
path = manufacturer->path + "/" + filename;
return path; return path;
} }




+ 1
- 1
src/core/MidiClockToCV.cpp View File

@@ -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;
} }


+ 12
- 11
src/core/core.cpp View File

@@ -2,15 +2,16 @@
#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::Manufacturer *m) {
m->slug = "Core";
m->name = "Core";
m->homepageUrl = "https://vcvrack.com/";
m->addModel(createModel<AudioInterfaceWidget>("AudioInterface", "Audio Interface"));
m->addModel(createModel<MidiToCVWidget>("MIDIToCVInterface", "MIDI-to-CV Interface"));
m->addModel(createModel<MIDICCToCVWidget>("MIDICCToCVInterface", "MIDI CC-to-CV Interface"));
m->addModel(createModel<MIDIClockToCVWidget>("MIDIClockToCVInterface", "MIDI Clock-to-CV Interface"));
m->addModel(createModel<MIDITriggerToCVWidget>("MIDITriggerToCVInterface", "MIDI Trigger-to-CV Interface"));
// m->addModel(createModel<BridgeWidget>("Bridge", "Bridge"));
m->addModel(createModel<BlankWidget>("Blank", "Blank"));
m->addModel(createModel<NotesWidget>("Notes", "Notes"));
} }

+ 7
- 0
src/core/core.hpp View File

@@ -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;
};

+ 32
- 32
src/plugin.cpp View File

@@ -28,7 +28,7 @@


namespace rack { namespace rack {


std::list<Plugin*> gPlugins;
std::list<Manufacturer*> gManufacturers;
std::string gToken; std::string gToken;


static bool isDownloading = false; static bool isDownloading = false;
@@ -38,12 +38,19 @@ static std::string loginStatus;






Plugin::~Plugin() {
Manufacturer::~Manufacturer() {
for (Model *model : models) { for (Model *model : models) {
delete model; delete model;
} }
} }


void Manufacturer::addModel(Model *model) {
assert(!model->manufacturer);
model->manufacturer = 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,8 +77,8 @@ static int loadPlugin(std::string path) {
} }
#endif #endif


// Call plugin init() function
typedef void (*InitCallback)(Plugin *);
// Call plugin's init() function
typedef void (*InitCallback)(Manufacturer *);
InitCallback initCallback; InitCallback initCallback;
#if ARCH_WIN #if ARCH_WIN
initCallback = (InitCallback) GetProcAddress(handle, "init"); initCallback = (InitCallback) GetProcAddress(handle, "init");
@@ -83,27 +90,23 @@ static int loadPlugin(std::string path) {
return -2; return -2;
} }


// Construct and initialize Plugin instance
Plugin *plugin = new Plugin();
plugin->path = path;
plugin->handle = handle;
initCallback(plugin);
// Construct and initialize Manufacturer instance
Manufacturer *manufacturer = new Manufacturer();
manufacturer->path = path;
manufacturer->handle = handle;
initCallback(manufacturer);


// Check that this is a unique slug // Check that this is a unique slug
for (Plugin *otherPlugin : gPlugins) {
assert(plugin->slug != otherPlugin->slug);
for (Manufacturer *otherManufacturer : gManufacturers) {
assert(manufacturer->slug != otherManufacturer->slug);
} }


// Add plugin to list
gPlugins.push_back(plugin);
// Add manufacturer to list
gManufacturers.push_back(manufacturer);
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 +118,6 @@ static void loadPlugins(std::string path) {
} }
closedir(dir); closedir(dir);
} }

// Sort plugins
gPlugins.sort(comparePlugins);
} }


//////////////////// ////////////////////
@@ -201,8 +201,8 @@ static void refreshPurchase(json_t *pluginJ) {
url += gToken; url += gToken;


// Find slug in plugins list, and return silently if slug already exists // Find slug in plugins list, and return silently if slug already exists
for (Plugin *p : gPlugins) {
if (p->slug == slug) {
for (Manufacturer *m : gManufacturers) {
if (m->slug == slug) {
return; return;
} }
} }
@@ -237,9 +237,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);
Manufacturer *coreManufacturer = new Manufacturer();
init(coreManufacturer);
gManufacturers.push_back(coreManufacturer);


// Load plugins from global directory // Load plugins from global directory
std::string globalPlugins = assetGlobal("plugins"); std::string globalPlugins = assetGlobal("plugins");
@@ -255,20 +255,20 @@ void pluginInit() {
} }


void pluginDestroy() { void pluginDestroy() {
for (Plugin *plugin : gPlugins) {
for (Manufacturer *manufacturer : gManufacturers) {
// Free library handle // Free library handle
#if ARCH_WIN #if ARCH_WIN
if (plugin->handle)
FreeLibrary((HINSTANCE)plugin->handle);
if (manufacturer->handle)
FreeLibrary((HINSTANCE)manufacturer->handle);
#elif ARCH_LIN || ARCH_MAC #elif ARCH_LIN || ARCH_MAC
if (plugin->handle)
dlclose(plugin->handle);
if (manufacturer->handle)
dlclose(manufacturer->handle);
#endif #endif


// For some reason this segfaults // For some reason this segfaults
// delete plugin;
// delete manufacturer;
} }
gPlugins.clear();
gManufacturers.clear();
} }


void pluginLogIn(std::string email, std::string password) { void pluginLogIn(std::string email, std::string password) {


+ 15
- 0
src/widgets/FramebufferWidget.cpp View File

@@ -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);


+ 8
- 0
src/widgets/TextField.cpp View File

@@ -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());


Loading…
Cancel
Save