123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763 |
- /**
- * @license
- * Visual Blocks Editor
- *
- * Copyright 2011 Google Inc.
- * https://developers.google.com/blockly/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /**
- * @fileoverview Library for creating scrollbars.
- * @author fraser@google.com (Neil Fraser)
- */
- 'use strict';
- goog.provide('Blockly.Scrollbar');
- goog.provide('Blockly.ScrollbarPair');
- goog.require('goog.dom');
- goog.require('goog.events');
- /**
- * Class for a pair of scrollbars. Horizontal and vertical.
- * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbars to.
- * @constructor
- */
- Blockly.ScrollbarPair = function(workspace) {
- this.workspace_ = workspace;
- this.hScroll = new Blockly.Scrollbar(workspace, true, true);
- this.vScroll = new Blockly.Scrollbar(workspace, false, true);
- this.corner_ = Blockly.createSvgElement('rect',
- {'height': Blockly.Scrollbar.scrollbarThickness,
- 'width': Blockly.Scrollbar.scrollbarThickness,
- 'class': 'blocklyScrollbarBackground'}, null);
- Blockly.Scrollbar.insertAfter_(this.corner_, workspace.getBubbleCanvas());
- };
- /**
- * Previously recorded metrics from the workspace.
- * @type {Object}
- * @private
- */
- Blockly.ScrollbarPair.prototype.oldHostMetrics_ = null;
- /**
- * Dispose of this pair of scrollbars.
- * Unlink from all DOM elements to prevent memory leaks.
- */
- Blockly.ScrollbarPair.prototype.dispose = function() {
- goog.dom.removeNode(this.corner_);
- this.corner_ = null;
- this.workspace_ = null;
- this.oldHostMetrics_ = null;
- this.hScroll.dispose();
- this.hScroll = null;
- this.vScroll.dispose();
- this.vScroll = null;
- };
- /**
- * Recalculate both of the scrollbars' locations and lengths.
- * Also reposition the corner rectangle.
- */
- Blockly.ScrollbarPair.prototype.resize = function() {
- // Look up the host metrics once, and use for both scrollbars.
- var hostMetrics = this.workspace_.getMetrics();
- if (!hostMetrics) {
- // Host element is likely not visible.
- return;
- }
- // Only change the scrollbars if there has been a change in metrics.
- var resizeH = false;
- var resizeV = false;
- if (!this.oldHostMetrics_ ||
- this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth ||
- this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight ||
- this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop ||
- this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) {
- // The window has been resized or repositioned.
- resizeH = true;
- resizeV = true;
- } else {
- // Has the content been resized or moved?
- if (!this.oldHostMetrics_ ||
- this.oldHostMetrics_.contentWidth != hostMetrics.contentWidth ||
- this.oldHostMetrics_.viewLeft != hostMetrics.viewLeft ||
- this.oldHostMetrics_.contentLeft != hostMetrics.contentLeft) {
- resizeH = true;
- }
- if (!this.oldHostMetrics_ ||
- this.oldHostMetrics_.contentHeight != hostMetrics.contentHeight ||
- this.oldHostMetrics_.viewTop != hostMetrics.viewTop ||
- this.oldHostMetrics_.contentTop != hostMetrics.contentTop) {
- resizeV = true;
- }
- }
- if (resizeH) {
- this.hScroll.resize(hostMetrics);
- }
- if (resizeV) {
- this.vScroll.resize(hostMetrics);
- }
- // Reposition the corner square.
- if (!this.oldHostMetrics_ ||
- this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth ||
- this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) {
- this.corner_.setAttribute('x', this.vScroll.position_.x);
- }
- if (!this.oldHostMetrics_ ||
- this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight ||
- this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop) {
- this.corner_.setAttribute('y', this.hScroll.position_.y);
- }
- // Cache the current metrics to potentially short-cut the next resize event.
- this.oldHostMetrics_ = hostMetrics;
- };
- /**
- * Set the sliders of both scrollbars to be at a certain position.
- * @param {number} x Horizontal scroll value.
- * @param {number} y Vertical scroll value.
- */
- Blockly.ScrollbarPair.prototype.set = function(x, y) {
- // This function is equivalent to:
- // this.hScroll.set(x);
- // this.vScroll.set(y);
- // However, that calls setMetrics twice which causes a chain of
- // getAttribute->setAttribute->getAttribute resulting in an extra layout pass.
- // Combining them speeds up rendering.
- var xyRatio = {};
- var hHandlePosition = x * this.hScroll.ratio_;
- var vHandlePosition = y * this.vScroll.ratio_;
- var hBarLength = this.hScroll.scrollViewSize_;
- var vBarLength = this.vScroll.scrollViewSize_;
- xyRatio.x = this.getRatio_(hHandlePosition, hBarLength);
- xyRatio.y = this.getRatio_(vHandlePosition, vBarLength);
- this.workspace_.setMetrics(xyRatio);
- this.hScroll.setHandlePosition(hHandlePosition);
- this.vScroll.setHandlePosition(vHandlePosition);
- };
- /**
- * Helper to calculate the ratio of handle position to scrollbar view size.
- * @param {number} handlePosition The value of the handle.
- * @param {number} viewSize The total size of the scrollbar's view.
- * @return {number} Ratio.
- * @private
- */
- Blockly.ScrollbarPair.prototype.getRatio_ = function(handlePosition, viewSize) {
- var ratio = handlePosition / viewSize;
- if (isNaN(ratio)) {
- return 0;
- }
- return ratio;
- };
- // --------------------------------------------------------------------
- /**
- * Class for a pure SVG scrollbar.
- * This technique offers a scrollbar that is guaranteed to work, but may not
- * look or behave like the system's scrollbars.
- * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbar to.
- * @param {boolean} horizontal True if horizontal, false if vertical.
- * @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair.
- * @constructor
- */
- Blockly.Scrollbar = function(workspace, horizontal, opt_pair) {
- this.workspace_ = workspace;
- this.pair_ = opt_pair || false;
- this.horizontal_ = horizontal;
- this.oldHostMetrics_ = null;
- this.createDom_();
- /**
- * The upper left corner of the scrollbar's svg group.
- * @type {goog.math.Coordinate}
- * @private
- */
- this.position_ = new goog.math.Coordinate(0, 0);
- if (horizontal) {
- this.svgBackground_.setAttribute('height',
- Blockly.Scrollbar.scrollbarThickness);
- this.svgHandle_.setAttribute('height',
- Blockly.Scrollbar.scrollbarThickness - 5);
- this.svgHandle_.setAttribute('y', 2.5);
- this.lengthAttribute_ = 'width';
- this.positionAttribute_ = 'x';
- } else {
- this.svgBackground_.setAttribute('width',
- Blockly.Scrollbar.scrollbarThickness);
- this.svgHandle_.setAttribute('width',
- Blockly.Scrollbar.scrollbarThickness - 5);
- this.svgHandle_.setAttribute('x', 2.5);
- this.lengthAttribute_ = 'height';
- this.positionAttribute_ = 'y';
- }
- var scrollbar = this;
- this.onMouseDownBarWrapper_ = Blockly.bindEventWithChecks_(
- this.svgBackground_, 'mousedown', scrollbar, scrollbar.onMouseDownBar_);
- this.onMouseDownHandleWrapper_ = Blockly.bindEventWithChecks_(this.svgHandle_,
- 'mousedown', scrollbar, scrollbar.onMouseDownHandle_);
- };
- /**
- * The size of the area within which the scrollbar handle can move.
- * @type {number}
- * @private
- */
- Blockly.Scrollbar.prototype.scrollViewSize_ = 0;
- /**
- * The length of the scrollbar handle.
- * @type {number}
- * @private
- */
- Blockly.Scrollbar.prototype.handleLength_ = 0;
- /**
- * The offset of the start of the handle from the start of the scrollbar range.
- * @type {number}
- * @private
- */
- Blockly.Scrollbar.prototype.handlePosition_ = 0;
- /**
- * Whether the scrollbar handle is visible.
- * @type {boolean}
- * @private
- */
- Blockly.Scrollbar.prototype.isVisible_ = true;
- /**
- * Width of vertical scrollbar or height of horizontal scrollbar.
- * Increase the size of scrollbars on touch devices.
- * Don't define if there is no document object (e.g. node.js).
- */
- Blockly.Scrollbar.scrollbarThickness = 15;
- if (goog.events.BrowserFeature.TOUCH_ENABLED) {
- Blockly.Scrollbar.scrollbarThickness = 25;
- }
- /**
- * @param {!Object} first An object containing computed measurements of a
- * workspace.
- * @param {!Object} second Another object containing computed measurements of a
- * workspace.
- * @return {boolean} Whether the two sets of metrics are equivalent.
- * @private
- */
- Blockly.Scrollbar.metricsAreEquivalent_ = function(first, second) {
- if (!(first && second)) {
- return false;
- }
- if (first.viewWidth != second.viewWidth ||
- first.viewHeight != second.viewHeight ||
- first.viewLeft != second.viewLeft ||
- first.viewTop != second.viewTop ||
- first.absoluteTop != second.absoluteTop ||
- first.absoluteLeft != second.absoluteLeft ||
- first.contentWidth != second.contentWidth ||
- first.contentHeight != second.contentHeight ||
- first.contentLeft != second.contentLeft ||
- first.contentTop != second.contentTop) {
- return false;
- }
- return true;
- };
- /**
- * Dispose of this scrollbar.
- * Unlink from all DOM elements to prevent memory leaks.
- */
- Blockly.Scrollbar.prototype.dispose = function() {
- this.cleanUp_();
- Blockly.unbindEvent_(this.onMouseDownBarWrapper_);
- this.onMouseDownBarWrapper_ = null;
- Blockly.unbindEvent_(this.onMouseDownHandleWrapper_);
- this.onMouseDownHandleWrapper_ = null;
- goog.dom.removeNode(this.svgGroup_);
- this.svgGroup_ = null;
- this.svgBackground_ = null;
- this.svgHandle_ = null;
- this.workspace_ = null;
- };
- /**
- * Set the length of the scrollbar's handle and change the SVG attribute
- * accordingly.
- * @param {number} newLength The new scrollbar handle length.
- */
- Blockly.Scrollbar.prototype.setHandleLength_ = function(newLength) {
- this.handleLength_ = newLength;
- this.svgHandle_.setAttribute(this.lengthAttribute_, this.handleLength_);
- };
- /**
- * Set the offset of the scrollbar's handle and change the SVG attribute
- * accordingly.
- * @param {number} newPosition The new scrollbar handle offset.
- */
- Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) {
- this.handlePosition_ = newPosition;
- this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_);
- };
- /**
- * Set the size of the scrollbar's background and change the SVG attribute
- * accordingly.
- * @param {number} newSize The new scrollbar background length.
- * @private
- */
- Blockly.Scrollbar.prototype.setScrollViewSize_ = function(newSize) {
- this.scrollViewSize_ = newSize;
- this.svgBackground_.setAttribute(this.lengthAttribute_, this.scrollViewSize_);
- };
- /**
- * Set the position of the scrollbar's svg group.
- * @param {number} x The new x coordinate.
- * @param {number} y The new y coordinate.
- */
- Blockly.Scrollbar.prototype.setPosition = function(x, y) {
- this.position_.x = x;
- this.position_.y = y;
- this.svgGroup_.setAttribute('transform',
- 'translate(' + this.position_.x + ',' + this.position_.y + ')');
- };
- /**
- * Recalculate the scrollbar's location and its length.
- * @param {Object=} opt_metrics A data structure of from the describing all the
- * required dimensions. If not provided, it will be fetched from the host
- * object.
- */
- Blockly.Scrollbar.prototype.resize = function(opt_metrics) {
- // Determine the location, height and width of the host element.
- var hostMetrics = opt_metrics;
- if (!hostMetrics) {
- hostMetrics = this.workspace_.getMetrics();
- if (!hostMetrics) {
- // Host element is likely not visible.
- return;
- }
- }
- if (Blockly.Scrollbar.metricsAreEquivalent_(hostMetrics,
- this.oldHostMetrics_)) {
- return;
- }
- this.oldHostMetrics_ = hostMetrics;
- /* hostMetrics is an object with the following properties.
- * .viewHeight: Height of the visible rectangle,
- * .viewWidth: Width of the visible rectangle,
- * .contentHeight: Height of the contents,
- * .contentWidth: Width of the content,
- * .viewTop: Offset of top edge of visible rectangle from parent,
- * .viewLeft: Offset of left edge of visible rectangle from parent,
- * .contentTop: Offset of the top-most content from the y=0 coordinate,
- * .contentLeft: Offset of the left-most content from the x=0 coordinate,
- * .absoluteTop: Top-edge of view.
- * .absoluteLeft: Left-edge of view.
- */
- if (this.horizontal_) {
- this.resizeHorizontal_(hostMetrics);
- } else {
- this.resizeVertical_(hostMetrics);
- }
- // Resizing may have caused some scrolling.
- this.onScroll_();
- };
- /**
- * Recalculate a horizontal scrollbar's location and length.
- * @param {!Object} hostMetrics A data structure describing all the
- * required dimensions, possibly fetched from the host object.
- * @private
- */
- Blockly.Scrollbar.prototype.resizeHorizontal_ = function(hostMetrics) {
- // TODO: Inspect metrics to determine if we can get away with just a content
- // resize.
- this.resizeViewHorizontal(hostMetrics);
- };
- /**
- * Recalculate a horizontal scrollbar's location on the screen and path length.
- * This should be called when the layout or size of the window has changed.
- * @param {!Object} hostMetrics A data structure describing all the
- * required dimensions, possibly fetched from the host object.
- */
- Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) {
- var viewSize = hostMetrics.viewWidth - 1;
- if (this.pair_) {
- // Shorten the scrollbar to make room for the corner square.
- viewSize -= Blockly.Scrollbar.scrollbarThickness;
- }
- this.setScrollViewSize_(Math.max(0, viewSize));
- var xCoordinate = hostMetrics.absoluteLeft + 0.5;
- if (this.pair_ && this.workspace_.RTL) {
- xCoordinate += Blockly.Scrollbar.scrollbarThickness;
- }
- // Horizontal toolbar should always be just above the bottom of the workspace.
- var yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight -
- Blockly.Scrollbar.scrollbarThickness - 0.5;
- this.setPosition(xCoordinate, yCoordinate);
- // If the view has been resized, a content resize will also be necessary. The
- // reverse is not true.
- this.resizeContentHorizontal(hostMetrics);
- };
- /**
- * Recalculate a horizontal scrollbar's location within its path and length.
- * This should be called when the contents of the workspace have changed.
- * @param {!Object} hostMetrics A data structure describing all the
- * required dimensions, possibly fetched from the host object.
- */
- Blockly.Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) {
- if (!this.pair_) {
- // Only show the scrollbar if needed.
- // Ideally this would also apply to scrollbar pairs, but that's a bigger
- // headache (due to interactions with the corner square).
- this.setVisible(this.scrollViewSize_ < hostMetrics.contentWidth);
- }
- this.ratio_ = this.scrollViewSize_ / hostMetrics.contentWidth;
- if (this.ratio_ == -Infinity || this.ratio_ == Infinity ||
- isNaN(this.ratio_)) {
- this.ratio_ = 0;
- }
- var handleLength = hostMetrics.viewWidth * this.ratio_;
- this.setHandleLength_(Math.max(0, handleLength));
- var handlePosition = (hostMetrics.viewLeft - hostMetrics.contentLeft) *
- this.ratio_;
- this.setHandlePosition(this.constrainHandle_(handlePosition));
- };
- /**
- * Recalculate a vertical scrollbar's location and length.
- * @param {!Object} hostMetrics A data structure describing all the
- * required dimensions, possibly fetched from the host object.
- * @private
- */
- Blockly.Scrollbar.prototype.resizeVertical_ = function(hostMetrics) {
- // TODO: Inspect metrics to determine if we can get away with just a content
- // resize.
- this.resizeViewVertical(hostMetrics);
- };
- /**
- * Recalculate a vertical scrollbar's location on the screen and path length.
- * This should be called when the layout or size of the window has changed.
- * @param {!Object} hostMetrics A data structure describing all the
- * required dimensions, possibly fetched from the host object.
- */
- Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) {
- var viewSize = hostMetrics.viewHeight - 1;
- if (this.pair_) {
- // Shorten the scrollbar to make room for the corner square.
- viewSize -= Blockly.Scrollbar.scrollbarThickness;
- }
- this.setScrollViewSize_(Math.max(0, viewSize));
- var xCoordinate = hostMetrics.absoluteLeft + 0.5;
- if (!this.workspace_.RTL) {
- xCoordinate += hostMetrics.viewWidth -
- Blockly.Scrollbar.scrollbarThickness - 1;
- }
- var yCoordinate = hostMetrics.absoluteTop + 0.5;
- this.setPosition(xCoordinate, yCoordinate);
- // If the view has been resized, a content resize will also be necessary. The
- // reverse is not true.
- this.resizeContentVertical(hostMetrics);
- };
- /**
- * Recalculate a vertical scrollbar's location within its path and length.
- * This should be called when the contents of the workspace have changed.
- * @param {!Object} hostMetrics A data structure describing all the
- * required dimensions, possibly fetched from the host object.
- */
- Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) {
- if (!this.pair_) {
- // Only show the scrollbar if needed.
- this.setVisible(this.scrollViewSize_ < hostMetrics.contentHeight);
- }
- this.ratio_ = this.scrollViewSize_ / hostMetrics.contentHeight;
- if (this.ratio_ == -Infinity || this.ratio_ == Infinity ||
- isNaN(this.ratio_)) {
- this.ratio_ = 0;
- }
- var handleLength = hostMetrics.viewHeight * this.ratio_;
- this.setHandleLength_(Math.max(0, handleLength));
- var handlePosition = (hostMetrics.viewTop - hostMetrics.contentTop) *
- this.ratio_;
- this.setHandlePosition(this.constrainHandle_(handlePosition));
- };
- /**
- * Create all the DOM elements required for a scrollbar.
- * The resulting widget is not sized.
- * @private
- */
- Blockly.Scrollbar.prototype.createDom_ = function() {
- /* Create the following DOM:
- <g class="blocklyScrollbarHorizontal">
- <rect class="blocklyScrollbarBackground" />
- <rect class="blocklyScrollbarHandle" rx="8" ry="8" />
- </g>
- */
- var className = 'blocklyScrollbar' +
- (this.horizontal_ ? 'Horizontal' : 'Vertical');
- this.svgGroup_ = Blockly.createSvgElement('g', {'class': className}, null);
- this.svgBackground_ = Blockly.createSvgElement('rect',
- {'class': 'blocklyScrollbarBackground'}, this.svgGroup_);
- var radius = Math.floor((Blockly.Scrollbar.scrollbarThickness - 5) / 2);
- this.svgHandle_ = Blockly.createSvgElement('rect',
- {'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius},
- this.svgGroup_);
- Blockly.Scrollbar.insertAfter_(this.svgGroup_,
- this.workspace_.getBubbleCanvas());
- };
- /**
- * Is the scrollbar visible. Non-paired scrollbars disappear when they aren't
- * needed.
- * @return {boolean} True if visible.
- */
- Blockly.Scrollbar.prototype.isVisible = function() {
- return this.isVisible_;
- };
- /**
- * Set whether the scrollbar is visible.
- * Only applies to non-paired scrollbars.
- * @param {boolean} visible True if visible.
- */
- Blockly.Scrollbar.prototype.setVisible = function(visible) {
- if (visible == this.isVisible()) {
- return;
- }
- // Ideally this would also apply to scrollbar pairs, but that's a bigger
- // headache (due to interactions with the corner square).
- if (this.pair_) {
- throw 'Unable to toggle visibility of paired scrollbars.';
- }
- this.isVisible_ = visible;
- if (visible) {
- this.svgGroup_.setAttribute('display', 'block');
- } else {
- // Hide the scrollbar.
- this.workspace_.setMetrics({x: 0, y: 0});
- this.svgGroup_.setAttribute('display', 'none');
- }
- };
- /**
- * Scroll by one pageful.
- * Called when scrollbar background is clicked.
- * @param {!Event} e Mouse down event.
- * @private
- */
- Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) {
- this.workspace_.markFocused();
- Blockly.Touch.clearTouchIdentifier(); // This is really a click.
- this.cleanUp_();
- if (Blockly.isRightButton(e)) {
- // Right-click.
- // Scrollbars have no context menu.
- e.stopPropagation();
- return;
- }
- var mouseXY = Blockly.mouseToSvg(e, this.workspace_.getParentSvg(),
- this.workspace_.getInverseScreenCTM());
- var mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y;
- var handleXY = Blockly.getSvgXY_(this.svgHandle_, this.workspace_);
- var handleStart = this.horizontal_ ? handleXY.x : handleXY.y;
- var handlePosition = this.handlePosition_;
- var pageLength = this.handleLength_ * 0.95;
- if (mouseLocation <= handleStart) {
- // Decrease the scrollbar's value by a page.
- handlePosition -= pageLength;
- } else if (mouseLocation >= handleStart + this.handleLength_) {
- // Increase the scrollbar's value by a page.
- handlePosition += pageLength;
- }
- this.setHandlePosition(this.constrainHandle_(handlePosition));
- this.onScroll_();
- e.stopPropagation();
- e.preventDefault();
- };
- /**
- * Start a dragging operation.
- * Called when scrollbar handle is clicked.
- * @param {!Event} e Mouse down event.
- * @private
- */
- Blockly.Scrollbar.prototype.onMouseDownHandle_ = function(e) {
- this.workspace_.markFocused();
- this.cleanUp_();
- if (Blockly.isRightButton(e)) {
- // Right-click.
- // Scrollbars have no context menu.
- e.stopPropagation();
- return;
- }
- // Look up the current translation and record it.
- this.startDragHandle = this.handlePosition_;
- // Record the current mouse position.
- this.startDragMouse = this.horizontal_ ? e.clientX : e.clientY;
- Blockly.Scrollbar.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document,
- 'mouseup', this, this.onMouseUpHandle_);
- Blockly.Scrollbar.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(document,
- 'mousemove', this, this.onMouseMoveHandle_);
- e.stopPropagation();
- e.preventDefault();
- };
- /**
- * Drag the scrollbar's handle.
- * @param {!Event} e Mouse up event.
- * @private
- */
- Blockly.Scrollbar.prototype.onMouseMoveHandle_ = function(e) {
- var currentMouse = this.horizontal_ ? e.clientX : e.clientY;
- var mouseDelta = currentMouse - this.startDragMouse;
- var handlePosition = this.startDragHandle + mouseDelta;
- // Position the bar.
- this.setHandlePosition(this.constrainHandle_(handlePosition));
- this.onScroll_();
- };
- /**
- * Release the scrollbar handle and reset state accordingly.
- * @private
- */
- Blockly.Scrollbar.prototype.onMouseUpHandle_ = function() {
- Blockly.Touch.clearTouchIdentifier();
- this.cleanUp_();
- };
- /**
- * Hide chaff and stop binding to mouseup and mousemove events. Call this to
- * wrap up lose ends associated with the scrollbar.
- * @private
- */
- Blockly.Scrollbar.prototype.cleanUp_ = function() {
- Blockly.hideChaff(true);
- if (Blockly.Scrollbar.onMouseUpWrapper_) {
- Blockly.unbindEvent_(Blockly.Scrollbar.onMouseUpWrapper_);
- Blockly.Scrollbar.onMouseUpWrapper_ = null;
- }
- if (Blockly.Scrollbar.onMouseMoveWrapper_) {
- Blockly.unbindEvent_(Blockly.Scrollbar.onMouseMoveWrapper_);
- Blockly.Scrollbar.onMouseMoveWrapper_ = null;
- }
- };
- /**
- * Constrain the handle's position within the minimum (0) and maximum
- * (length of scrollbar) values allowed for the scrollbar.
- * @param {number} value Value that is potentially out of bounds.
- * @return {number} Constrained value.
- * @private
- */
- Blockly.Scrollbar.prototype.constrainHandle_ = function(value) {
- if (value <= 0 || isNaN(value) || this.scrollViewSize_ < this.handleLength_) {
- value = 0;
- } else {
- value = Math.min(value, this.scrollViewSize_ - this.handleLength_);
- }
- return value;
- };
- /**
- * Called when scrollbar is moved.
- * @private
- */
- Blockly.Scrollbar.prototype.onScroll_ = function() {
- var ratio = this.handlePosition_ / this.scrollViewSize_;
- if (isNaN(ratio)) {
- ratio = 0;
- }
- var xyRatio = {};
- if (this.horizontal_) {
- xyRatio.x = ratio;
- } else {
- xyRatio.y = ratio;
- }
- this.workspace_.setMetrics(xyRatio);
- };
- /**
- * Set the scrollbar slider's position.
- * @param {number} value The distance from the top/left end of the bar.
- */
- Blockly.Scrollbar.prototype.set = function(value) {
- this.setHandlePosition(this.constrainHandle_(value * this.ratio_));
- this.onScroll_();
- };
- /**
- * Insert a node after a reference node.
- * Contrast with node.insertBefore function.
- * @param {!Element} newNode New element to insert.
- * @param {!Element} refNode Existing element to precede new node.
- * @private
- */
- Blockly.Scrollbar.insertAfter_ = function(newNode, refNode) {
- var siblingNode = refNode.nextSibling;
- var parentNode = refNode.parentNode;
- if (!parentNode) {
- throw 'Reference node has no parent.';
- }
- if (siblingNode) {
- parentNode.insertBefore(newNode, siblingNode);
- } else {
- parentNode.appendChild(newNode);
- }
- };
|