Wednesday, November 21, 2012

Creating EasyTooltip class: Part 20

In this tutorial we'll smoothen our tooltip movement even more.

First of all, we'll pass a new parameter to the TooltipCursor class when creating an instance of it in EasyTooltip.

Go to the constructor of the EasyTooltip class and pass smoothMovement value as the third parameter of the cursor:

/**
 * Create a Tooltip manager. Needed for creating and managing tooltips.
 * @paramparent Reference to the parent of tooltips - the container, which will contain the objects that will be rolled over.
 * @paramparentWidth Width of the parent.
 * @paramparentHeight Height of the parent.
 * @paramsmoothMovement The movement smoothener - the bigger the value, the smoother the movement. 1 = no smoothment.
 * @paramspawnDelay Delay in milliseconds of how long the user has to hold their mouse over the object to invoke a tooltip.
 */

public function EasyTooltip(parent:DisplayObjectContainer, parentWidth:int, parentHeight:int, smoothMovement:Number = 3, spawnDelay:int = 0) 
{
smooth = smoothMovement;
delay = spawnDelay;
par = parent;
listeners = [];
parW = parentWidth;
parH = parentHeight;
cursor = new TooltipCursor(parentWidth, parentHeight, smoothMovement);
cursor.mouseChildren = false;
cursor.mouseEnabled = false;
parent.addChild(cursor);
cursor.addEventListener(Event.ENTER_FRAME, onFrame);
setStyle(new TooltipStyle());
trace("Tooltip manager created!");
}

Full EasyTooltip class:

package com.kircode.EasyTooltip 
{
import flash.display.DisplayObjectContainer;
import flash.display.DisplayObject;
import flash.events.Event;
/**
 * Utility for creation of tooltips.
 * @author Kirill Poletaev
 */
public class EasyTooltip 
{

private var par:DisplayObject;
public var listeners:Array;
private var cursor:TooltipCursor;
private var parW:int;
private var parH:int;
private var delay:int;
private var smooth:Number;

/**
 * Create a Tooltip manager. Needed for creating and managing tooltips.
 * @paramparent Reference to the parent of tooltips - the container, which will contain the objects that will be rolled over.
 * @paramparentWidth Width of the parent.
 * @paramparentHeight Height of the parent.
 * @paramsmoothMovement The movement smoothener - the bigger the value, the smoother the movement. 1 = no smoothment.
 * @paramspawnDelay Delay in milliseconds of how long the user has to hold their mouse over the object to invoke a tooltip.
 */

public function EasyTooltip(parent:DisplayObjectContainer, parentWidth:int, parentHeight:int, smoothMovement:Number = 3, spawnDelay:int = 0) 
{
smooth = smoothMovement;
delay = spawnDelay;
par = parent;
listeners = [];
parW = parentWidth;
parH = parentHeight;
cursor = new TooltipCursor(parentWidth, parentHeight, smoothMovement);
cursor.mouseChildren = false;
cursor.mouseEnabled = false;
parent.addChild(cursor);
cursor.addEventListener(Event.ENTER_FRAME, onFrame);
setStyle(new TooltipStyle());
trace("Tooltip manager created!");
}

/**
 * Set a new style for the tooltips.
 * @paramstyle TooltipStyle object that defines the style.
 */

public function setStyle(style:TooltipStyle):void {
cursor.setStyle(style);
}

/**
 * Add a Tooltip listener.
 * @paramlistener Object, which invokes the tooltip on roll over.
 * @paramtooltip Message to be displayed.
 * @returnNewly created Tooltip object. Can be used to dynamically change its properties in real-time.
 */

public function addListener(listener:DisplayObject, tooltip:String):Tooltip {
var tip:Tooltip = new Tooltip(tooltip);
var list:TooltipListener = new TooltipListener(listener, tip, delay);
listeners.push(list);
return tip;
}

private function onFrame(evt:Event):void {
cursor.x += (par.mouseX - cursor.x)/smooth;
cursor.y += (par.mouseY - cursor.y)/smooth;
var displayInd:int = -1;
for (var i:int = 0; i < listeners.length; i++) {
if (listeners[i].tip.display) {
displayInd = i;
break;
}
}
if (displayInd == -1) {
cursor.alpha = 0;
}else {
cursor.alpha = 1;
cursor.setText(listeners[displayInd].tip.msg);
}
}

}

}

Now go to TooltipCursor class. Declare 3 new variables - smooth, realX and realY:

private var smooth:Number;
private var realX:int = 0;
private var realY:int = 0;

Receive the third parameter the the constructor and apply its value to smooth:

