diff options
Diffstat (limited to 'blockly/core/tooltip.js')
-rw-r--r-- | blockly/core/tooltip.js | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/blockly/core/tooltip.js b/blockly/core/tooltip.js new file mode 100644 index 0000000..2ff612b --- /dev/null +++ b/blockly/core/tooltip.js @@ -0,0 +1,286 @@ +/** + * @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 to create tooltips for Blockly. + * First, call Blockly.Tooltip.init() after onload. + * Second, set the 'tooltip' property on any SVG element that needs a tooltip. + * If the tooltip is a string, then that message will be displayed. + * If the tooltip is an SVG element, then that object's tooltip will be used. + * Third, call Blockly.Tooltip.bindMouseEvents(e) passing the SVG element. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Tooltip'); + +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); + + +/** + * Is a tooltip currently showing? + */ +Blockly.Tooltip.visible = false; + +/** + * Maximum width (in characters) of a tooltip. + */ +Blockly.Tooltip.LIMIT = 50; + +/** + * PID of suspended thread to clear tooltip on mouse out. + * @private + */ +Blockly.Tooltip.mouseOutPid_ = 0; + +/** + * PID of suspended thread to show the tooltip. + * @private + */ +Blockly.Tooltip.showPid_ = 0; + +/** + * Last observed X location of the mouse pointer (freezes when tooltip appears). + * @private + */ +Blockly.Tooltip.lastX_ = 0; + +/** + * Last observed Y location of the mouse pointer (freezes when tooltip appears). + * @private + */ +Blockly.Tooltip.lastY_ = 0; + +/** + * Current element being pointed at. + * @private + */ +Blockly.Tooltip.element_ = null; + +/** + * Once a tooltip has opened for an element, that element is 'poisoned' and + * cannot respawn a tooltip until the pointer moves over a different element. + * @private + */ +Blockly.Tooltip.poisonedElement_ = null; + +/** + * Horizontal offset between mouse cursor and tooltip. + */ +Blockly.Tooltip.OFFSET_X = 0; + +/** + * Vertical offset between mouse cursor and tooltip. + */ +Blockly.Tooltip.OFFSET_Y = 10; + +/** + * Radius mouse can move before killing tooltip. + */ +Blockly.Tooltip.RADIUS_OK = 10; + +/** + * Delay before tooltip appears. + */ +Blockly.Tooltip.HOVER_MS = 750; + +/** + * Horizontal padding between tooltip and screen edge. + */ +Blockly.Tooltip.MARGINS = 5; + +/** + * The HTML container. Set once by Blockly.Tooltip.createDom. + * @type {Element} + */ +Blockly.Tooltip.DIV = null; + +/** + * Create the tooltip div and inject it onto the page. + */ +Blockly.Tooltip.createDom = function() { + if (Blockly.Tooltip.DIV) { + return; // Already created. + } + // Create an HTML container for popup overlays (e.g. editor widgets). + Blockly.Tooltip.DIV = + goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyTooltipDiv'); + document.body.appendChild(Blockly.Tooltip.DIV); +}; + +/** + * Binds the required mouse events onto an SVG element. + * @param {!Element} element SVG element onto which tooltip is to be bound. + */ +Blockly.Tooltip.bindMouseEvents = function(element) { + Blockly.bindEvent_(element, 'mouseover', null, Blockly.Tooltip.onMouseOver_); + Blockly.bindEvent_(element, 'mouseout', null, Blockly.Tooltip.onMouseOut_); + Blockly.bindEvent_(element, 'mousemove', null, Blockly.Tooltip.onMouseMove_); +}; + +/** + * Hide the tooltip if the mouse is over a different object. + * Initialize the tooltip to potentially appear for this object. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.Tooltip.onMouseOver_ = function(e) { + // If the tooltip is an object, treat it as a pointer to the next object in + // the chain to look at. Terminate when a string or function is found. + var element = e.target; + while (!goog.isString(element.tooltip) && !goog.isFunction(element.tooltip)) { + element = element.tooltip; + } + if (Blockly.Tooltip.element_ != element) { + Blockly.Tooltip.hide(); + Blockly.Tooltip.poisonedElement_ = null; + Blockly.Tooltip.element_ = element; + } + // Forget about any immediately preceeding mouseOut event. + clearTimeout(Blockly.Tooltip.mouseOutPid_); +}; + +/** + * Hide the tooltip if the mouse leaves the object and enters the workspace. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.Tooltip.onMouseOut_ = function(e) { + // Moving from one element to another (overlapping or with no gap) generates + // a mouseOut followed instantly by a mouseOver. Fork off the mouseOut + // event and kill it if a mouseOver is received immediately. + // This way the task only fully executes if mousing into the void. + Blockly.Tooltip.mouseOutPid_ = setTimeout(function() { + Blockly.Tooltip.element_ = null; + Blockly.Tooltip.poisonedElement_ = null; + Blockly.Tooltip.hide(); + }, 1); + clearTimeout(Blockly.Tooltip.showPid_); +}; + +/** + * When hovering over an element, schedule a tooltip to be shown. If a tooltip + * is already visible, hide it if the mouse strays out of a certain radius. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.Tooltip.onMouseMove_ = function(e) { + if (!Blockly.Tooltip.element_ || !Blockly.Tooltip.element_.tooltip) { + // No tooltip here to show. + return; + } else if (Blockly.dragMode_ != Blockly.DRAG_NONE) { + // Don't display a tooltip during a drag. + return; + } else if (Blockly.WidgetDiv.isVisible()) { + // Don't display a tooltip if a widget is open (tooltip would be under it). + return; + } + if (Blockly.Tooltip.visible) { + // Compute the distance between the mouse position when the tooltip was + // shown and the current mouse position. Pythagorean theorem. + var dx = Blockly.Tooltip.lastX_ - e.pageX; + var dy = Blockly.Tooltip.lastY_ - e.pageY; + if (Math.sqrt(dx * dx + dy * dy) > Blockly.Tooltip.RADIUS_OK) { + Blockly.Tooltip.hide(); + } + } else if (Blockly.Tooltip.poisonedElement_ != Blockly.Tooltip.element_) { + // The mouse moved, clear any previously scheduled tooltip. + clearTimeout(Blockly.Tooltip.showPid_); + // Maybe this time the mouse will stay put. Schedule showing of tooltip. + Blockly.Tooltip.lastX_ = e.pageX; + Blockly.Tooltip.lastY_ = e.pageY; + Blockly.Tooltip.showPid_ = + setTimeout(Blockly.Tooltip.show_, Blockly.Tooltip.HOVER_MS); + } +}; + +/** + * Hide the tooltip. + */ +Blockly.Tooltip.hide = function() { + if (Blockly.Tooltip.visible) { + Blockly.Tooltip.visible = false; + if (Blockly.Tooltip.DIV) { + Blockly.Tooltip.DIV.style.display = 'none'; + } + } + clearTimeout(Blockly.Tooltip.showPid_); +}; + +/** + * Create the tooltip and show it. + * @private + */ +Blockly.Tooltip.show_ = function() { + Blockly.Tooltip.poisonedElement_ = Blockly.Tooltip.element_; + if (!Blockly.Tooltip.DIV) { + return; + } + // Erase all existing text. + goog.dom.removeChildren(/** @type {!Element} */ (Blockly.Tooltip.DIV)); + // Get the new text. + var tip = Blockly.Tooltip.element_.tooltip; + while (goog.isFunction(tip)) { + tip = tip(); + } + tip = Blockly.utils.wrap(tip, Blockly.Tooltip.LIMIT); + // Create new text, line by line. + var lines = tip.split('\n'); + for (var i = 0; i < lines.length; i++) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(lines[i])); + Blockly.Tooltip.DIV.appendChild(div); + } + var rtl = Blockly.Tooltip.element_.RTL; + var windowSize = goog.dom.getViewportSize(); + // Display the tooltip. + Blockly.Tooltip.DIV.style.direction = rtl ? 'rtl' : 'ltr'; + Blockly.Tooltip.DIV.style.display = 'block'; + Blockly.Tooltip.visible = true; + // Move the tooltip to just below the cursor. + var anchorX = Blockly.Tooltip.lastX_; + if (rtl) { + anchorX -= Blockly.Tooltip.OFFSET_X + Blockly.Tooltip.DIV.offsetWidth; + } else { + anchorX += Blockly.Tooltip.OFFSET_X; + } + var anchorY = Blockly.Tooltip.lastY_ + Blockly.Tooltip.OFFSET_Y; + + if (anchorY + Blockly.Tooltip.DIV.offsetHeight > + windowSize.height + window.scrollY) { + // Falling off the bottom of the screen; shift the tooltip up. + anchorY -= Blockly.Tooltip.DIV.offsetHeight + 2 * Blockly.Tooltip.OFFSET_Y; + } + if (rtl) { + // Prevent falling off left edge in RTL mode. + anchorX = Math.max(Blockly.Tooltip.MARGINS - window.scrollX, anchorX); + } else { + if (anchorX + Blockly.Tooltip.DIV.offsetWidth > + windowSize.width + window.scrollX - 2 * Blockly.Tooltip.MARGINS) { + // Falling off the right edge of the screen; + // clamp the tooltip on the edge. + anchorX = windowSize.width - Blockly.Tooltip.DIV.offsetWidth - + 2 * Blockly.Tooltip.MARGINS; + } + } + Blockly.Tooltip.DIV.style.top = anchorY + 'px'; + Blockly.Tooltip.DIV.style.left = anchorX + 'px'; +}; |