From 739b8b3adc41a2719fc10c1e42318d9b3e267ea5 Mon Sep 17 00:00:00 2001 From: Monica Kozbial Date: Mon, 31 Aug 2020 18:41:20 -0700 Subject: [PATCH] Viewport change event (#4180) * Adding viewport ui event emitting and tests * comment out viewport ui event logic and add TODO for reference --- core/scrollbar.js | 18 ++- core/workspace_svg.js | 59 ++++++++- tests/mocha/workspace_svg_test.js | 206 ++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+), 7 deletions(-) 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(); });