public function TooltipCursor(pW:int, pH:int, sm:Number) 
{
smooth = sm;
parentW = pW;
parentH = pH;
txt = new TextField();
txt.multiline = true;
background = new Shape();
addChild(background);
addChild(txt);
txt.width = 1;
txt.height = 1;
content = [];
}

Now go to updateDisplay() function. Here, update the values of realX and realY to simulate smooth movement. The destination are actual_x and actual_y values.

realX += (actual_x - realX) / smooth;
realY += (actual_y - realY) / smooth;

Now in the whole updateDisplay() function, replace actual_x and actual_y with realX and realY.

private function updateDisplay():void {
realX += (actual_x - realX) / smooth;
realY += (actual_y - realY) / smooth;

background.graphics.clear();

if (style.backgroundStroke is SolidColorStroke) {
background.graphics.lineStyle(style.backgroundStroke._lineThickness, style.backgroundStroke._lineColor, style.backgroundStroke._lineAlpha);
}

if (style.backgroundStroke is GradientColorStroke) {
if(style.backgroundStroke._matrix){
style.backgroundStroke._matrix.tx = actual_x;
style.backgroundStroke._matrix.ty = actual_y;
}
background.graphics.lineStyle(style.backgroundStroke._lineThickness);
background.graphics.lineGradientStyle(style.backgroundStroke._gradientType, style.backgroundStroke._colors, style.backgroundStroke._alphas, style.backgroundStroke._ratios, style.backgroundStroke._matrix, style.backgroundStroke._spreadMethod, style.backgroundStroke._interpolationMethod, style.backgroundStroke._focalPointRatio);
}

if (style.backgroundStroke is BitmapStroke) {
if(style.backgroundStroke._matrix){
style.backgroundStroke._matrix.tx = actual_x;
style.backgroundStroke._matrix.ty = actual_y;
}
background.graphics.lineStyle(style.backgroundStroke._lineThickness);
background.graphics.lineBitmapStyle(style.backgroundStroke._bitmap, style.backgroundStroke._matrix, style.backgroundStroke._repeat, style.backgroundStroke._smooth);
}

if (style.backgroundRect is SolidColorRect) {
background.graphics.beginFill(style.backgroundRect._backgroundColor, style.backgroundRect._alpha);
background.graphics.drawRoundRectComplex(realX, realY, w, h, style.backgroundRect._radius[0], style.backgroundRect._radius[1], style.backgroundRect._radius[2], style.backgroundRect._radius[3]);
background.graphics.endFill();
}

if (style.backgroundRect is GradientColorRect) {
if(style.backgroundRect._matrix){
style.backgroundRect._matrix.tx = actual_x;
style.backgroundRect._matrix.ty = actual_y;
}
background.graphics.beginGradientFill(style.backgroundRect._gradientType, style.backgroundRect._colors, style.backgroundRect._alphas, style.backgroundRect._ratios, style.backgroundRect._matrix, style.backgroundRect._spreadMethod, style.backgroundRect._interpolationMethod, style.backgroundRect._focalPointRatio);
background.graphics.drawRoundRectComplex(realX, realY, w, h, style.backgroundRect._radius[0], style.backgroundRect._radius[1], style.backgroundRect._radius[2], style.backgroundRect._radius[3]);
background.graphics.endFill();
}

if (style.backgroundRect is BitmapRect) {
if(style.backgroundRect._matrix){
style.backgroundRect._matrix.tx = actual_x;
style.backgroundRect._matrix.ty = actual_y;
}
background.graphics.beginBitmapFill(style.backgroundRect._bitmap, style.backgroundRect._matrix, style.backgroundRect._repeat, style.backgroundRect._smooth);
background.graphics.drawRoundRectComplex(realX, realY, w, h, style.backgroundRect._radius[0], style.backgroundRect._radius[1], style.backgroundRect._radius[2], style.backgroundRect._radius[3]);
background.graphics.endFill();
}

txt.width = w - style.textPadding.left - style.textPadding.right;
txt.height = h - style.textPadding.top - style.textPadding.bottom;
txt.x = realX + style.textPadding.left;
txt.y = realY + style.textPadding.top;

var i:int;
for (i = 0; i < content.length; i++) {
content[i].c.x = realX + content[i].x;
content[i].c.y = realY + content[i].y;
}
}

And we're done - you can now test the movie and see that there are no "jumpy" movements - everything moves smoothly and neatly.

Full TooltipCursor code:

