@@ -312,6 +312,7 @@ struct SVGScrew : FramebufferWidget { | |||||
struct Toolbar : OpaqueWidget { | struct Toolbar : OpaqueWidget { | ||||
Slider *wireOpacitySlider; | Slider *wireOpacitySlider; | ||||
Slider *wireTensionSlider; | Slider *wireTensionSlider; | ||||
Slider *zoomSlider; | |||||
RadioButton *cpuUsageButton; | RadioButton *cpuUsageButton; | ||||
RadioButton *plugLightButton; | RadioButton *plugLightButton; | ||||
@@ -21,6 +21,8 @@ extern NVGcontext *gFramebufferVg; | |||||
extern std::shared_ptr<Font> gGuiFont; | extern std::shared_ptr<Font> gGuiFont; | ||||
extern float gPixelRatio; | extern float gPixelRatio; | ||||
extern bool gAllowCursorLock; | extern bool gAllowCursorLock; | ||||
extern int gGuiFrame; | |||||
extern Vec gMousePos; | |||||
void guiInit(); | void guiInit(); | ||||
@@ -191,6 +191,12 @@ struct Vec { | |||||
Vec round() { | Vec round() { | ||||
return Vec(roundf(x), roundf(y)); | return Vec(roundf(x), roundf(y)); | ||||
} | } | ||||
Vec floor() { | |||||
return Vec(floorf(x), floorf(y)); | |||||
} | |||||
Vec ceil() { | |||||
return Vec(ceilf(x), ceilf(y)); | |||||
} | |||||
bool isZero() { | bool isZero() { | ||||
return x == 0.0 && y == 0.0; | return x == 0.0 && y == 0.0; | ||||
} | } | ||||
@@ -216,10 +216,6 @@ Events are not passed to the underlying scene. | |||||
struct FramebufferWidget : virtual Widget { | struct FramebufferWidget : virtual Widget { | ||||
/** Set this to true to re-render the children to the framebuffer in the next step() override */ | /** Set this to true to re-render the children to the framebuffer in the next step() override */ | ||||
bool dirty = true; | bool dirty = true; | ||||
/** A margin in pixels around the children in the framebuffer | |||||
This prevents cutting the rendered SVG off on the box edges. | |||||
*/ | |||||
float oversample = 2.0; | |||||
/** The root object in the framebuffer scene | /** The root object in the framebuffer scene | ||||
The FramebufferWidget owns the pointer | The FramebufferWidget owns the pointer | ||||
*/ | */ | ||||
@@ -353,6 +349,7 @@ struct Slider : OpaqueWidget, QuantityWidget { | |||||
void onDragStart() override; | void onDragStart() override; | ||||
void onDragMove(Vec mouseRel) override; | void onDragMove(Vec mouseRel) override; | ||||
void onDragEnd() override; | void onDragEnd() override; | ||||
void onMouseDownOpaque(int button) override; | |||||
}; | }; | ||||
/** Parent must be a ScrollWidget */ | /** Parent must be a ScrollWidget */ | ||||
@@ -438,12 +435,10 @@ struct Scene : OpaqueWidget { | |||||
// globals | // globals | ||||
//////////////////// | //////////////////// | ||||
extern Vec gMousePos; | |||||
extern Widget *gHoveredWidget; | extern Widget *gHoveredWidget; | ||||
extern Widget *gDraggedWidget; | extern Widget *gDraggedWidget; | ||||
extern Widget *gDragHoveredWidget; | extern Widget *gDragHoveredWidget; | ||||
extern Widget *gFocusedWidget; | extern Widget *gFocusedWidget; | ||||
extern int gGuiFrame; | |||||
extern Scene *gScene; | extern Scene *gScene; | ||||
@@ -31,7 +31,6 @@ RackScene::RackScene() { | |||||
scrollWidget = new RackScrollWidget(); | scrollWidget = new RackScrollWidget(); | ||||
{ | { | ||||
zoomWidget = new ZoomWidget(); | zoomWidget = new ZoomWidget(); | ||||
zoomWidget->zoom = 0.5; | |||||
{ | { | ||||
assert(!gRackWidget); | assert(!gRackWidget); | ||||
gRackWidget = new RackWidget(); | gRackWidget = new RackWidget(); | ||||
@@ -63,6 +62,9 @@ void RackScene::step() { | |||||
.plus(Vec(500, 500)) | .plus(Vec(500, 500)) | ||||
.div(zoomWidget->zoom); | .div(zoomWidget->zoom); | ||||
// Set zoom from the toolbar's zoom slider | |||||
zoomWidget->zoom = gToolbar->zoomSlider->value / 100.0; | |||||
Scene::step(); | Scene::step(); | ||||
zoomWidget->box.size = gRackWidget->box.size.mult(zoomWidget->zoom); | zoomWidget->box.size = gRackWidget->box.size.mult(zoomWidget->zoom); | ||||
@@ -1,4 +1,5 @@ | |||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "gui.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -189,9 +189,9 @@ void RackWidget::fromJson(json_t *rootJ) { | |||||
// version | // version | ||||
json_t *versionJ = json_object_get(rootJ, "version"); | json_t *versionJ = json_object_get(rootJ, "version"); | ||||
if (versionJ) { | if (versionJ) { | ||||
const char *version = json_string_value(versionJ); | |||||
if (gApplicationVersion != version) | |||||
message += stringf("This patch was created with Rack %s. Saving it will convert it to a Rack %s patch.\n\n", version, gApplicationVersion.c_str()); | |||||
std::string version = json_string_value(versionJ); | |||||
if (!version.empty() && gApplicationVersion != version) | |||||
message += stringf("This patch was created with Rack %s. Saving it will convert it to a Rack %s patch.\n\n", version.c_str(), gApplicationVersion.empty() ? "dev" : gApplicationVersion.c_str()); | |||||
} | } | ||||
// modules | // modules | ||||
@@ -139,13 +139,26 @@ Toolbar::Toolbar() { | |||||
wireTensionSlider->box.pos = Vec(xPos, margin); | wireTensionSlider->box.pos = Vec(xPos, margin); | ||||
wireTensionSlider->box.size.x = 150; | wireTensionSlider->box.size.x = 150; | ||||
wireTensionSlider->label = "Cable tension"; | wireTensionSlider->label = "Cable tension"; | ||||
// wireTensionSlider->unit = ""; | |||||
wireTensionSlider->unit = ""; | |||||
wireTensionSlider->setLimits(0.0, 1.0); | wireTensionSlider->setLimits(0.0, 1.0); | ||||
wireTensionSlider->setDefaultValue(0.5); | wireTensionSlider->setDefaultValue(0.5); | ||||
addChild(wireTensionSlider); | addChild(wireTensionSlider); | ||||
xPos += wireTensionSlider->box.size.x; | xPos += wireTensionSlider->box.size.x; | ||||
} | } | ||||
xPos += margin; | |||||
{ | |||||
zoomSlider = new Slider(); | |||||
zoomSlider->box.pos = Vec(xPos, margin); | |||||
zoomSlider->box.size.x = 150; | |||||
zoomSlider->label = "Zoom"; | |||||
zoomSlider->unit = "%"; | |||||
zoomSlider->setLimits(50.0, 200.0); | |||||
zoomSlider->setDefaultValue(100.0); | |||||
addChild(zoomSlider); | |||||
xPos += zoomSlider->box.size.x; | |||||
} | |||||
xPos += margin; | xPos += margin; | ||||
{ | { | ||||
plugLightButton = new RadioButton(); | plugLightButton = new RadioButton(); | ||||
@@ -1,6 +1,7 @@ | |||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "engine.hpp" | #include "engine.hpp" | ||||
#include "components.hpp" | #include "components.hpp" | ||||
#include "gui.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -33,6 +33,8 @@ NVGcontext *gFramebufferVg = NULL; | |||||
std::shared_ptr<Font> gGuiFont; | std::shared_ptr<Font> gGuiFont; | ||||
float gPixelRatio = 0.0; | float gPixelRatio = 0.0; | ||||
bool gAllowCursorLock = true; | bool gAllowCursorLock = true; | ||||
int gGuiFrame; | |||||
Vec gMousePos; | |||||
void windowSizeCallback(GLFWwindow* window, int width, int height) { | void windowSizeCallback(GLFWwindow* window, int width, int height) { | ||||
@@ -27,6 +27,11 @@ static json_t *settingsToJson() { | |||||
json_t *tensionJ = json_real(tension); | json_t *tensionJ = json_real(tension); | ||||
json_object_set_new(rootJ, "wireTension", tensionJ); | json_object_set_new(rootJ, "wireTension", tensionJ); | ||||
// zoom | |||||
float zoom = gToolbar->zoomSlider->value; | |||||
json_t *zoomJ = json_real(zoom); | |||||
json_object_set_new(rootJ, "zoom", zoomJ); | |||||
// allowCursorLock | // allowCursorLock | ||||
json_t *allowCursorLockJ = json_boolean(gAllowCursorLock); | json_t *allowCursorLockJ = json_boolean(gAllowCursorLock); | ||||
json_object_set_new(rootJ, "allowCursorLock", allowCursorLockJ); | json_object_set_new(rootJ, "allowCursorLock", allowCursorLockJ); | ||||
@@ -62,6 +67,11 @@ static void settingsFromJson(json_t *rootJ) { | |||||
if (tensionJ) | if (tensionJ) | ||||
gToolbar->wireTensionSlider->value = json_number_value(tensionJ); | gToolbar->wireTensionSlider->value = json_number_value(tensionJ); | ||||
// zoom | |||||
json_t *zoomJ = json_object_get(rootJ, "zoom"); | |||||
if (zoomJ) | |||||
gToolbar->zoomSlider->value = json_number_value(zoomJ); | |||||
// allowCursorLock | // allowCursorLock | ||||
json_t *allowCursorLockJ = json_object_get(rootJ, "allowCursorLock"); | json_t *allowCursorLockJ = json_object_get(rootJ, "allowCursorLock"); | ||||
if (allowCursorLockJ) | if (allowCursorLockJ) | ||||
@@ -2,12 +2,10 @@ | |||||
namespace rack { | namespace rack { | ||||
Vec gMousePos; | |||||
Widget *gHoveredWidget = NULL; | Widget *gHoveredWidget = NULL; | ||||
Widget *gDraggedWidget = NULL; | Widget *gDraggedWidget = NULL; | ||||
Widget *gDragHoveredWidget = NULL; | Widget *gDragHoveredWidget = NULL; | ||||
Widget *gFocusedWidget = NULL; | Widget *gFocusedWidget = NULL; | ||||
int gGuiFrame; | |||||
Scene *gScene = NULL; | Scene *gScene = NULL; | ||||
@@ -9,9 +9,16 @@ | |||||
namespace rack { | namespace rack { | ||||
/** A margin in pixels around the children in the framebuffer | |||||
This prevents cutting the rendered SVG off on the box edges. | |||||
*/ | |||||
static const float oversample = 2.0; | |||||
struct FramebufferWidget::Internal { | struct FramebufferWidget::Internal { | ||||
NVGLUframebuffer *fb = NULL; | NVGLUframebuffer *fb = NULL; | ||||
Rect box; | Rect box; | ||||
Vec lastS; | |||||
~Internal() { | ~Internal() { | ||||
setFramebuffer(NULL); | setFramebuffer(NULL); | ||||
@@ -33,10 +40,31 @@ FramebufferWidget::~FramebufferWidget() { | |||||
} | } | ||||
void FramebufferWidget::draw(NVGcontext *vg) { | void FramebufferWidget::draw(NVGcontext *vg) { | ||||
// Bypass framebuffer rendering entirely | |||||
// Widget::draw(vg); | |||||
// return; | |||||
// Get world transform | |||||
float xform[6]; | |||||
nvgCurrentTransform(vg, xform); | |||||
// Skew is not supported | |||||
assert(fabsf(xform[1]) < 1e-6); | |||||
assert(fabsf(xform[2]) < 1e-6); | |||||
Vec s = Vec(xform[0], xform[3]); | |||||
Vec b = Vec(xform[4], xform[5]); | |||||
if (gGuiFrame % 10 == 0) { | |||||
// Check if scale has changed | |||||
if (s.x != internal->lastS.x || s.y != internal->lastS.y) { | |||||
dirty = true; | |||||
} | |||||
internal->lastS = s; | |||||
} | |||||
// Render to framebuffer | |||||
if (dirty) { | if (dirty) { | ||||
internal->box.pos = Vec(0, 0); | internal->box.pos = Vec(0, 0); | ||||
internal->box.size = box.size; | |||||
internal->box.size = Vec(ceilf(internal->box.size.x), ceilf(internal->box.size.y)); | |||||
internal->box.size = box.size.mult(s).ceil(); | |||||
Vec fbSize = internal->box.size.mult(gPixelRatio * oversample); | Vec fbSize = internal->box.size.mult(gPixelRatio * oversample); | ||||
// assert(fbSize.isFinite()); | // assert(fbSize.isFinite()); | ||||
@@ -60,6 +88,8 @@ void FramebufferWidget::draw(NVGcontext *vg) { | |||||
nvgBeginFrame(gFramebufferVg, fbSize.x, fbSize.y, gPixelRatio * oversample); | nvgBeginFrame(gFramebufferVg, fbSize.x, fbSize.y, gPixelRatio * oversample); | ||||
nvgScale(gFramebufferVg, gPixelRatio * oversample, gPixelRatio * oversample); | nvgScale(gFramebufferVg, gPixelRatio * oversample, gPixelRatio * oversample); | ||||
// Use local scaling | |||||
nvgScale(gFramebufferVg, s.x, s.y); | |||||
Widget::draw(gFramebufferVg); | Widget::draw(gFramebufferVg); | ||||
nvgEndFrame(gFramebufferVg); | nvgEndFrame(gFramebufferVg); | ||||
@@ -72,32 +102,24 @@ void FramebufferWidget::draw(NVGcontext *vg) { | |||||
return; | return; | ||||
} | } | ||||
// Draw framebuffer image | |||||
// Draw framebuffer image, using world coordinates | |||||
b = b.round(); | |||||
nvgSave(vg); | |||||
nvgResetTransform(vg); | |||||
nvgTranslate(vg, b.x, b.y); | |||||
nvgBeginPath(vg); | nvgBeginPath(vg); | ||||
nvgRect(vg, internal->box.pos.x, internal->box.pos.y, internal->box.size.x, internal->box.size.y); | nvgRect(vg, internal->box.pos.x, internal->box.pos.y, internal->box.size.x, internal->box.size.y); | ||||
NVGpaint paint = nvgImagePattern(vg, internal->box.pos.x, internal->box.pos.y, internal->box.size.x, internal->box.size.y, 0.0, internal->fb->image, 1.0); | NVGpaint paint = nvgImagePattern(vg, internal->box.pos.x, internal->box.pos.y, internal->box.size.x, internal->box.size.y, 0.0, internal->fb->image, 1.0); | ||||
nvgFillPaint(vg, paint); | nvgFillPaint(vg, paint); | ||||
nvgFill(vg); | nvgFill(vg); | ||||
// For debugging bounding box of framebuffer image | |||||
// nvgFillColor(vg, nvgRGBA(255, 0, 0, 64)); | |||||
// nvgFill(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[4], xform[5]); | |||||
nvgScale(vg, xform[0], xform[3]); | |||||
nvgBeginPath(vg); | |||||
nvgRect(vg, 0, 0, internal->box.size.x, internal->box.size.y); | |||||
nvgStrokeWidth(vg, 2.0); | |||||
nvgStrokeColor(vg, nvgRGBf(1.0, 0.0, 0.0)); | |||||
nvgStroke(vg); | |||||
nvgRestore(vg); | |||||
} | |||||
// For debugging the bounding box of the framebuffer | |||||
// nvgStrokeWidth(vg, 2.0); | |||||
// nvgStrokeColor(vg, nvgRGBA(255, 0, 0, 128)); | |||||
// nvgStroke(vg); | |||||
nvgRestore(vg); | |||||
} | } | ||||
int FramebufferWidget::getImageHandle() { | int FramebufferWidget::getImageHandle() { | ||||
@@ -1,4 +1,5 @@ | |||||
#include "widgets.hpp" | #include "widgets.hpp" | ||||
#include "gui.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -25,5 +25,11 @@ void Slider::onDragEnd() { | |||||
guiCursorUnlock(); | guiCursorUnlock(); | ||||
} | } | ||||
void Slider::onMouseDownOpaque(int button) { | |||||
if (button == 1) { | |||||
setValue(defaultValue); | |||||
} | |||||
} | |||||
} // namespace rack | } // namespace rack |
@@ -18,6 +18,10 @@ void TextField::draw(NVGcontext *vg) { | |||||
state = BND_DEFAULT; | state = BND_DEFAULT; | ||||
bndTextField(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end); | bndTextField(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end); | ||||
// Draw placeholder text | |||||
if (text.empty() && state != BND_ACTIVE) { | |||||
bndIconLabelCaret(vg, 0.0, 0.0, box.size.x, box.size.y, -1, bndGetTheme()->textFieldTheme.itemColor, 13, placeholder.c_str(), bndGetTheme()->textFieldTheme.itemColor, 0, -1); | |||||
} | |||||
} | } | ||||
Widget *TextField::onMouseDown(Vec pos, int button) { | Widget *TextField::onMouseDown(Vec pos, int button) { | ||||
@@ -1,4 +1,5 @@ | |||||
#include "widgets.hpp" | #include "widgets.hpp" | ||||
#include "gui.hpp" | |||||
namespace rack { | namespace rack { | ||||