| @@ -26,7 +26,7 @@ struct ModuleWidget : widget::OpaqueWidget { | |||||
| math::Vec dragPos; | math::Vec dragPos; | ||||
| math::Vec oldPos; | math::Vec oldPos; | ||||
| ModuleWidget() {} | |||||
| ModuleWidget(); | |||||
| DEPRECATED ModuleWidget(engine::Module *module) { | DEPRECATED ModuleWidget(engine::Module *module) { | ||||
| setModule(module); | setModule(module); | ||||
| } | } | ||||
| @@ -1,5 +1,5 @@ | |||||
| #include "common.hpp" | |||||
| #include "sse_mathfun.h" | #include "sse_mathfun.h" | ||||
| #include <cstring> | |||||
| #include <emmintrin.h> | #include <emmintrin.h> | ||||
| @@ -7,7 +7,10 @@ namespace rack { | |||||
| namespace dsp { | namespace dsp { | ||||
| /** Casts an int to float, bitwise without conversion. */ | |||||
| inline float cast_i32_f32(int i) { | inline float cast_i32_f32(int i) { | ||||
| static_assert(sizeof(int) == sizeof(float), "int and float must be the same size"); | |||||
| // Should be optimized to two `mov` instructions | |||||
| float f; | float f; | ||||
| std::memcpy(&f, &i, sizeof(f)); | std::memcpy(&f, &i, sizeof(f)); | ||||
| return f; | return f; | ||||
| @@ -21,6 +24,17 @@ inline int cast_f32_i32(float f) { | |||||
| } | } | ||||
| /** Generic class for vector float types. | |||||
| This class is designed to be used just like `float` scalars, with extra features for handling bitwise logic, conditions, loading, and storing. | |||||
| Usage example: | |||||
| float a[4], b[4]; | |||||
| f32_4 a = f32_4::load(in); | |||||
| f32_4 b = 2.f * a / (1 - a); | |||||
| b *= sin(2 * M_PI * a); | |||||
| b.store(out); | |||||
| */ | |||||
| template <int N> | template <int N> | ||||
| struct f32; | struct f32; | ||||
| @@ -65,7 +79,7 @@ typedef f32<4> f32_4; | |||||
| // Operator overloads | // Operator overloads | ||||
| /** `a operator b` */ | |||||
| /** `a @ b` */ | |||||
| #define DECLARE_F32_4_OPERATOR_INFIX(operator, func) \ | #define DECLARE_F32_4_OPERATOR_INFIX(operator, func) \ | ||||
| inline f32_4 operator(const f32_4 &a, const f32_4 &b) { \ | inline f32_4 operator(const f32_4 &a, const f32_4 &b) { \ | ||||
| return f32_4(func(a.v, b.v)); \ | return f32_4(func(a.v, b.v)); \ | ||||
| @@ -79,7 +93,7 @@ typedef f32<4> f32_4; | |||||
| return operator(a, f32_4(b)); \ | return operator(a, f32_4(b)); \ | ||||
| } | } | ||||
| /** `a operator b` */ | |||||
| /** `a @= b` */ | |||||
| #define DECLARE_F32_4_OPERATOR_INCREMENT(operator, opfunc) \ | #define DECLARE_F32_4_OPERATOR_INCREMENT(operator, opfunc) \ | ||||
| inline f32_4 &operator(f32_4 &a, const f32_4 &b) { \ | inline f32_4 &operator(f32_4 &a, const f32_4 &b) { \ | ||||
| a = opfunc(a, b); \ | a = opfunc(a, b); \ | ||||
| @@ -95,7 +109,7 @@ DECLARE_F32_4_OPERATOR_INFIX(operator-, _mm_sub_ps) | |||||
| DECLARE_F32_4_OPERATOR_INFIX(operator*, _mm_mul_ps) | DECLARE_F32_4_OPERATOR_INFIX(operator*, _mm_mul_ps) | ||||
| DECLARE_F32_4_OPERATOR_INFIX(operator/, _mm_div_ps) | DECLARE_F32_4_OPERATOR_INFIX(operator/, _mm_div_ps) | ||||
| /** Boolean operators on vectors give 0x00000000 for false and 0xffffffff for true, for each vector element. | |||||
| /** | |||||
| Use these to apply logic, bit masks, and conditions to elements. | Use these to apply logic, bit masks, and conditions to elements. | ||||
| Examples: | Examples: | ||||
| @@ -106,6 +120,8 @@ DECLARE_F32_4_OPERATOR_INFIX(operator^, _mm_xor_ps) | |||||
| DECLARE_F32_4_OPERATOR_INFIX(operator&, _mm_and_ps) | DECLARE_F32_4_OPERATOR_INFIX(operator&, _mm_and_ps) | ||||
| DECLARE_F32_4_OPERATOR_INFIX(operator|, _mm_mul_ps) | DECLARE_F32_4_OPERATOR_INFIX(operator|, _mm_mul_ps) | ||||
| /** Boolean operators on vectors give 0x00000000 for false and 0xffffffff for true, for each vector element. | |||||
| */ | |||||
| DECLARE_F32_4_OPERATOR_INCREMENT(operator+=, operator+); | DECLARE_F32_4_OPERATOR_INCREMENT(operator+=, operator+); | ||||
| DECLARE_F32_4_OPERATOR_INCREMENT(operator-=, operator-); | DECLARE_F32_4_OPERATOR_INCREMENT(operator-=, operator-); | ||||
| DECLARE_F32_4_OPERATOR_INCREMENT(operator*=, operator*); | DECLARE_F32_4_OPERATOR_INCREMENT(operator*=, operator*); | ||||
| @@ -245,30 +245,42 @@ struct Zoom : Event { | |||||
| }; | }; | ||||
| /** Occurs after the Widget's position is set by setPos(); | |||||
| /** Occurs after a Widget's position is set by Widget::setPos(). | |||||
| */ | */ | ||||
| struct Reposition : Event { | struct Reposition : Event { | ||||
| }; | }; | ||||
| /** Occurs after the Widget's size is set by setSize(); | |||||
| /** Occurs after a Widget's size is set by Widget::setSize(). | |||||
| */ | */ | ||||
| struct Resize : Event { | struct Resize : Event { | ||||
| }; | }; | ||||
| /** Occurs after the Widget is added to a parent. | |||||
| /** Occurs after a Widget is added to a parent. | |||||
| */ | */ | ||||
| struct Add : Event { | struct Add : Event { | ||||
| }; | }; | ||||
| /** Occurs before the Widget is remove from its parent. | |||||
| /** Occurs before a Widget is removed from its parent. | |||||
| */ | */ | ||||
| struct Remove : Event { | struct Remove : Event { | ||||
| }; | }; | ||||
| /** Occurs after a Widget is shown with Widget::show(). | |||||
| */ | |||||
| struct Show : Event { | |||||
| }; | |||||
| /** Occurs after a Widget is hidden with Widget::hide(). | |||||
| */ | |||||
| struct Hide : Event { | |||||
| }; | |||||
| struct State { | struct State { | ||||
| widget::Widget *rootWidget = NULL; | widget::Widget *rootWidget = NULL; | ||||
| /** State widgets | /** State widgets | ||||
| @@ -3,7 +3,7 @@ | |||||
| #include "plugin/Plugin.hpp" | #include "plugin/Plugin.hpp" | ||||
| #include "plugin/Model.hpp" | #include "plugin/Model.hpp" | ||||
| #include <vector> | #include <vector> | ||||
| #include <list> | |||||
| #include <set> | |||||
| namespace rack { | namespace rack { | ||||
| @@ -16,21 +16,21 @@ namespace plugin { | |||||
| void init(bool devMode); | void init(bool devMode); | ||||
| void destroy(); | void destroy(); | ||||
| void logIn(std::string email, std::string password); | |||||
| void logIn(const std::string &email, const std::string &password); | |||||
| void logOut(); | void logOut(); | ||||
| /** Returns whether a new plugin is available, and downloads it unless doing a dry run */ | /** Returns whether a new plugin is available, and downloads it unless doing a dry run */ | ||||
| bool sync(bool dryRun); | bool sync(bool dryRun); | ||||
| void cancelDownload(); | void cancelDownload(); | ||||
| bool isLoggedIn(); | bool isLoggedIn(); | ||||
| Plugin *getPlugin(std::string pluginSlug); | |||||
| Model *getModel(std::string pluginSlug, std::string modelSlug); | |||||
| std::string getAllowedTag(std::string tag); | |||||
| Plugin *getPlugin(const std::string &pluginSlug); | |||||
| Model *getModel(const std::string &pluginSlug, const std::string &modelSlug); | |||||
| std::string normalizeTag(const std::string &tag); | |||||
| /** Checks that the slug contains only alphanumeric characters, "-", and "_" */ | /** Checks that the slug contains only alphanumeric characters, "-", and "_" */ | ||||
| bool isSlugValid(std::string slug); | |||||
| bool isSlugValid(const std::string &slug); | |||||
| extern const std::vector<std::string> allowedTags; | extern const std::vector<std::string> allowedTags; | ||||
| extern std::list<Plugin*> plugins; | |||||
| extern std::vector<Plugin*> plugins; | |||||
| extern bool isDownloading; | extern bool isDownloading; | ||||
| extern float downloadProgress; | extern float downloadProgress; | ||||
| extern std::string downloadName; | extern std::string downloadName; | ||||
| @@ -2,7 +2,7 @@ | |||||
| #include "common.hpp" | #include "common.hpp" | ||||
| #include "plugin/Plugin.hpp" | #include "plugin/Plugin.hpp" | ||||
| #include <jansson.h> | #include <jansson.h> | ||||
| #include <list> | |||||
| #include <set> | |||||
| namespace rack { | namespace rack { | ||||
| @@ -31,7 +31,7 @@ struct Model { | |||||
| /** 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; | ||||
| /** List of tags representing the function(s) of the module */ | /** List of tags representing the function(s) of the module */ | ||||
| std::list<std::string> tags; | |||||
| std::set<std::string> tags; | |||||
| /** A one-line summary of the module's purpose */ | /** A one-line summary of the module's purpose */ | ||||
| std::string description; | std::string description; | ||||
| @@ -0,0 +1,19 @@ | |||||
| #pragma once | |||||
| #include "widget/Widget.hpp" | |||||
| #include "ui/common.hpp" | |||||
| namespace rack { | |||||
| namespace ui { | |||||
| /** Positions children with a margin between the layout's box. */ | |||||
| struct MarginLayout : widget::Widget { | |||||
| math::Vec margin; | |||||
| void step() override; | |||||
| }; | |||||
| } // namespace ui | |||||
| } // namespace rack | |||||
| @@ -40,6 +40,9 @@ struct Widget { | |||||
| void setPos(math::Vec pos); | void setPos(math::Vec pos); | ||||
| void setSize(math::Vec size); | void setSize(math::Vec size); | ||||
| void show(); | |||||
| void hide(); | |||||
| void requestDelete(); | |||||
| virtual math::Rect getChildrenBoundingBox(); | virtual math::Rect getChildrenBoundingBox(); | ||||
| /** Returns `v` transformed into the coordinate system of `relative` */ | /** Returns `v` transformed into the coordinate system of `relative` */ | ||||
| @@ -156,6 +159,8 @@ struct Widget { | |||||
| virtual void onResize(const event::Resize &e) {} | virtual void onResize(const event::Resize &e) {} | ||||
| virtual void onAdd(const event::Add &e) {} | virtual void onAdd(const event::Add &e) {} | ||||
| virtual void onRemove(const event::Remove &e) {} | virtual void onRemove(const event::Remove &e) {} | ||||
| virtual void onShow(const event::Show &e) {recurseEvent(&Widget::onShow, e);} | |||||
| virtual void onHide(const event::Hide &e) {recurseEvent(&Widget::onHide, e);} | |||||
| }; | }; | ||||
| @@ -14,6 +14,7 @@ struct BlankPanel : Widget { | |||||
| void step() override { | void step() override { | ||||
| panelBorder->box.size = box.size; | panelBorder->box.size = box.size; | ||||
| Widget::step(); | |||||
| } | } | ||||
| void draw(const DrawContext &ctx) override { | void draw(const DrawContext &ctx) override { | ||||
| @@ -16,7 +16,7 @@ void LedDisplay::draw(const widget::DrawContext &ctx) { | |||||
| nvgFillColor(ctx.vg, nvgRGB(0x00, 0x00, 0x00)); | nvgFillColor(ctx.vg, nvgRGB(0x00, 0x00, 0x00)); | ||||
| nvgFill(ctx.vg); | nvgFill(ctx.vg); | ||||
| nvgScissor(ctx.vg, 0, 0, box.size.x, box.size.y); | |||||
| nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||||
| widget::Widget::draw(ctx); | widget::Widget::draw(ctx); | ||||
| nvgResetScissor(ctx.vg); | nvgResetScissor(ctx.vg); | ||||
| } | } | ||||
| @@ -79,7 +79,7 @@ LedDisplayTextField::LedDisplayTextField() { | |||||
| void LedDisplayTextField::draw(const widget::DrawContext &ctx) { | void LedDisplayTextField::draw(const widget::DrawContext &ctx) { | ||||
| nvgScissor(ctx.vg, 0, 0, box.size.x, box.size.y); | |||||
| nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||||
| // Background | // Background | ||||
| nvgBeginPath(ctx.vg); | nvgBeginPath(ctx.vg); | ||||
| @@ -4,9 +4,14 @@ | |||||
| #include "widget/ZoomWidget.hpp" | #include "widget/ZoomWidget.hpp" | ||||
| #include "ui/ScrollWidget.hpp" | #include "ui/ScrollWidget.hpp" | ||||
| #include "ui/SequentialLayout.hpp" | #include "ui/SequentialLayout.hpp" | ||||
| #include "ui/MarginLayout.hpp" | |||||
| #include "ui/Label.hpp" | #include "ui/Label.hpp" | ||||
| #include "ui/TextField.hpp" | #include "ui/TextField.hpp" | ||||
| #include "ui/MenuOverlay.hpp" | #include "ui/MenuOverlay.hpp" | ||||
| #include "ui/List.hpp" | |||||
| #include "ui/MenuItem.hpp" | |||||
| #include "ui/Button.hpp" | |||||
| #include "ui/ChoiceButton.hpp" | |||||
| #include "app/ModuleWidget.hpp" | #include "app/ModuleWidget.hpp" | ||||
| #include "app/Scene.hpp" | #include "app/Scene.hpp" | ||||
| #include "plugin.hpp" | #include "plugin.hpp" | ||||
| @@ -68,96 +73,135 @@ struct BrowserOverlay : widget::OpaqueWidget { | |||||
| return; | return; | ||||
| if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | ||||
| this->visible = false; | |||||
| hide(); | |||||
| } | } | ||||
| } | } | ||||
| }; | |||||
| void onHoverKey(const event::HoverKey &e) override { | |||||
| if (e.action == GLFW_PRESS) { | |||||
| switch (e.key) { | |||||
| case GLFW_KEY_ESCAPE: { | |||||
| this->visible = false; | |||||
| e.consume(this); | |||||
| } break; | |||||
| } | |||||
| } | |||||
| if (!e.getConsumed()) | |||||
| widget::OpaqueWidget::onHoverKey(e); | |||||
| } | |||||
| }; | |||||
| static const float MODEL_BOX_ZOOM = 0.5f; | |||||
| struct ModelBox : widget::OpaqueWidget { | struct ModelBox : widget::OpaqueWidget { | ||||
| plugin::Model *model; | plugin::Model *model; | ||||
| widget::Widget *infoWidget; | |||||
| /** Lazily created */ | /** Lazily created */ | ||||
| widget::Widget *previewWidget = NULL; | widget::Widget *previewWidget = NULL; | ||||
| /** Number of frames since draw() has been called */ | /** Number of frames since draw() has been called */ | ||||
| int visibleFrames = 0; | int visibleFrames = 0; | ||||
| bool selected = false; | bool selected = false; | ||||
| ModelBox() { | |||||
| box.size.x = 0.f; | |||||
| box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM); | |||||
| } | |||||
| void setModel(plugin::Model *model) { | void setModel(plugin::Model *model) { | ||||
| this->model = model; | this->model = model; | ||||
| box.size.x = 70.f; | |||||
| box.size.y = std::ceil(RACK_GRID_SIZE.y * 0.5f); | |||||
| infoWidget = new widget::Widget; | |||||
| infoWidget->box.size.x = 140; | |||||
| infoWidget->box.size.y = box.size.y; | |||||
| addChild(infoWidget); | |||||
| math::Vec p; | |||||
| p.y = box.size.y; | |||||
| box.size.y += 40.0; | |||||
| math::Vec pos; | |||||
| ui::Label *nameLabel = new ui::Label; | ui::Label *nameLabel = new ui::Label; | ||||
| // nameLabel->box.size.x = infoWidget->box.size.x; | |||||
| nameLabel->box.pos = pos; | |||||
| nameLabel->text = model->name; | nameLabel->text = model->name; | ||||
| nameLabel->box.pos = p; | |||||
| p.y += nameLabel->box.size.y; | |||||
| addChild(nameLabel); | |||||
| infoWidget->addChild(nameLabel); | |||||
| pos = nameLabel->box.getBottomLeft(); | |||||
| ui::Label *pluginLabel = new ui::Label; | ui::Label *pluginLabel = new ui::Label; | ||||
| // pluginLabel->box.size.x = infoWidget->box.size.x; | |||||
| pluginLabel->box.pos = pos; | |||||
| pluginLabel->text = model->plugin->name; | pluginLabel->text = model->plugin->name; | ||||
| pluginLabel->box.pos = p; | |||||
| p.y += pluginLabel->box.size.y; | |||||
| addChild(pluginLabel); | |||||
| infoWidget->addChild(pluginLabel); | |||||
| pos = pluginLabel->box.getBottomLeft(); | |||||
| ui::Label *descriptionLabel = new ui::Label; | |||||
| descriptionLabel->box.size.x = infoWidget->box.size.x; | |||||
| descriptionLabel->box.pos = pos; | |||||
| descriptionLabel->text = model->description; | |||||
| infoWidget->addChild(descriptionLabel); | |||||
| pos = descriptionLabel->box.getBottomLeft(); | |||||
| pos.y = infoWidget->box.size.y; | |||||
| for (const std::string &tag : model->tags) { | |||||
| ui::Button *tagButton = new ui::Button; | |||||
| tagButton->box.size.x = infoWidget->box.size.x; | |||||
| tagButton->box.pos = pos; | |||||
| tagButton->box.pos.y -= tagButton->box.size.y; | |||||
| tagButton->text = tag; | |||||
| infoWidget->addChild(tagButton); | |||||
| pos = tagButton->box.getTopLeft(); | |||||
| } | |||||
| ui::Button *favoriteButton = new ui::Button; | |||||
| favoriteButton->box.size.x = infoWidget->box.size.x; | |||||
| favoriteButton->box.pos = pos; | |||||
| favoriteButton->box.pos.y -= favoriteButton->box.size.y; | |||||
| favoriteButton->text = "★"; | |||||
| infoWidget->addChild(favoriteButton); | |||||
| pos = favoriteButton->box.getTopLeft(); | |||||
| } | |||||
| void createPreview() { | |||||
| assert(!previewWidget); | |||||
| previewWidget = new widget::TransparentWidget; | |||||
| previewWidget->box.size.y = std::ceil(RACK_GRID_HEIGHT * MODEL_BOX_ZOOM); | |||||
| addChild(previewWidget); | |||||
| widget::FramebufferWidget *fbWidget = new widget::FramebufferWidget; | |||||
| if (math::isNear(APP->window->pixelRatio, 1.0)) { | |||||
| // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer | |||||
| fbWidget->oversample = 2.0; | |||||
| } | |||||
| previewWidget->addChild(fbWidget); | |||||
| widget::ZoomWidget *zoomWidget = new widget::ZoomWidget; | |||||
| zoomWidget->setZoom(MODEL_BOX_ZOOM); | |||||
| fbWidget->addChild(zoomWidget); | |||||
| ModuleWidget *moduleWidget = model->createModuleWidgetNull(); | |||||
| zoomWidget->addChild(moduleWidget); | |||||
| zoomWidget->box.size.x = moduleWidget->box.size.x * MODEL_BOX_ZOOM; | |||||
| zoomWidget->box.size.y = RACK_GRID_HEIGHT * MODEL_BOX_ZOOM; | |||||
| previewWidget->box.size.x = std::ceil(zoomWidget->box.size.x); | |||||
| // Reposition infoWidget | |||||
| infoWidget->box.pos.x = previewWidget->box.size.x; | |||||
| box.size.x = previewWidget->box.size.x + infoWidget->box.size.x; | |||||
| } | |||||
| void deletePreview() { | |||||
| assert(previewWidget); | |||||
| removeChild(previewWidget); | |||||
| delete previewWidget; | |||||
| previewWidget = NULL; | |||||
| } | } | ||||
| void step() override { | void step() override { | ||||
| if (previewWidget && ++visibleFrames >= 60) { | if (previewWidget && ++visibleFrames >= 60) { | ||||
| removeChild(previewWidget); | |||||
| delete previewWidget; | |||||
| previewWidget = NULL; | |||||
| deletePreview(); | |||||
| } | } | ||||
| } | } | ||||
| void draw(const widget::DrawContext &ctx) override { | void draw(const widget::DrawContext &ctx) override { | ||||
| visibleFrames = 0; | visibleFrames = 0; | ||||
| // Lazily create ModuleWidget when drawn | |||||
| // Lazily create preview when drawn | |||||
| if (!previewWidget) { | if (!previewWidget) { | ||||
| widget::Widget *transparentWidget = new widget::TransparentWidget; | |||||
| addChild(transparentWidget); | |||||
| widget::FramebufferWidget *fbWidget = new widget::FramebufferWidget; | |||||
| if (math::isNear(APP->window->pixelRatio, 1.0)) { | |||||
| // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer | |||||
| fbWidget->oversample = 2.0; | |||||
| } | |||||
| transparentWidget->addChild(fbWidget); | |||||
| widget::ZoomWidget *zoomWidget = new widget::ZoomWidget; | |||||
| zoomWidget->setZoom(0.5f); | |||||
| fbWidget->addChild(zoomWidget); | |||||
| ModuleWidget *moduleWidget = model->createModuleWidgetNull(); | |||||
| zoomWidget->addChild(moduleWidget); | |||||
| zoomWidget->box.size.x = moduleWidget->box.size.x * zoomWidget->zoom; | |||||
| zoomWidget->box.size.y = RACK_GRID_HEIGHT; | |||||
| float width = std::ceil(zoomWidget->box.size.x); | |||||
| box.size.x = std::max(box.size.x, width); | |||||
| previewWidget = transparentWidget; | |||||
| createPreview(); | |||||
| } | } | ||||
| nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||||
| widget::OpaqueWidget::draw(ctx); | widget::OpaqueWidget::draw(ctx); | ||||
| nvgResetScissor(ctx.vg); | |||||
| // Translucent overlay when selected | |||||
| if (selected) { | if (selected) { | ||||
| nvgBeginPath(ctx.vg); | nvgBeginPath(ctx.vg); | ||||
| nvgRect(ctx.vg, 0.0, 0.0, box.size.x, box.size.y); | nvgRect(ctx.vg, 0.0, 0.0, box.size.x, box.size.y); | ||||
| @@ -180,20 +224,82 @@ struct ModelBox : widget::OpaqueWidget { | |||||
| struct BrowserSearchField : ui::TextField { | struct BrowserSearchField : ui::TextField { | ||||
| void step() override { | |||||
| // Steal focus when step is called | |||||
| APP->event->setSelected(this); | |||||
| ui::TextField::step(); | |||||
| } | |||||
| void onSelectKey(const event::SelectKey &e) override { | |||||
| if (e.action == GLFW_PRESS) { | |||||
| if (e.key == GLFW_KEY_ESCAPE) { | |||||
| BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>(); | |||||
| overlay->hide(); | |||||
| e.consume(this); | |||||
| } | |||||
| } | |||||
| if (!e.getConsumed()) | |||||
| ui::TextField::onSelectKey(e); | |||||
| } | |||||
| void onChange(const event::Change &e) override; | void onChange(const event::Change &e) override; | ||||
| void onHide(const event::Hide &e) override { | |||||
| setText(""); | |||||
| APP->event->setSelected(NULL); | |||||
| ui::TextField::onHide(e); | |||||
| } | |||||
| }; | }; | ||||
| struct BrowserSidebar : widget::Widget { | struct BrowserSidebar : widget::Widget { | ||||
| BrowserSearchField *searchField; | BrowserSearchField *searchField; | ||||
| ui::List *pluginList; | |||||
| ui::ScrollWidget *pluginScroll; | |||||
| ui::List *tagList; | |||||
| ui::ScrollWidget *tagScroll; | |||||
| BrowserSidebar() { | BrowserSidebar() { | ||||
| searchField = new BrowserSearchField; | searchField = new BrowserSearchField; | ||||
| addChild(searchField); | addChild(searchField); | ||||
| pluginScroll = new ui::ScrollWidget; | |||||
| pluginScroll->box.pos = searchField->box.getBottomLeft(); | |||||
| addChild(pluginScroll); | |||||
| pluginList = new ui::List; | |||||
| pluginScroll->container->addChild(pluginList); | |||||
| std::set<std::string> pluginNames; | |||||
| for (plugin::Plugin *plugin : plugin::plugins) { | |||||
| pluginNames.insert(plugin->name); | |||||
| } | |||||
| for (const std::string &pluginName : pluginNames) { | |||||
| ui::MenuItem *item = new ui::MenuItem; | |||||
| item->text = pluginName; | |||||
| pluginList->addChild(item); | |||||
| } | |||||
| tagScroll = new ui::ScrollWidget; | |||||
| tagScroll->box.pos = searchField->box.getBottomLeft(); | |||||
| addChild(tagScroll); | |||||
| tagList = new ui::List; | |||||
| tagScroll->container->addChild(tagList); | |||||
| for (const std::string &tag : plugin::allowedTags) { | |||||
| ui::MenuItem *item = new ui::MenuItem; | |||||
| item->text = tag; | |||||
| tagList->addChild(item); | |||||
| } | |||||
| } | } | ||||
| void step() override { | void step() override { | ||||
| searchField->box.size.x = box.size.x; | searchField->box.size.x = box.size.x; | ||||
| pluginScroll->box.size.y = box.size.y - searchField->box.size.y; | |||||
| pluginList->box.size.x = pluginScroll->box.size.x = box.size.x / 2; | |||||
| tagScroll->box.pos.x = box.size.x / 2; | |||||
| tagScroll->box.size.y = box.size.y - searchField->box.size.y; | |||||
| tagList->box.size.x = tagScroll->box.size.x = box.size.x / 2; | |||||
| widget::Widget::step(); | widget::Widget::step(); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -202,6 +308,7 @@ struct BrowserSidebar : widget::Widget { | |||||
| struct ModuleBrowser : widget::OpaqueWidget { | struct ModuleBrowser : widget::OpaqueWidget { | ||||
| BrowserSidebar *sidebar; | BrowserSidebar *sidebar; | ||||
| ui::ScrollWidget *modelScroll; | ui::ScrollWidget *modelScroll; | ||||
| ui::MarginLayout *modelMargin; | |||||
| ui::SequentialLayout *modelContainer; | ui::SequentialLayout *modelContainer; | ||||
| ModuleBrowser() { | ModuleBrowser() { | ||||
| @@ -212,9 +319,13 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||||
| modelScroll = new ui::ScrollWidget; | modelScroll = new ui::ScrollWidget; | ||||
| addChild(modelScroll); | addChild(modelScroll); | ||||
| modelMargin = new ui::MarginLayout; | |||||
| modelMargin->margin = math::Vec(20, 20); | |||||
| modelScroll->container->addChild(modelMargin); | |||||
| modelContainer = new ui::SequentialLayout; | modelContainer = new ui::SequentialLayout; | ||||
| modelContainer->spacing = math::Vec(10, 10); | |||||
| modelScroll->container->addChild(modelContainer); | |||||
| modelContainer->spacing = math::Vec(20, 20); | |||||
| modelMargin->addChild(modelContainer); | |||||
| for (plugin::Plugin *plugin : plugin::plugins) { | for (plugin::Plugin *plugin : plugin::plugins) { | ||||
| for (plugin::Model *model : plugin->models) { | for (plugin::Model *model : plugin->models) { | ||||
| @@ -233,8 +344,8 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||||
| modelScroll->box.pos.x = sidebar->box.size.x; | modelScroll->box.pos.x = sidebar->box.size.x; | ||||
| modelScroll->box.size.x = box.size.x - sidebar->box.size.x; | modelScroll->box.size.x = box.size.x - sidebar->box.size.x; | ||||
| modelScroll->box.size.y = box.size.y; | modelScroll->box.size.y = box.size.y; | ||||
| modelContainer->box.size.x = modelScroll->box.size.x; | |||||
| modelContainer->box.size.y = modelContainer->getChildrenBoundingBox().getBottomRight().y; | |||||
| modelMargin->box.size.x = modelScroll->box.size.x; | |||||
| modelMargin->box.size.y = modelContainer->getChildrenBoundingBox().size.y + 2 * modelMargin->margin.y; | |||||
| widget::OpaqueWidget::step(); | widget::OpaqueWidget::step(); | ||||
| } | } | ||||
| @@ -251,6 +362,8 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||||
| bool match = isModelMatch(modelBox->model, search); | bool match = isModelMatch(modelBox->model, search); | ||||
| modelBox->visible = match; | modelBox->visible = match; | ||||
| } | } | ||||
| // Reset scroll position | |||||
| modelScroll->offset = math::Vec(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -259,6 +372,10 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||||
| void ModelBox::onButton(const event::Button &e) { | void ModelBox::onButton(const event::Button &e) { | ||||
| widget::OpaqueWidget::onButton(e); | |||||
| if (e.getConsumed() != this) | |||||
| return; | |||||
| if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | ||||
| // Create module | // Create module | ||||
| ModuleWidget *moduleWidget = model->createModuleWidget(); | ModuleWidget *moduleWidget = model->createModuleWidget(); | ||||
| @@ -268,9 +385,9 @@ void ModelBox::onButton(const event::Button &e) { | |||||
| // Pretend the moduleWidget was clicked so it can be dragged in the RackWidget | // Pretend the moduleWidget was clicked so it can be dragged in the RackWidget | ||||
| e.consume(moduleWidget); | e.consume(moduleWidget); | ||||
| // Close Module Browser | |||||
| // Hide Module Browser | |||||
| BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>(); | BrowserOverlay *overlay = getAncestorOfType<BrowserOverlay>(); | ||||
| overlay->visible = false; | |||||
| overlay->hide(); | |||||
| // Push ModuleAdd history action | // Push ModuleAdd history action | ||||
| history::ModuleAdd *h = new history::ModuleAdd; | history::ModuleAdd *h = new history::ModuleAdd; | ||||
| @@ -278,7 +395,6 @@ void ModelBox::onButton(const event::Button &e) { | |||||
| h->setModule(moduleWidget); | h->setModule(moduleWidget); | ||||
| APP->history->push(h); | APP->history->push(h); | ||||
| } | } | ||||
| widget::OpaqueWidget::onButton(e); | |||||
| } | } | ||||
| void BrowserSearchField::onChange(const event::Change &e) { | void BrowserSearchField::onChange(const event::Change &e) { | ||||
| @@ -133,15 +133,21 @@ struct ModuleDeleteItem : ui::MenuItem { | |||||
| }; | }; | ||||
| ModuleWidget::ModuleWidget() { | |||||
| box.size = math::Vec(0, RACK_GRID_HEIGHT); | |||||
| } | |||||
| ModuleWidget::~ModuleWidget() { | ModuleWidget::~ModuleWidget() { | ||||
| setModule(NULL); | setModule(NULL); | ||||
| } | } | ||||
| void ModuleWidget::draw(const widget::DrawContext &ctx) { | void ModuleWidget::draw(const widget::DrawContext &ctx) { | ||||
| nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||||
| if (module && module->bypass) { | if (module && module->bypass) { | ||||
| nvgGlobalAlpha(ctx.vg, 0.25); | nvgGlobalAlpha(ctx.vg, 0.25); | ||||
| } | } | ||||
| // nvgScissor(ctx.vg, 0, 0, box.size.x, box.size.y); | |||||
| widget::Widget::draw(ctx); | widget::Widget::draw(ctx); | ||||
| // Power meter | // Power meter | ||||
| @@ -175,7 +181,7 @@ void ModuleWidget::draw(const widget::DrawContext &ctx) { | |||||
| // bndLabel(ctx.vg, 0, 0, INFINITY, INFINITY, -1, debugText.c_str()); | // bndLabel(ctx.vg, 0, 0, INFINITY, INFINITY, -1, debugText.c_str()); | ||||
| // } | // } | ||||
| // nvgResetScissor(ctx.vg); | |||||
| nvgResetScissor(ctx.vg); | |||||
| } | } | ||||
| void ModuleWidget::drawShadow(const widget::DrawContext &ctx) { | void ModuleWidget::drawShadow(const widget::DrawContext &ctx) { | ||||
| @@ -307,13 +313,11 @@ void ModuleWidget::setPanel(std::shared_ptr<Svg> svg) { | |||||
| panel = NULL; | panel = NULL; | ||||
| } | } | ||||
| { | |||||
| SvgPanel *panel = new SvgPanel; | |||||
| panel->setBackground(svg); | |||||
| addChild(panel); | |||||
| box.size = panel->box.size; | |||||
| this->panel = panel; | |||||
| } | |||||
| SvgPanel *svgPanel = new SvgPanel; | |||||
| svgPanel->setBackground(svg); | |||||
| addChild(svgPanel); | |||||
| box.size.x = svgPanel->box.size.x; | |||||
| panel = svgPanel; | |||||
| } | } | ||||
| void ModuleWidget::addParam(ParamWidget *param) { | void ModuleWidget::addParam(ParamWidget *param) { | ||||
| @@ -17,6 +17,14 @@ namespace rack { | |||||
| namespace engine { | namespace engine { | ||||
| void disableDenormals() { | |||||
| // Set CPU to flush-to-zero (FTZ) and denormals-are-zero (DAZ) mode | |||||
| // https://software.intel.com/en-us/node/682949 | |||||
| _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); | |||||
| _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); | |||||
| } | |||||
| /** Threads which obtain a VIPLock will cause wait() to block for other less important threads. | /** Threads which obtain a VIPLock will cause wait() to block for other less important threads. | ||||
| This does not provide the VIPs with an exclusive lock. That should be left up to another mutex shared between the less important thread. | This does not provide the VIPs with an exclusive lock. That should be left up to another mutex shared between the less important thread. | ||||
| */ | */ | ||||
| @@ -111,6 +119,7 @@ struct EngineWorker { | |||||
| void run() { | void run() { | ||||
| system::setThreadName("Engine worker"); | system::setThreadName("Engine worker"); | ||||
| system::setThreadRealTime(); | system::setThreadRealTime(); | ||||
| disableDenormals(); | |||||
| while (running) { | while (running) { | ||||
| step(); | step(); | ||||
| } | } | ||||
| @@ -179,7 +188,7 @@ Engine::~Engine() { | |||||
| static void Engine_stepModules(Engine *engine, int threadId) { | static void Engine_stepModules(Engine *engine, int threadId) { | ||||
| Engine::Internal *internal = engine->internal; | Engine::Internal *internal = engine->internal; | ||||
| int threadCount = internal->threadCount; | |||||
| // int threadCount = internal->threadCount; | |||||
| int modulesLen = internal->modules.size(); | int modulesLen = internal->modules.size(); | ||||
| // Step each module | // Step each module | ||||
| @@ -259,11 +268,7 @@ static void Engine_run(Engine *engine) { | |||||
| // Set up thread | // Set up thread | ||||
| system::setThreadName("Engine"); | system::setThreadName("Engine"); | ||||
| system::setThreadRealTime(); | system::setThreadRealTime(); | ||||
| // Set CPU to flush-to-zero (FTZ) and denormals-are-zero (DAZ) mode | |||||
| // https://software.intel.com/en-us/node/682949 | |||||
| _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); | |||||
| _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); | |||||
| disableDenormals(); | |||||
| // Every time the engine waits and locks a mutex, it steps this many frames | // Every time the engine waits and locks a mutex, it steps this many frames | ||||
| const int mutexSteps = 64; | const int mutexSteps = 64; | ||||
| @@ -116,6 +116,17 @@ static bool loadPlugin(std::string path) { | |||||
| initCallback(plugin); | initCallback(plugin); | ||||
| plugin->fromJson(rootJ); | plugin->fromJson(rootJ); | ||||
| // Normalize tags | |||||
| for (Model *model : plugin->models) { | |||||
| std::set<std::string> normalizedTags; | |||||
| for (const std::string &tag : model->tags) { | |||||
| std::string normalizedTag = normalizeTag(tag); | |||||
| if (!normalizedTag.empty()) | |||||
| normalizedTags.insert(normalizedTag); | |||||
| } | |||||
| model->tags = normalizedTags; | |||||
| } | |||||
| // Check slug | // Check slug | ||||
| if (!isSlugValid(plugin->slug)) { | if (!isSlugValid(plugin->slug)) { | ||||
| WARN("Plugin slug \"%s\" is invalid", plugin->slug.c_str()); | WARN("Plugin slug \"%s\" is invalid", plugin->slug.c_str()); | ||||
| @@ -305,7 +316,7 @@ static int extractZip(const char *filename, const char *path) { | |||||
| return err; | return err; | ||||
| } | } | ||||
| static void extractPackages(std::string path) { | |||||
| static void extractPackages(const std::string &path) { | |||||
| std::string message; | std::string message; | ||||
| for (std::string packagePath : system::listEntries(path)) { | for (std::string packagePath : system::listEntries(path)) { | ||||
| @@ -374,7 +385,7 @@ void destroy() { | |||||
| plugins.clear(); | plugins.clear(); | ||||
| } | } | ||||
| void logIn(std::string email, std::string password) { | |||||
| void logIn(const std::string &email, const std::string &password) { | |||||
| json_t *reqJ = json_object(); | json_t *reqJ = json_object(); | ||||
| json_object_set(reqJ, "email", json_string(email.c_str())); | json_object_set(reqJ, "email", json_string(email.c_str())); | ||||
| json_object_set(reqJ, "password", json_string(password.c_str())); | json_object_set(reqJ, "password", json_string(password.c_str())); | ||||
| @@ -496,7 +507,7 @@ bool isLoggedIn() { | |||||
| return settings.token != ""; | return settings.token != ""; | ||||
| } | } | ||||
| Plugin *getPlugin(std::string pluginSlug) { | |||||
| Plugin *getPlugin(const std::string &pluginSlug) { | |||||
| for (Plugin *plugin : plugins) { | for (Plugin *plugin : plugins) { | ||||
| if (plugin->slug == pluginSlug) { | if (plugin->slug == pluginSlug) { | ||||
| return plugin; | return plugin; | ||||
| @@ -505,7 +516,7 @@ Plugin *getPlugin(std::string pluginSlug) { | |||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| Model *getModel(std::string pluginSlug, std::string modelSlug) { | |||||
| Model *getModel(const std::string &pluginSlug, const std::string &modelSlug) { | |||||
| Plugin *plugin = getPlugin(pluginSlug); | Plugin *plugin = getPlugin(pluginSlug); | ||||
| if (!plugin) | if (!plugin) | ||||
| return NULL; | return NULL; | ||||
| @@ -599,21 +610,21 @@ const std::map<std::string, std::string> tagAliases = { | |||||
| }; | }; | ||||
| std::string getAllowedTag(std::string tag) { | |||||
| tag = string::lowercase(tag); | |||||
| std::string normalizeTag(const std::string &tag) { | |||||
| std::string lowercaseTag = string::lowercase(tag); | |||||
| // Transform aliases | // Transform aliases | ||||
| auto it = tagAliases.find(tag); | |||||
| auto it = tagAliases.find(lowercaseTag); | |||||
| if (it != tagAliases.end()) | if (it != tagAliases.end()) | ||||
| tag = it->second; | |||||
| lowercaseTag = it->second; | |||||
| // Find allowed tag | // Find allowed tag | ||||
| for (std::string allowedTag : allowedTags) { | for (std::string allowedTag : allowedTags) { | ||||
| if (tag == string::lowercase(allowedTag)) | |||||
| if (lowercaseTag == string::lowercase(allowedTag)) | |||||
| return allowedTag; | return allowedTag; | ||||
| } | } | ||||
| return ""; | return ""; | ||||
| } | } | ||||
| bool isSlugValid(std::string slug) { | |||||
| bool isSlugValid(const std::string &slug) { | |||||
| for (char c : slug) { | for (char c : slug) { | ||||
| if (!(std::isalnum(c) || c == '-' || c == '_')) | if (!(std::isalnum(c) || c == '-' || c == '_')) | ||||
| return false; | return false; | ||||
| @@ -622,7 +633,7 @@ bool isSlugValid(std::string slug) { | |||||
| } | } | ||||
| std::list<Plugin*> plugins; | |||||
| std::vector<Plugin*> plugins; | |||||
| bool isDownloading = false; | bool isDownloading = false; | ||||
| float downloadProgress = 0.f; | float downloadProgress = 0.f; | ||||
| std::string downloadName; | std::string downloadName; | ||||
| @@ -20,7 +20,7 @@ void Model::fromJson(json_t *rootJ) { | |||||
| json_t *tagJ; | json_t *tagJ; | ||||
| json_array_foreach(tagsJ, i, tagJ) { | json_array_foreach(tagsJ, i, tagJ) { | ||||
| std::string tag = json_string_value(tagJ); | std::string tag = json_string_value(tagJ); | ||||
| tags.push_back(tag); | |||||
| tags.insert(tag); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,20 @@ | |||||
| #include "ui/MarginLayout.hpp" | |||||
| #include <vector> | |||||
| namespace rack { | |||||
| namespace ui { | |||||
| void MarginLayout::step() { | |||||
| widget::Widget::step(); | |||||
| math::Rect childBox = box.zeroPos().grow(margin.neg()); | |||||
| for (Widget *child : children) { | |||||
| child->box = childBox; | |||||
| } | |||||
| } | |||||
| } // namespace ui | |||||
| } // namespace rack | |||||
| @@ -78,7 +78,9 @@ void MenuItem::doAction() { | |||||
| return; | return; | ||||
| widget::Widget *overlay = getAncestorOfType<MenuOverlay>(); | widget::Widget *overlay = getAncestorOfType<MenuOverlay>(); | ||||
| overlay->requestedDelete = true; | |||||
| if (overlay) { | |||||
| overlay->requestedDelete = true; | |||||
| } | |||||
| } | } | ||||
| @@ -8,9 +8,6 @@ namespace rack { | |||||
| namespace ui { | namespace ui { | ||||
| static const float SCROLLBAR_SENSITIVITY = 2.f; | |||||
| ScrollBar::ScrollBar() { | ScrollBar::ScrollBar() { | ||||
| box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | box.size = math::Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); | ||||
| } | } | ||||
| @@ -26,12 +23,14 @@ void ScrollBar::onDragStart(const event::DragStart &e) { | |||||
| } | } | ||||
| void ScrollBar::onDragMove(const event::DragMove &e) { | void ScrollBar::onDragMove(const event::DragMove &e) { | ||||
| const float sensitivity = 1.f; | |||||
| ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent); | ScrollWidget *scrollWidget = dynamic_cast<ScrollWidget*>(parent); | ||||
| assert(scrollWidget); | assert(scrollWidget); | ||||
| if (orientation == HORIZONTAL) | if (orientation == HORIZONTAL) | ||||
| scrollWidget->offset.x += SCROLLBAR_SENSITIVITY * e.mouseDelta.x; | |||||
| scrollWidget->offset.x += sensitivity * e.mouseDelta.x; | |||||
| else | else | ||||
| scrollWidget->offset.y += SCROLLBAR_SENSITIVITY * e.mouseDelta.y; | |||||
| scrollWidget->offset.y += sensitivity * e.mouseDelta.y; | |||||
| } | } | ||||
| void ScrollBar::onDragEnd(const event::DragEnd &e) { | void ScrollBar::onDragEnd(const event::DragEnd &e) { | ||||
| @@ -28,7 +28,7 @@ void ScrollWidget::scrollTo(math::Rect r) { | |||||
| } | } | ||||
| void ScrollWidget::draw(const widget::DrawContext &ctx) { | void ScrollWidget::draw(const widget::DrawContext &ctx) { | ||||
| nvgScissor(ctx.vg, 0, 0, box.size.x, box.size.y); | |||||
| nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||||
| widget::Widget::draw(ctx); | widget::Widget::draw(ctx); | ||||
| nvgResetScissor(ctx.vg); | nvgResetScissor(ctx.vg); | ||||
| } | } | ||||
| @@ -6,8 +6,9 @@ namespace rack { | |||||
| namespace ui { | namespace ui { | ||||
| #define X(_v) (orientation == HORIZONTAL_ORIENTATION ? (_v).x : (_v).y) | |||||
| #define Y(_v) (orientation == HORIZONTAL_ORIENTATION ? (_v).y : (_v).x) | |||||
| #define X(v) (orientation == HORIZONTAL_ORIENTATION ? (v).x : (v).y) | |||||
| #define Y(v) (orientation == HORIZONTAL_ORIENTATION ? (v).y : (v).x) | |||||
| void SequentialLayout::step() { | void SequentialLayout::step() { | ||||
| widget::Widget::step(); | widget::Widget::step(); | ||||
| @@ -9,7 +9,7 @@ TextField::TextField() { | |||||
| } | } | ||||
| void TextField::draw(const widget::DrawContext &ctx) { | void TextField::draw(const widget::DrawContext &ctx) { | ||||
| nvgScissor(ctx.vg, 0, 0, box.size.x, box.size.y); | |||||
| nvgScissor(ctx.vg, RECT_ARGS(ctx.clipBox)); | |||||
| BNDwidgetState state; | BNDwidgetState state; | ||||
| if (this == APP->event->selectedWidget) | if (this == APP->event->selectedWidget) | ||||
| @@ -180,7 +180,6 @@ void TextField::onSelectKey(const event::SelectKey &e) { | |||||
| } | } | ||||
| } | } | ||||
| /** Inserts text at the cursor, replacing the selection if necessary */ | |||||
| void TextField::insertText(std::string text) { | void TextField::insertText(std::string text) { | ||||
| if (cursor != selection) { | if (cursor != selection) { | ||||
| int begin = std::min(cursor, selection); | int begin = std::min(cursor, selection); | ||||
| @@ -194,12 +193,15 @@ void TextField::insertText(std::string text) { | |||||
| onChange(eChange); | onChange(eChange); | ||||
| } | } | ||||
| /** Replaces the entire text */ | |||||
| void TextField::setText(std::string text) { | void TextField::setText(std::string text) { | ||||
| bool changed = (text != this->text); | |||||
| this->text = text; | this->text = text; | ||||
| selection = cursor = text.size(); | selection = cursor = text.size(); | ||||
| event::Change eChange; | |||||
| onChange(eChange); | |||||
| if (changed) { | |||||
| // event::Change | |||||
| event::Change eChange; | |||||
| onChange(eChange); | |||||
| } | |||||
| } | } | ||||
| void TextField::selectAll() { | void TextField::selectAll() { | ||||
| @@ -28,6 +28,28 @@ void Widget::setSize(math::Vec size) { | |||||
| onResize(eResize); | onResize(eResize); | ||||
| } | } | ||||
| void Widget::show() { | |||||
| if (visible) | |||||
| return; | |||||
| visible = true; | |||||
| // event::Show | |||||
| event::Show eShow; | |||||
| onShow(eShow); | |||||
| } | |||||
| void Widget::hide() { | |||||
| if (!visible) | |||||
| return; | |||||
| visible = false; | |||||
| // event::Hide | |||||
| event::Hide eHide; | |||||
| onHide(eHide); | |||||
| } | |||||
| void Widget::requestDelete() { | |||||
| requestedDelete = true; | |||||
| } | |||||
| math::Rect Widget::getChildrenBoundingBox() { | math::Rect Widget::getChildrenBoundingBox() { | ||||
| math::Vec min = math::Vec(INFINITY, INFINITY); | math::Vec min = math::Vec(INFINITY, INFINITY); | ||||
| math::Vec max = math::Vec(-INFINITY, -INFINITY); | math::Vec max = math::Vec(-INFINITY, -INFINITY); | ||||