package com.kircode.EasyTooltip 
{
import flash.display.MovieClip;
import flash.display.Shape;
import flash.text.TextField;
import flash.text.TextFormat;
/**
 * The cursor that follows the mouse (if not set otherwise) and displays each tooltip.
 * @author Kirill Poletaev
 */
public class TooltipCursor extends MovieClip
{

public var txt:TextField;
private var style:TooltipStyle;
private var background:Shape;
private var w:int;
private var h:int;
private var actual_x:int;
private var actual_y:int;
private var parentW:int;
private var parentH:int;
private var direction:String = "";
private var content:Array;
private var smooth:Number;
private var realX:int = 0;
private var realY:int = 0;

public function TooltipCursor(pW:int, pH:int, sm:Number) 
{
smooth = sm;
parentW = pW;
parentH = pH;
txt = new TextField();
txt.multiline = true;
background = new Shape();
addChild(background);
addChild(txt);
txt.width = 1;
txt.height = 1;
content = [];
}

/**
 * Set style of the tooltips.
 * @paramst TooltipStyle object to apply.
 */

public function setStyle(st:TooltipStyle):void {
style = st;
var i:int;
for (i = content.length-1; i >= 0; i--) {
content[i].c.parent.removeChild(content[i].c);
content[i].c = null;
content.splice(i, 1);
}

for (i = 0; i < style.dynamicContent.length; i++) {
var obj = new style.dynamicContent[i].c();
content.push({c:obj, x:style.dynamicContent[i].x, y:style.dynamicContent[i].y});
addChild(obj);
}
this.filters = style.filters;
}

/**
 * Change the text value of the tooltip.
 * @parammessage The new text value.
 */

public function setText(message:String):void {
txt.text = message;
txt.setTextFormat(style.textFormat);
updateSize();
updateDisplay();
}

private function updateDisplay():void {
realX += (actual_x - realX) / smooth;
realY += (actual_y - realY) / smooth;

background.graphics.clear();

if (style.backgroundStroke is SolidColorStroke) {
background.graphics.lineStyle(style.backgroundStroke._lineThickness, style.backgroundStroke._lineColor, style.backgroundStroke._lineAlpha);
}

if (style.backgroundStroke is GradientColorStroke) {
if(style.backgroundStroke._matrix){
style.backgroundStroke._matrix.tx = actual_x;
style.backgroundStroke._matrix.ty = actual_y;
}
background.graphics.lineStyle(style.backgroundStroke._lineThickness);
background.graphics.lineGradientStyle(style.backgroundStroke._gradientType, style.backgroundStroke._colors, style.backgroundStroke._alphas, style.backgroundStroke._ratios, style.backgroundStroke._matrix, style.backgroundStroke._spreadMethod, style.backgroundStroke._interpolationMethod, style.backgroundStroke._focalPointRatio);
}

if (style.backgroundStroke is BitmapStroke) {
if(style.backgroundStroke._matrix){
style.backgroundStroke._matrix.tx = actual_x;
style.backgroundStroke._matrix.ty = actual_y;
}
background.graphics.lineStyle(style.backgroundStroke._lineThickness);
background.graphics.lineBitmapStyle(style.backgroundStroke._bitmap, style.backgroundStroke._matrix, style.backgroundStroke._repeat, style.backgroundStroke._smooth);
}

if (style.backgroundRect is SolidColorRect) {
background.graphics.beginFill(style.backgroundRect._backgroundColor, style.backgroundRect._alpha);
background.graphics.drawRoundRectComplex(realX, realY, w, h, style.backgroundRect._radius[0], style.backgroundRect._radius[1], style.backgroundRect._radius[2], style.backgroundRect._radius[3]);
background.graphics.endFill();
}

if (style.backgroundRect is GradientColorRect) {
if(style.backgroundRect._matrix){
style.backgroundRect._matrix.tx = actual_x;
style.backgroundRect._matrix.ty = actual_y;
}
background.graphics.beginGradientFill(style.backgroundRect._gradientType, style.backgroundRect._colors, style.backgroundRect._alphas, style.backgroundRect._ratios, style.backgroundRect._matrix, style.backgroundRect._spreadMethod, style.backgroundRect._interpolationMethod, style.backgroundRect._focalPointRatio);
background.graphics.drawRoundRectComplex(realX, realY, w, h, style.backgroundRect._radius[0], style.backgroundRect._radius[1], style.backgroundRect._radius[2], style.backgroundRect._radius[3]);
background.graphics.endFill();
}

if (style.backgroundRect is BitmapRect) {
if(style.backgroundRect._matrix){
style.backgroundRect._matrix.tx = actual_x;
style.backgroundRect._matrix.ty = actual_y;
}
background.graphics.beginBitmapFill(style.backgroundRect._bitmap, style.backgroundRect._matrix, style.backgroundRect._repeat, style.backgroundRect._smooth);
background.graphics.drawRoundRectComplex(realX, realY, w, h, style.backgroundRect._radius[0], style.backgroundRect._radius[1], style.backgroundRect._radius[2], style.backgroundRect._radius[3]);
background.graphics.endFill();
}

txt.width = w - style.textPadding.left - style.textPadding.right;
txt.height = h - style.textPadding.top - style.textPadding.bottom;
txt.x = realX + style.textPadding.left;
txt.y = realY + style.textPadding.top;

var i:int;
for (i = 0; i < content.length; i++) {
content[i].c.x = realX + content[i].x;
content[i].c.y = realY + content[i].y;
}
}

private function updateSize():void {
if (style.maxWidth == 0) style.maxWidth = parentW;
if (style.maxHeight == 0) style.maxHeight = parentH;
txt.wordWrap = true;
w = txt.textWidth + 4 + style.textPadding.left + style.textPadding.right;
h = txt.textHeight + 4 + style.textPadding.top + style.textPadding.bottom;
if (w > style.maxWidth) w = style.maxWidth;
if (h > style.maxHeight) h = style.maxHeight;
if (w < style.minWidth) w = style.minWidth;
if (h < style.minHeight) h = style.minHeight;

// calculate best direction
if (this.y <= h-style.offsetY) { 
direction = "bottom"; 
actual_y = -style.offsetY;
} else
if (this.y > h-style.offsetY) { 
direction = "top";
actual_y = -h + style.offsetY;
}

if (this.x <= parentW * 0.5) { 
direction += "right"; 
actual_x = - style.offsetX;
} else
if (this.x > parentW * 0.5) { 
direction += "left";
actual_x = -w + style.offsetX;
}

// If sticks out on the right:
if(direction == "topright" || direction == "bottomright"){
// if tooltip sticks out of border - calculate shortened width to fit the content
if (this.x + actual_x + w > parentW) {
txt.wordWrap = true;
w = parentW - this.x - actual_x;
h = txt.textHeight + 4 + style.textPadding.top + style.textPadding.bottom;
if (w > style.maxWidth) w = style.maxWidth;
if (h > style.maxHeight) h = style.maxHeight;
if (w < style.minWidth) {
w = style.minWidth;
this.x = parentW - w + style.offsetX;
}
if (h < style.minHeight) h = style.minHeight;
} else {
// if doesn't stick out, keep adding 1 pixel to the width until as much text is seen as possible
while (this.x + actual_x + w < parentW && w < style.maxWidth && w >= style.minWidth) {
w += 1;
// try turning off wordwrap and see if it still fits (as one line)
txt.wordWrap = false;
if (w > txt.textWidth + 4 + style.textPadding.left + style.textPadding.right && w >= style.minWidth && w < style.maxWidth) {
w = txt.textWidth + 4 + style.textPadding.left + style.textPadding.right;
if (w < style.minWidth) {
w = style.minWidth;
}
break;
}
txt.wordWrap = true;
}
}
}

// If sticks out on the left:
if(direction == "topleft" || direction == "bottomleft"){
if (this.x + actual_x <= 0) {
txt.wordWrap = true;
actual_x = -this.x;
w = this.x + style.offsetX;
h = txt.textHeight + 4 + style.textPadding.top + style.textPadding.bottom;
if (w > style.maxWidth) w = style.maxWidth;
if (h > style.maxHeight) h = style.maxHeight;
if (w < style.minWidth) w = style.minWidth;
if (h < style.minHeight) h = style.minHeight;
} else {
// if doesn't stick out, keep adding 1 pixel to the width (and substracting it from actual_x) until as much text is seen as possible
while (this.x + actual_x > 0 && w < style.maxWidth && w >= style.minWidth) {
actual_x -= 1;
w += 1;
// try turning off wordwrap and see if it still fits (as one line)
txt.wordWrap = false;
if (w > txt.textWidth + 4 + style.textPadding.left + style.textPadding.right && w >= style.minWidth && w <= style.maxWidth) {
w = txt.textWidth + 4 + style.textPadding.left + style.textPadding.right;
if (w < style.minWidth) {
w = style.minWidth;
}
break;
}
txt.wordWrap = true; 
}
}
}

}

}

}

Thanks for reading!

The results:


1 comment:

Aimo said...

This is already now very beautiful movements effect!

Post a Comment