diff --git a/core/scrollbar.js b/core/scrollbar.js
index 832abaaa7..37a353b23 100644
--- a/core/scrollbar.js
+++ b/core/scrollbar.js
@@ -107,11 +107,19 @@ Blockly.ScrollbarPair.prototype.resize = function() {
resizeV = true;
}
}
- if (resizeH) {
- this.hScroll.resize(hostMetrics);
- }
- if (resizeV) {
- this.vScroll.resize(hostMetrics);
+ if (resizeH || resizeV) {
+ try {
+ Blockly.Events.disable();
+ if (resizeH) {
+ this.hScroll.resize(hostMetrics);
+ }
+ if (resizeV) {
+ this.vScroll.resize(hostMetrics);
+ }
+ } finally {
+ Blockly.Events.enable();
+ }
+ this.workspace_.maybeFireViewportChangeEvent();
}
// Reposition the corner square.
diff --git a/core/workspace_svg.js b/core/workspace_svg.js
index 99399691b..398d94b4c 100644
--- a/core/workspace_svg.js
+++ b/core/workspace_svg.js
@@ -323,6 +323,28 @@ Blockly.WorkspaceSvg.prototype.dragDeltaXY_ = null;
*/
Blockly.WorkspaceSvg.prototype.scale = 1;
+// TODO(#4203) Enable viewport events after ui events refactor.
+// /**
+// * Cached scale value. Used to detect changes in viewport.
+// * @type {number}
+// * @private
+// */
+// Blockly.WorkspaceSvg.prototype.oldScale_ = 1;
+//
+// /**
+// * Cached viewport top value. Used to detect changes in viewport.
+// * @type {number}
+// * @private
+// */
+// Blockly.WorkspaceSvg.prototype.oldTop_ = 0;
+//
+// /**
+// * Cached viewport left value. Used to detect changes in viewport.
+// * @type {number}
+// * @private
+// */
+// Blockly.WorkspaceSvg.prototype.oldLeft_ = 0;
+
/**
* The workspace's trashcan (if any).
* @type {Blockly.Trashcan}
@@ -1073,6 +1095,31 @@ Blockly.WorkspaceSvg.prototype.getParentSvg = function() {
return /** @type {!SVGElement} */ (this.cachedParentSvg_);
};
+/**
+ * Fires a viewport event if events are enabled and there is a change in
+ * viewport values.
+ * @package
+ */
+Blockly.WorkspaceSvg.prototype.maybeFireViewportChangeEvent = function() {
+ // TODO(#4203) Enable viewport events after ui events refactor.
+ // if (!Blockly.Events.isEnabled()) {
+ // return;
+ // }
+ // var scale = this.scale;
+ // var top = -this.scrollY;
+ // var left = -this.scrollX;
+ // if (scale == this.oldScale_ && top == this.oldTop_ && left == this.oldLeft_) {
+ // return;
+ // }
+ // this.oldScale_ = scale;
+ // this.oldTop_ = top;
+ // this.oldLeft_ = left;
+ // var event = new Blockly.Events.Ui(null, 'viewport', null,
+ // { scale: scale, top: top, left: left });
+ // event.workspaceId = this.id;
+ // Blockly.Events.fire(event);
+};
+
/**
* Translate this workspace to new coordinates.
* @param {number} x Horizontal translation, in pixel units relative to the
@@ -1097,6 +1144,8 @@ Blockly.WorkspaceSvg.prototype.translate = function(x, y) {
if (this.grid_) {
this.grid_.moveTo(x, y);
}
+
+ this.maybeFireViewportChangeEvent();
};
/**
@@ -1891,8 +1940,14 @@ Blockly.WorkspaceSvg.prototype.zoomToFit = function() {
// Scale Units: (pixels / workspaceUnit)
var ratioX = workspaceWidth / blocksWidth;
var ratioY = workspaceHeight / blocksHeight;
- this.setScale(Math.min(ratioX, ratioY));
- this.scrollCenter();
+ Blockly.Events.disable();
+ try {
+ this.setScale(Math.min(ratioX, ratioY));
+ this.scrollCenter();
+ } finally {
+ Blockly.Events.enable();
+ }
+ this.maybeFireViewportChangeEvent();
};
/**
diff --git a/tests/mocha/workspace_svg_test.js b/tests/mocha/workspace_svg_test.js
index ce6d9e900..25d7d5274 100644
--- a/tests/mocha/workspace_svg_test.js
+++ b/tests/mocha/workspace_svg_test.js
@@ -164,6 +164,212 @@ suite('WorkspaceSvg', function() {
});
});
+ suite.skip('Viewport change events', function() {
+ function resetEventHistory(eventsFireStub, changeListenerSpy) {
+ eventsFireStub.resetHistory();
+ changeListenerSpy.resetHistory();
+ }
+ function assertSpyFiredViewportEvent(spy, workspace, expectedProperties) {
+ assertEventFired(
+ spy, Blockly.Events.Ui, {element: 'viewport'},
+ workspace.id, null);
+ assertEventFired(spy, Blockly.Events.Ui, expectedProperties,
+ workspace.id, null);
+ }
+ function assertViewportEventFired(eventsFireStub, changeListenerSpy,
+ workspace, expectedEventCount = 1) {
+ var metrics = workspace.getMetrics();
+ var expectedProperties = {
+ element: 'viewport',
+ newValue: {scale: workspace.scale, top: metrics.viewTop,
+ left: metrics.viewLeft}
+ };
+ assertSpyFiredViewportEvent(
+ eventsFireStub, workspace, expectedProperties);
+ assertSpyFiredViewportEvent(
+ changeListenerSpy, workspace,expectedProperties);
+ sinon.assert.callCount(changeListenerSpy, expectedEventCount);
+ sinon.assert.callCount(eventsFireStub, expectedEventCount);
+ }
+ function runViewportEventTest(eventTriggerFunc, eventsFireStub,
+ changeListenerSpy, workspace, clock, expectedEventCount = 1) {
+ clock.runAll();
+ resetEventHistory(eventsFireStub, changeListenerSpy);
+ eventTriggerFunc();
+ assertViewportEventFired(
+ eventsFireStub, changeListenerSpy, workspace, expectedEventCount);
+ }
+ setup(function() {
+ defineStackBlock();
+ this.changeListenerSpy = createFireChangeListenerSpy(this.workspace);
+ });
+ teardown(function() {
+ delete Blockly.Blocks['stack_block'];
+ });
+
+ suite('zoom', function() {
+ test('setScale', function() {
+ runViewportEventTest(() => this.workspace.setScale(2),
+ this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock);
+ });
+ test('zoom(50, 50, 1)', function() {
+ runViewportEventTest(() => this.workspace.zoom(50, 50, 1),
+ this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock);
+ });
+ test('zoom(50, 50, -1)', function() {
+ runViewportEventTest(() => this.workspace.zoom(50, 50, -1),
+ this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock);
+ });
+ test('zoomCenter(1)', function() {
+ runViewportEventTest(() => this.workspace.zoomCenter(1),
+ this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock);
+ });
+ test('zoomCenter(-1)', function() {
+ runViewportEventTest(() => this.workspace.zoomCenter(-1),
+ this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock);
+ });
+ test('zoomToFit', function() {
+ var block = this.workspace.newBlock('stack_block');
+ block.initSvg();
+ block.render();
+ runViewportEventTest(() => this.workspace.zoomToFit(),
+ this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock);
+ });
+ });
+ suite('scroll', function() {
+ test('centerOnBlock', function() {
+ var block = this.workspace.newBlock('stack_block');
+ block.initSvg();
+ block.render();
+ runViewportEventTest(() => this.workspace.zoomToFit(block.id),
+ this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock);
+ });
+ test('scroll', function() {
+ runViewportEventTest(() => this.workspace.scroll(50, 50),
+ this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock);
+ });
+ test('scrollCenter', function() {
+ runViewportEventTest(() => this.workspace.scrollCenter(),
+ this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock);
+ });
+ });
+ suite('resize', function() {
+ setup(function() {
+ sinon.stub(Blockly, 'svgSize').callsFake((svg) => {
+ return new Blockly.utils.Size(
+ svg.cachedWidth_ + 10, svg.cachedHeight_ + 10);
+ });
+ });
+ test('resize', function() {
+ runViewportEventTest(() => this.workspace.resize(),
+ this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock);
+ });
+ test('resizeContents', function() {
+ runViewportEventTest(() => this.workspace.resizeContents(),
+ this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock);
+ });
+ });
+ suite('Blocks triggering viewport changes', function() {
+ test('block render that doesn\'t trigger scroll', function() {
+ this.clock.runAll();
+ resetEventHistory(this.eventsFireStub, this.changeListenerSpy);
+ var block = this.workspace.newBlock('stack_block');
+ block.initSvg();
+ block.render();
+ this.clock.runAll();
+ assertEventNotFired(
+ this.eventsFireStub, Blockly.Events.Ui, {element: 'viewport'});
+ });
+ test('block move that triggers scroll', function() {
+ var block = this.workspace.newBlock('stack_block');
+ block.initSvg();
+ block.render();
+ this.clock.runAll();
+ resetEventHistory(this.eventsFireStub, this.changeListenerSpy);
+ // Expect 2 events, 1 move, 1 viewport
+ runViewportEventTest(() => {
+ block.moveBy(1000, 1000);
+ }, this.eventsFireStub, this.changeListenerSpy, this.workspace,
+ this.clock, 2);
+ });
+ test.skip('domToWorkspace that doesn\'t trigger scroll' , function() {
+ // TODO(#4192): un-skip after fixing bug with unintentional scroll.
+ // 4 blocks with space in center.
+ Blockly.Xml.domToWorkspace(
+ Blockly.Xml.textToDom(
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ ''),
+ this.workspace);
+ var xmlDom = Blockly.Xml.textToDom(
+ '');
+ this.clock.runAll();
+ resetEventHistory(this.eventsFireStub, this.changeListenerSpy);
+ // Add block in center of other blocks, not triggering scroll.
+ Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(
+ ''), this.workspace);
+ this.clock.runAll();
+ assertEventNotFired(
+ this.eventsFireStub, Blockly.Events.Ui, {element: 'viewport'});
+ assertEventNotFired(
+ this.changeListenerSpy, Blockly.Events.Ui, {element: 'viewport'});
+ });
+ test('domToWorkspace at 0,0 that doesn\'t trigger scroll' , function() {
+ // 4 blocks with space in center.
+ Blockly.Xml.domToWorkspace(
+ Blockly.Xml.textToDom(
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ ''),
+ this.workspace);
+ var xmlDom = Blockly.Xml.textToDom(
+ '');
+ this.clock.runAll();
+ resetEventHistory(this.eventsFireStub, this.changeListenerSpy);
+ // Add block in center of other blocks, not triggering scroll.
+ Blockly.Xml.domToWorkspace(xmlDom, this.workspace);
+ this.clock.runAll();
+ assertEventNotFired(
+ this.eventsFireStub, Blockly.Events.Ui, {element: 'viewport'});
+ assertEventNotFired(
+ this.changeListenerSpy, Blockly.Events.Ui, {element: 'viewport'});
+ });
+ test('domToWorkspace multiple blocks triggers one viewport event', function() {
+ var addingMultipleBlocks = () => {
+ Blockly.Xml.domToWorkspace(
+ Blockly.Xml.textToDom(
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ ''),
+ this.workspace);
+ };
+ // Expect 10 events, 4 create, 4 move, 1 viewport, 1 finished loading
+ runViewportEventTest(addingMultipleBlocks, this.eventsFireStub,
+ this.changeListenerSpy, this.workspace, this.clock, 10);
+ });
+ });
+ });
+
suite('Workspace Base class', function() {
testAWorkspace();
});