@@ -18,7 +18,18 @@ struct RackScrollWidget : ui::ScrollWidget { | |||||
RackScrollWidget(); | RackScrollWidget(); | ||||
~RackScrollWidget(); | ~RackScrollWidget(); | ||||
void reset(); | 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 step() override; | ||||
void draw(const DrawArgs& args) override; | void draw(const DrawArgs& args) override; | ||||
void onHoverKey(const HoverKeyEvent& e) override; | void onHoverKey(const HoverKeyEvent& e) override; | ||||
@@ -35,10 +35,6 @@ extern bool windowMaximized; | |||||
extern math::Vec windowSize; | extern math::Vec windowSize; | ||||
/** Position in window in pixels */ | /** Position in window in pixels */ | ||||
extern math::Vec windowPos; | 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 */ | /** Reverse the zoom scroll direction */ | ||||
extern bool invertZoom; | extern bool invertZoom; | ||||
/** Ratio between UI pixel and physical screen pixel. | /** Ratio between UI pixel and physical screen pixel. | ||||
@@ -168,16 +168,16 @@ struct EditButton : MenuButton { | |||||
struct ZoomQuantity : Quantity { | struct ZoomQuantity : Quantity { | ||||
void setValue(float value) override { | void setValue(float value) override { | ||||
settings::zoom = math::clamp(value, getMinValue(), getMaxValue()); | |||||
APP->scene->rackScroll->setZoom(std::pow(2.f, value)); | |||||
} | } | ||||
float getValue() override { | float getValue() override { | ||||
return settings::zoom; | |||||
return std::log2(APP->scene->rackScroll->getZoom()); | |||||
} | } | ||||
float getMinValue() override { | float getMinValue() override { | ||||
return settings::zoomMin; | |||||
return -2.f; | |||||
} | } | ||||
float getMaxValue() override { | float getMaxValue() override { | ||||
return settings::zoomMax; | |||||
return 2.f; | |||||
} | } | ||||
float getDefaultValue() override { | float getDefaultValue() override { | ||||
return 0.0; | return 0.0; | ||||
@@ -10,8 +10,6 @@ namespace app { | |||||
struct RackScrollWidget::Internal { | struct RackScrollWidget::Internal { | ||||
/** The pivot point for zooming */ | |||||
math::Vec zoomPos; | |||||
/** For viewport expanding */ | /** For viewport expanding */ | ||||
float oldZoom = 0.f; | float oldZoom = 0.f; | ||||
math::Vec oldOffset; | 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 | // Compute module bounding box | ||||
math::Rect moduleBox = rackWidget->getModuleContainer()->getChildrenBoundingBox(); | math::Rect moduleBox = rackWidget->getModuleContainer()->getChildrenBoundingBox(); | ||||
if (!moduleBox.size.isFinite()) | if (!moduleBox.size.isFinite()) | ||||
moduleBox = math::Rect(RACK_OFFSET, math::Vec(0, 0)); | 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; | math::Rect scrollBox = moduleBox; | ||||
scrollBox.pos = scrollBox.pos.mult(zoom); | scrollBox.pos = scrollBox.pos.mult(zoom); | ||||
scrollBox.size = scrollBox.size.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. | // 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) { | if (zoom == internal->oldZoom) { | ||||
@@ -81,7 +98,7 @@ void RackScrollWidget::step() { | |||||
// Scroll rack if dragging cable near the edge of the screen | // Scroll rack if dragging cable near the edge of the screen | ||||
math::Vec pos = APP->scene->mousePos; | math::Vec pos = APP->scene->mousePos; | ||||
math::Rect viewport = getViewport(box.zeroPos()); | math::Rect viewport = getViewport(box.zeroPos()); | ||||
if (rackWidget->incompleteCable) { | |||||
if (APP->event->getDraggedWidget()) { | |||||
float margin = 20.0; | float margin = 20.0; | ||||
float speed = 15.0; | float speed = 15.0; | ||||
if (pos.x <= viewport.pos.x + margin) | if (pos.x <= viewport.pos.x + margin) | ||||
@@ -98,6 +115,7 @@ void RackScrollWidget::step() { | |||||
hideScrollbars = APP->window->isFullScreen(); | hideScrollbars = APP->window->isFullScreen(); | ||||
ScrollWidget::step(); | ScrollWidget::step(); | ||||
internal->oldOffset = offset; | internal->oldOffset = offset; | ||||
internal->oldZoom = zoom; | internal->oldZoom = zoom; | ||||
} | } | ||||
@@ -119,13 +137,8 @@ void RackScrollWidget::onHoverScroll(const HoverScrollEvent& e) { | |||||
float zoomDelta = e.scrollDelta.y / 50 / 4; | float zoomDelta = e.scrollDelta.y / 50 / 4; | ||||
if (settings::invertZoom) | if (settings::invertZoom) | ||||
zoomDelta *= -1; | 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); | e.consume(this); | ||||
} | } | ||||
@@ -153,13 +166,13 @@ void RackScrollWidget::onButton(const ButtonEvent& e) { | |||||
// Zoom in/out with extra mouse buttons | // Zoom in/out with extra mouse buttons | ||||
if (e.action == GLFW_PRESS) { | if (e.action == GLFW_PRESS) { | ||||
if (e.button == GLFW_MOUSE_BUTTON_4) { | 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); | e.consume(this); | ||||
} | } | ||||
if (e.button == GLFW_MOUSE_BUTTON_5) { | 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); | e.consume(this); | ||||
} | } | ||||
} | } | ||||
@@ -202,26 +202,24 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { | |||||
e.consume(this); | e.consume(this); | ||||
} | } | ||||
if (e.keyName == "-" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | 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 *= 2; | ||||
zoom = std::ceil(zoom - 0.01f) - 1; | zoom = std::ceil(zoom - 0.01f) - 1; | ||||
zoom /= 2; | 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); | e.consume(this); | ||||
} | } | ||||
// Numpad has a "+" key, but the main keyboard section hides it under "=" | // 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) { | 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 *= 2; | ||||
zoom = std::floor(zoom + 0.01f) + 1; | zoom = std::floor(zoom + 0.01f) + 1; | ||||
zoom /= 2; | 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); | e.consume(this); | ||||
} | } | ||||
if ((e.keyName == "0") && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | 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); | e.consume(this); | ||||
} | } | ||||
if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { | if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { | ||||
@@ -416,11 +416,18 @@ json_t* Manager::toJson() { | |||||
if (!APP->history->isSaved()) | if (!APP->history->isSaved()) | ||||
json_object_set_new(rootJ, "unsaved", json_boolean(true)); | 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_t* engineJ = APP->engine->toJson(); | ||||
json_object_update(rootJ, engineJ); | json_object_update(rootJ, engineJ); | ||||
json_decref(engineJ); | json_decref(engineJ); | ||||
// Merge with RackWidget JSON | |||||
if (APP->scene) { | if (APP->scene) { | ||||
APP->scene->rack->mergeJson(rootJ); | APP->scene->rack->mergeJson(rootJ); | ||||
} | } | ||||
@@ -453,6 +460,14 @@ void Manager::fromJson(json_t* rootJ) { | |||||
if (!unsavedJ) | if (!unsavedJ) | ||||
APP->history->setSaved(); | 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 { | try { | ||||
APP->engine->fromJson(rootJ); | APP->engine->fromJson(rootJ); | ||||
if (APP->scene) { | if (APP->scene) { | ||||
@@ -23,7 +23,6 @@ std::string token; | |||||
bool windowMaximized = false; | bool windowMaximized = false; | ||||
math::Vec windowSize = math::Vec(1024, 768); | math::Vec windowSize = math::Vec(1024, 768); | ||||
math::Vec windowPos = math::Vec(NAN, NAN); | math::Vec windowPos = math::Vec(NAN, NAN); | ||||
float zoom = 0.0; | |||||
bool invertZoom = false; | bool invertZoom = false; | ||||
float pixelRatio = 0.0; | float pixelRatio = 0.0; | ||||
float cableOpacity = 0.5; | float cableOpacity = 0.5; | ||||
@@ -95,8 +94,6 @@ json_t* toJson() { | |||||
json_t* windowPosJ = json_pack("[f, f]", windowPos.x, windowPos.y); | json_t* windowPosJ = json_pack("[f, f]", windowPos.x, windowPos.y); | ||||
json_object_set_new(rootJ, "windowPos", windowPosJ); | 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, "invertZoom", json_boolean(invertZoom)); | ||||
json_object_set_new(rootJ, "pixelRatio", json_real(pixelRatio)); | json_object_set_new(rootJ, "pixelRatio", json_real(pixelRatio)); | ||||
@@ -217,10 +214,6 @@ void fromJson(json_t* rootJ) { | |||||
windowPos = math::Vec(x, y); | 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"); | json_t* invertZoomJ = json_object_get(rootJ, "invertZoom"); | ||||
if (invertZoomJ) | if (invertZoomJ) | ||||
invertZoom = json_boolean_value(invertZoomJ); | invertZoom = json_boolean_value(invertZoomJ); | ||||