@@ -18,7 +18,18 @@ struct RackScrollWidget : ui::ScrollWidget { | |||
RackScrollWidget(); | |||
~RackScrollWidget(); | |||
void reset(); | |||
/** Gets the top-left scroll offset in grid coordinates. | |||
*/ | |||
math::Vec getGridOffset(); | |||
void setGridOffset(math::Vec gridOffset); | |||
float getZoom(); | |||
/** Sets the zoom level, with a pivot at the center of the scroll viewport. | |||
*/ | |||
void setZoom(float zoom); | |||
void setZoom(float zoom, math::Vec pivot); | |||
void step() override; | |||
void draw(const DrawArgs& args) override; | |||
void onHoverKey(const HoverKeyEvent& e) override; | |||
@@ -35,10 +35,6 @@ extern bool windowMaximized; | |||
extern math::Vec windowSize; | |||
/** Position in window in pixels */ | |||
extern math::Vec windowPos; | |||
/** Rack zoom level, log2. E.g. 100% = 0, 200% = 1, 50% = -1. */ | |||
extern float zoom; | |||
static const float zoomMax = 2.f; | |||
static const float zoomMin = -2.f; | |||
/** Reverse the zoom scroll direction */ | |||
extern bool invertZoom; | |||
/** Ratio between UI pixel and physical screen pixel. | |||
@@ -168,16 +168,16 @@ struct EditButton : MenuButton { | |||
struct ZoomQuantity : Quantity { | |||
void setValue(float value) override { | |||
settings::zoom = math::clamp(value, getMinValue(), getMaxValue()); | |||
APP->scene->rackScroll->setZoom(std::pow(2.f, value)); | |||
} | |||
float getValue() override { | |||
return settings::zoom; | |||
return std::log2(APP->scene->rackScroll->getZoom()); | |||
} | |||
float getMinValue() override { | |||
return settings::zoomMin; | |||
return -2.f; | |||
} | |||
float getMaxValue() override { | |||
return settings::zoomMax; | |||
return 2.f; | |||
} | |||
float getDefaultValue() override { | |||
return 0.0; | |||
@@ -10,8 +10,6 @@ namespace app { | |||
struct RackScrollWidget::Internal { | |||
/** The pivot point for zooming */ | |||
math::Vec zoomPos; | |||
/** For viewport expanding */ | |||
float oldZoom = 0.f; | |||
math::Vec oldOffset; | |||
@@ -43,28 +41,47 @@ void RackScrollWidget::reset() { | |||
} | |||
void RackScrollWidget::step() { | |||
// Compute zoom from exponential zoom | |||
float zoom = std::pow(2.f, settings::zoom); | |||
if (zoom != zoomWidget->zoom) { | |||
// Set offset based on zoomPos | |||
offset = offset.plus(internal->zoomPos).div(zoomWidget->zoom).mult(zoom).minus(internal->zoomPos); | |||
// Set zoom | |||
zoomWidget->setZoom(zoom); | |||
} | |||
math::Vec RackScrollWidget::getGridOffset() { | |||
return offset.minus(RACK_OFFSET).div(RACK_GRID_SIZE); | |||
} | |||
void RackScrollWidget::setGridOffset(math::Vec gridOffset) { | |||
offset = gridOffset.mult(RACK_GRID_SIZE).plus(RACK_OFFSET); | |||
} | |||
internal->zoomPos = box.size.div(2); | |||
float RackScrollWidget::getZoom() { | |||
return zoomWidget->getZoom(); | |||
} | |||
void RackScrollWidget::setZoom(float zoom) { | |||
setZoom(zoom, getSize().div(2)); | |||
} | |||
void RackScrollWidget::setZoom(float zoom, math::Vec pivot) { | |||
zoom = math::clamp(zoom, std::pow(2.f, -2), std::pow(2.f, 2)); | |||
offset = offset.plus(pivot).mult(zoom / zoomWidget->getZoom()).minus(pivot); | |||
zoomWidget->setZoom(zoom); | |||
} | |||
void RackScrollWidget::step() { | |||
float zoom = getZoom(); | |||
// Compute module bounding box | |||
math::Rect moduleBox = rackWidget->getModuleContainer()->getChildrenBoundingBox(); | |||
if (!moduleBox.size.isFinite()) | |||
moduleBox = math::Rect(RACK_OFFSET, math::Vec(0, 0)); | |||
// Expand moduleBox by half a screen size | |||
// Expand moduleBox by a screen size | |||
math::Rect scrollBox = moduleBox; | |||
scrollBox.pos = scrollBox.pos.mult(zoom); | |||
scrollBox.size = scrollBox.size.mult(zoom); | |||
scrollBox = scrollBox.grow(box.size.mult(0.6666)); | |||
scrollBox = scrollBox.grow(box.size.mult(0.9)); | |||
// Expand to the current viewport box so that moving modules (and thus changing the module bounding box) doesn't clamp the scroll offset. | |||
if (zoom == internal->oldZoom) { | |||
@@ -81,7 +98,7 @@ void RackScrollWidget::step() { | |||
// Scroll rack if dragging cable near the edge of the screen | |||
math::Vec pos = APP->scene->mousePos; | |||
math::Rect viewport = getViewport(box.zeroPos()); | |||
if (rackWidget->incompleteCable) { | |||
if (APP->event->getDraggedWidget()) { | |||
float margin = 20.0; | |||
float speed = 15.0; | |||
if (pos.x <= viewport.pos.x + margin) | |||
@@ -98,6 +115,7 @@ void RackScrollWidget::step() { | |||
hideScrollbars = APP->window->isFullScreen(); | |||
ScrollWidget::step(); | |||
internal->oldOffset = offset; | |||
internal->oldZoom = zoom; | |||
} | |||
@@ -119,13 +137,8 @@ void RackScrollWidget::onHoverScroll(const HoverScrollEvent& e) { | |||
float zoomDelta = e.scrollDelta.y / 50 / 4; | |||
if (settings::invertZoom) | |||
zoomDelta *= -1; | |||
settings::zoom += zoomDelta; | |||
// Limit min/max depending on the direction of zooming | |||
if (zoomDelta > 0.f) | |||
settings::zoom = std::fmin(settings::zoom, settings::zoomMax); | |||
else | |||
settings::zoom = std::fmax(settings::zoom, settings::zoomMin); | |||
internal->zoomPos = e.pos; | |||
float zoom = getZoom() * std::pow(2.f, zoomDelta); | |||
setZoom(zoom, e.pos); | |||
e.consume(this); | |||
} | |||
@@ -153,13 +166,13 @@ void RackScrollWidget::onButton(const ButtonEvent& e) { | |||
// Zoom in/out with extra mouse buttons | |||
if (e.action == GLFW_PRESS) { | |||
if (e.button == GLFW_MOUSE_BUTTON_4) { | |||
settings::zoom -= 0.5f; | |||
settings::zoom = std::fmax(settings::zoom, settings::zoomMin); | |||
float zoom = getZoom() * std::pow(2.f, -0.5f); | |||
setZoom(zoom, e.pos); | |||
e.consume(this); | |||
} | |||
if (e.button == GLFW_MOUSE_BUTTON_5) { | |||
settings::zoom += 0.5f; | |||
settings::zoom = std::fmin(settings::zoom, settings::zoomMax); | |||
float zoom = getZoom() * std::pow(2.f, 0.5f); | |||
setZoom(zoom, e.pos); | |||
e.consume(this); | |||
} | |||
} | |||
@@ -202,26 +202,24 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { | |||
e.consume(this); | |||
} | |||
if (e.keyName == "-" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
float zoom = settings::zoom; | |||
float zoom = std::log2(APP->scene->rackScroll->getZoom()); | |||
zoom *= 2; | |||
zoom = std::ceil(zoom - 0.01f) - 1; | |||
zoom /= 2; | |||
settings::zoom = zoom; | |||
settings::zoom = std::fmax(settings::zoom, settings::zoomMin); | |||
APP->scene->rackScroll->setZoom(std::pow(2.f, zoom)); | |||
e.consume(this); | |||
} | |||
// Numpad has a "+" key, but the main keyboard section hides it under "=" | |||
if ((e.keyName == "=" || e.keyName == "+") && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
float zoom = settings::zoom; | |||
float zoom = std::log2(APP->scene->rackScroll->getZoom()); | |||
zoom *= 2; | |||
zoom = std::floor(zoom + 0.01f) + 1; | |||
zoom /= 2; | |||
settings::zoom = zoom; | |||
settings::zoom = std::fmin(settings::zoom, settings::zoomMax); | |||
APP->scene->rackScroll->setZoom(std::pow(2.f, zoom)); | |||
e.consume(this); | |||
} | |||
if ((e.keyName == "0") && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
settings::zoom = 0.f; | |||
APP->scene->rackScroll->setZoom(1.f); | |||
e.consume(this); | |||
} | |||
if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { | |||
@@ -416,11 +416,18 @@ json_t* Manager::toJson() { | |||
if (!APP->history->isSaved()) | |||
json_object_set_new(rootJ, "unsaved", json_boolean(true)); | |||
// Merge with rootJ | |||
// zoom | |||
if (APP->scene) { | |||
float zoom = APP->scene->rackScroll->getZoom(); | |||
json_object_set_new(rootJ, "zoom", json_real(zoom)); | |||
} | |||
// Merge with Engine JSON | |||
json_t* engineJ = APP->engine->toJson(); | |||
json_object_update(rootJ, engineJ); | |||
json_decref(engineJ); | |||
// Merge with RackWidget JSON | |||
if (APP->scene) { | |||
APP->scene->rack->mergeJson(rootJ); | |||
} | |||
@@ -453,6 +460,14 @@ void Manager::fromJson(json_t* rootJ) { | |||
if (!unsavedJ) | |||
APP->history->setSaved(); | |||
// zoom | |||
if (APP->scene) { | |||
json_t* zoomJ = json_object_get(rootJ, "zoom"); | |||
if (zoomJ) | |||
APP->scene->rackScroll->setZoom(json_number_value(zoomJ)); | |||
} | |||
// Pass JSON to Engine and RackWidget | |||
try { | |||
APP->engine->fromJson(rootJ); | |||
if (APP->scene) { | |||
@@ -23,7 +23,6 @@ std::string token; | |||
bool windowMaximized = false; | |||
math::Vec windowSize = math::Vec(1024, 768); | |||
math::Vec windowPos = math::Vec(NAN, NAN); | |||
float zoom = 0.0; | |||
bool invertZoom = false; | |||
float pixelRatio = 0.0; | |||
float cableOpacity = 0.5; | |||
@@ -95,8 +94,6 @@ json_t* toJson() { | |||
json_t* windowPosJ = json_pack("[f, f]", windowPos.x, windowPos.y); | |||
json_object_set_new(rootJ, "windowPos", windowPosJ); | |||
json_object_set_new(rootJ, "zoom", json_real(zoom)); | |||
json_object_set_new(rootJ, "invertZoom", json_boolean(invertZoom)); | |||
json_object_set_new(rootJ, "pixelRatio", json_real(pixelRatio)); | |||
@@ -217,10 +214,6 @@ void fromJson(json_t* rootJ) { | |||
windowPos = math::Vec(x, y); | |||
} | |||
json_t* zoomJ = json_object_get(rootJ, "zoom"); | |||
if (zoomJ) | |||
zoom = json_number_value(zoomJ); | |||
json_t* invertZoomJ = json_object_get(rootJ, "invertZoom"); | |||
if (invertZoomJ) | |||
invertZoom = json_boolean_value(invertZoomJ); | |||