summaryrefslogtreecommitdiff
path: root/blockly/core/tooltip.js
diff options
context:
space:
mode:
Diffstat (limited to 'blockly/core/tooltip.js')
-rw-r--r--blockly/core/tooltip.js286
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';
+};