Share 'Tutorial: Creating tooltips with the AS3Commons ToolTipManager' on Delicious Share 'Tutorial: Creating tooltips with the AS3Commons ToolTipManager' on Facebook Share 'Tutorial: Creating tooltips with the AS3Commons ToolTipManager' on Google Bookmarks Share 'Tutorial: Creating tooltips with the AS3Commons ToolTipManager' on Twitter

Article | Published: 10. Juni 2011 | Changed: 15. Dezember 2014 | Category: UI | 5 Comments
  1. About the ToolTipManager class
  2. About this tutorial
  3. Requirements
    1. Owner and tooltip size
    2. External libraries
  4. Creating a simple moveable box and a basic tooltip
  5. Setup of ToolTipManager
    1. Triggering the tooltip from the box
    2. Register the tooltip
  6. Customizing the tooltip placement
    1. Anchors
    2. Offset
    3. Bounds
    4. Auto swap anchors
    5. Auto swap anchors tolerance
  7. Animating tooltip show and hide events
    1. Setting the tooltips position
    2. startAutoHide()
    3. commitRemove()
  8. Adavanced Tooltip
  9. The final code and the compiled swf
  10. Comments (5)
  11. Leave a Comment

About the ToolTipManager class

The ToolTipManager is included in the layers package of the AS3Commons UI project. The class lets you create custom tooltips and register them to be shown for different display objects throughout the application. It is meant to be a pure ActionScript alternative to the well-known tooltip managers incorporated by Flex or the Flash component package.

About this tutorial

In this tutorial we are going to create a small application containing a couple of boxes where each box will show a tooltip on mouse over. The tooltips should in no case exceed the stage’s boundaries.

ToolTipTutorial.swf

If you want to see the final code first, jump to the last section of this tutorial.

The tutorial explains:

  1. Creating and managing an instance of ToolTipManager
  2. Creating a tooltip component
  3. Creating a tooltip adapter
  4. Assigning tooltips to components
  5. Animating the tooltip’s show and hide events

Requirements

Owner and tooltip size

The ToolTipManager relies on the width and height properties of both the owner and the tooltip object. You need to make sure that these properties are set and return the actual size of the component at the time the ToolTipManager.show() method is invoked. Otherwise the ToolTipManager cannot set a valid position of the tooltip.

External libraries

To run the code shown in this tutorial you need to install the following libraries.

  • AS3Commons UI 0.3 – Contains the PopUpManager and several layout algorithms
  • AS3Commons Collections 1.3.0 – Data structures required by AS3Commons UI
  • GTween 2.01 – Tweening library of choice

All libraries are included in the ZIP download of the AS3Commons UI project.

Creating a simple moveable box and a basic tooltip

To be able to test the behaviour of our later tooltip we need a source or owner object that can be freely moved aournd the stage. Such an object is not very special and easliy created. We will call it box. Moving the mouse over the box will bring the box to front before all other boxes. Moving the box is restricted to the specified boundaries. We store these boundaries in a global variable since we will later use them on different places.

ToolTipTutorialStep1.swf
Box.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package layer.tooltip.tooltiptutorial.step1 {
  import common.ColorUtil;
  import flash.display.GradientType;
  import flash.display.Sprite;
  import flash.events.MouseEvent;
  import flash.geom.Matrix;
  import flash.geom.Point;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.TextFormat;

  public class Box extends Sprite {
    private static var _boxId : uint;
    private var _color : uint;
    private var _id : uint;
    private var _mousePosition : Point;

    public function Box(x : uint, y : uint, width : uint, height : uint, color : uint) {
      this.x = x;
      this.y = y;
      _color = color;
      _id = ++_boxId;

      var matrix : Matrix = new Matrix();
      matrix.createGradientBox(width, height, Math.PI / 180 * 45, 0, 0);
      var gradient : Array = ColorUtil.getGradient(_color);

      with (graphics) {
        beginGradientFill(GradientType.LINEAR, gradient, [1, 1], [0, 255], matrix);
        drawRoundRect(0, 0, width, height, 6, 6);
      }
     
      var tf : TextField = new TextField();
      tf.defaultTextFormat = new TextFormat("_sans", 12, 0xFFFFFF);
      tf.text = "" + _id;
      tf.x = tf.y = 4;
      tf.autoSize = TextFieldAutoSize.LEFT;
      addChild(tf);

      mouseChildren = false;
      addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
      addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
    }

    private function mouseOverHandler(event : MouseEvent) : void {
      parent.addChild(this); // move to top
    }

    private function mouseDownHandler(event : MouseEvent) : void {
      _mousePosition = new Point(mouseX, mouseY);
      stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
      stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    }

    private function mouseUpHandler(event : MouseEvent) : void {
      stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
      stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    }
   
    private function mouseMoveHandler(event : MouseEvent) : void {
      var point : Point = new Point(event.stageX, event.stageY);
      point.x -= _mousePosition.x;
      point.y -= _mousePosition.y;
      point = parent.globalToLocal(point);

      point.x = Math.max(Globals.bounds.left, point.x);
      point.x = Math.min(Globals.bounds.right - width, point.x);
      point.y = Math.max(Globals.bounds.top, point.y);
      point.y = Math.min(Globals.bounds.bottom - height, point.y);

      x = point.x;
      y = point.y;
    }
  }
}
Globals.as
1
2
3
4
5
6
7
package layer.tooltip.tooltiptutorial.step1 {
  import flash.geom.Rectangle;

  public class Globals {
    public static var bounds : Rectangle;
  }
}

A basic tooltip is roughly a text field with background and border. Since a tooltip can be shared between different components, it should be possible to assign text at runtime.

BoxToolTip.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package layer.tooltip.tooltiptutorial.step1 {
  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.TextFormat;

  public class BoxToolTip extends Sprite {
    private var _tf : TextField;
   
    public function BoxToolTip() {
      _tf = new TextField();
      _tf.defaultTextFormat = new TextFormat("_sans", 10, 0x333333);
      _tf.background = true;
      _tf.backgroundColor = 0xFFFFEE;
      _tf.border = true;
      _tf.borderColor = 0xAAAA66;
      _tf.autoSize = TextFieldAutoSize.LEFT;
      addChild(_tf);
    }
   
    public function set text(text : String) : void {
      _tf.wordWrap = false;
      _tf.text = text;
      if (_tf.textWidth > 140) {
        _tf.width = 140;
        _tf.wordWrap = true;
      }
    }
  }
}

Setup of ToolTipManager

The ToolTipManager expects a tooltip container display object within its constructor. We we will create the container at the very top of our application. There should be only one instance of ToolTipManager for a single application. We hence store the ToolTipManager instance in our global class Globals created in step 1.

...
Globals.bounds = bounds;

// init tooltips
var container : Sprite = addChild(new Sprite()) as Sprite;
Globals.toolTipManager = new ToolTipManager(container);

// add content
var items : Sprite = addChildAt(new Sprite(), 0) as Sprite;
...
Globals.as
1
2
3
4
5
6
7
8
9
package layer.tooltip.tooltiptutorial.final {
  import org.as3commons.ui.layer.ToolTipManager;
  import flash.geom.Rectangle;

  public class Globals {
    public static var toolTipManager : ToolTipManager;
    public static var bounds : Rectangle;
  }
}

Triggering the tooltip from the box

The box should show a tooltip when the move enters the box and hide when the mouse leaves. Once we have the single ToolTipManager accessible by the box we can put the appropriate code.

Box.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package layer.tooltip.tooltiptutorial.final {
  import common.ColorUtil;
  import flash.display.GradientType;
  import flash.display.Sprite;
  import flash.events.MouseEvent;
  import flash.geom.Matrix;
  import flash.geom.Point;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.TextFormat;

  public class Box extends Sprite {
    private static var _boxId : uint;
    private var _color : uint;
    private var _id : uint;
    private var _mousePosition : Point;

    public function Box(x : uint, y : uint, width : uint, height : uint, color : uint) {
      this.x = x;
      this.y = y;
      _color = color;
      _id = ++_boxId;

      var matrix : Matrix = new Matrix();
      matrix.createGradientBox(width, height, Math.PI / 180 * 45, 0, 0);
      var gradient : Array = ColorUtil.getGradient(_color);

      with (graphics) {
        beginGradientFill(GradientType.LINEAR, gradient, [1, 1], [0, 255], matrix);
        drawRoundRect(0, 0, width, height, 6, 6);
      }
     
      var tf : TextField = new TextField();
      tf.defaultTextFormat = new TextFormat("_sans", 12, 0xFFFFFF);
      tf.text = "" + _id;
      tf.x = tf.y = 4;
      tf.autoSize = TextFieldAutoSize.LEFT;
      addChild(tf);

      mouseChildren = false;
      addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
      addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
    }

    private function mouseOverHandler(event : MouseEvent) : void {
      parent.addChild(this); // move to top
      removeEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
      addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
     
      showToolTip();
    }

    private function mouseOutHandler(event : MouseEvent) : void {
      removeEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
      addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);

      hideToolTip();
    }
   
    private function mouseDownHandler(event : MouseEvent) : void {
      _mousePosition = new Point(mouseX, mouseY);
      stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
      stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    }

    private function mouseUpHandler(event : MouseEvent) : void {
      stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
      stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    }
   
    private function mouseMoveHandler(event : MouseEvent) : void {
      var point : Point = new Point(event.stageX, event.stageY);
      point.x -= _mousePosition.x;
      point.y -= _mousePosition.y;
      point = parent.globalToLocal(point);

      point.x = Math.max(Globals.bounds.left, point.x);
      point.x = Math.min(Globals.bounds.right - width, point.x);
      point.y = Math.max(Globals.bounds.top, point.y);
      point.y = Math.min(Globals.bounds.bottom - height, point.y);

      x = point.x;
      y = point.y;
      showToolTip(); // move tooltip
    }
   
    private function showToolTip() : void {
      var text : String = "Box" + _id + " " + ColorUtil.hexToString(_color);
      if (_id == 2) {
        text = "Box" + "\n\n"
          + "ID: " + _id + "\n"
          + "Color: " + ColorUtil.hexToString(_color) + "\n\n"
          + "Info: This is a large tooltip that will be autosized to a max width of 140 px.";
      }
      Globals.toolTipManager.show(this, text);
    }

    private function hideToolTip() : void {
      Globals.toolTipManager.hide();
    }
  }
}

Register the tooltip

A tooltip can be shown by invoking the ToolTipManager.show(owner, content) method. Internally the ToolTipManager decides what actual tooltip class is to use for the particular owner component. To connect our simple tooltip with the box object we need to register it.

The ToolTipManager is designed to support arbitrary display objects as both owner and tooltip. To anyhow let the ToolTipManager communicate with our tooltip we need to create an adapter that will serve as an mediator between both. A tooltip adapter must subclass the base ToolTipAdapter class and here override certain adapter hooks.

The ToolTipAdapter hooks

onToolTip()

When it is invoked

  • Once for a tooltip
  • Right after the tooltip has been registered

Purpose

Set up initial adapter properties such as offsets or enable anchor swaps.

onContent()

When it is invoked

Called whenever the source object and content have been changed.

Purpose

The content usually will be someting literally. This hook should forward the content specified by the owner object to the tooltip. The tooltip is supposed store this content and to calculate here it’s final dimensions. The tooltip does not necessary need to draw its final appearance here. The tooltip adapter will query the tooltip’s dimensions right after this hook has been invoked and calculate the tooltip’s position.

onDraw()

When it is invoked

Called whenever a hidden tooltip should be shown.

Purpose

The tooltip is supposed to set the tooltip’s position and create its final appearance.

onRemove()

When it is invoked

Called whenever a tooltip should be hidden.

Purpose

To finalize the removal, the adapter needs to commit the removal by invocation of commitRemove() within this hook. The adapter may call commitRemove() synchronously or asynchronously. The latter case is appropriate if some transitions should be performed before the tooltip leaves.

Simple tooltip adapter

The most simple adapter only overrides the onContent() hook and forward the given content to the tooltip.

public class MyToolTipAdapter extends ToolTipAdapter {
  override protected function onContent(toolTip : DisplayObject, content : *) : void {
    MyToolTip(toolTip).text = content;
  }
}

The tooltip selector

The tooltip registration additionally requires the declaration of a tooltip selector together with the adapter and the tooltip itself. The selector accepts an arbitrary display object and decides if this object should use the mapped tooltip adapter or not. The selector is a perfect pattern to assign a certain tooltip right to a class of components while enabling different tootlips for different components.

public class MyTipSelector implements IToolTipSelector {
    public function approve(displayObject : DisplayObject) : Boolean {
      return displayObject is MyComponent;
    }
}

The registration

Having tooltip, adapter and selector, the registration is a single line of code:

_toolTipManager.registerToolTip(
  new MyToolTipSelector(),
  new MyToolTipAdapter(),
  new MyToolTip()
);

Here is our step 2 code with tooltip adapter, selector and registration. The tooltip did not change from step1.

ToolTipTutorialStep2.swf
ToolTipTutorialStep2.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package layer.tooltip.tooltiptutorial.step2 {
  import layer.tooltip.tooltiptutorial.final.Box;
  import layer.tooltip.tooltiptutorial.final.BoxToolTipSelector;
  import layer.tooltip.tooltiptutorial.final.Globals;
  import layer.tooltip.tooltiptutorial.step1.BoxToolTip;
  import org.as3commons.ui.layer.ToolTipManager;
  import flash.display.Sprite;
  import flash.events.Event;
  import flash.geom.Rectangle;

  public class ToolTipTutorialStep2 extends Sprite {
    public function ToolTipTutorialStep2() {
      addEventListener(Event.ADDED_TO_STAGE, init);
    }

    private function init(event : Event) : void {
      removeEventListener(Event.ADDED_TO_STAGE, init);

      // init bounds
      var bounds : Rectangle = new Rectangle(20, 20, 400, 300);
      with (graphics) {
        lineStyle(1, 0xCCCCCC);
        drawRect(bounds.x - 1, bounds.y - 1, bounds.width + 1, bounds.height + 1);
      }
      Globals.bounds = bounds;

      // init tooltips
      var container : Sprite = addChild(new Sprite()) as Sprite;
      Globals.toolTipManager = new ToolTipManager(container);
      Globals.toolTipManager.registerToolTip(
        new BoxToolTipSelector(),
        new BoxToolTipAdapter(),
        new BoxToolTip()
      );

      // add content
      var items : Sprite = addChildAt(new Sprite(), 0) as Sprite;
      items.addChild(new Box(60, 60, 80, 40, 0xAAAAAA));
      items.addChild(new Box(120, 230, 60, 60, 0x666666));
      items.addChild(new Box(180, 100, 40, 80, 0xEE4400));
      items.addChild(new Box(300, 50, 60, 60, 0x0044EE));
      items.addChild(new Box(280, 180, 40, 40, 0x44CC44));
    }
  }
}
BoxToolTipAdapter.as
1
2
3
4
5
6
7
8
9
10
package layer.tooltip.tooltiptutorial.step2 {
  import layer.tooltip.tooltiptutorial.step1.BoxToolTip;
  import org.as3commons.ui.layer.tooltip.ToolTipAdapter;
  import flash.display.DisplayObject;
  public class BoxToolTipAdapter extends ToolTipAdapter {
    override protected function onContent(toolTip : DisplayObject, content : *) : void {
      BoxToolTip(toolTip).text = content;
    }
  }
}
BoxToolTipSelector.as
1
2
3
4
5
6
7
8
9
10
package layer.tooltip.tooltiptutorial.final {
  import org.as3commons.ui.layer.tooltip.IToolTipSelector;
  import flash.display.DisplayObject;

  public class BoxToolTipSelector implements IToolTipSelector {
    public function approve(displayObject : DisplayObject) : Boolean {
      return displayObject is Box;
    }
  }
}

The tooltips are now already working but could be improved a bit to not exceed the boundaries and to slightly extend into (overlap) the owner object.

Customizing the tooltip placement

There are different tooltip placement rules we may set up in our tooltip adapter. The appropriate adapter hook is onToolTip().

Anchors

Aligns owner and tooltip object. By default the owner anchor is PlacementAnchor.TOP_RIGHT and the tooltip anchor is PlacementAnchor.BOTTOM_LEFT. To modify the default anchors to show the tooltips centered below their owner we modify our tooltip adapter like this:

TestBoxToolTipAdapter.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package layer.tooltip.tooltiptutorial.step3 {
  import layer.tooltip.tooltiptutorial.step1.BoxToolTip;
  import org.as3commons.ui.layer.placement.PlacementAnchor;
  import org.as3commons.ui.layer.tooltip.ToolTipAdapter;
  import flash.display.DisplayObject;
  public class TestBoxToolTipAdapter extends ToolTipAdapter {
    override protected function onToolTip(toolTip : DisplayObject) : void {
      ownerAnchor = PlacementAnchor.BOTTOM;
      toolTipAnchor = PlacementAnchor.TOP;
    }

    override protected function onContent(toolTip : DisplayObject, content : *) : void {
      BoxToolTip(toolTip).text = content;
    }
  }
}
ToolTipTutorialStep3.swf

Offset

Shifts the tooltip from the default placement by the value specified by the offset point object.

Bounds

If specified, all tooltip positions will be auto corrected to keep the tooltip within the boundary rect.

Auto swap anchors

If bounds are given, the tooltip will be locked at the bounds in the case the tooltip would exceed the bounds. The tooltip adapter enables you to let the placement anchors swap automatically. A tooltip that would jut out the right side is then swapped to the left side of the owner object.

Auto swap anchors tolerance

This is a combination of auto swap and simple auto correction. A tolerance value starting from that the swap will be performed. If set to 10, the tooltip will be swapped not until it would exceed the bounds by the amount of 10 pixel.

We configure our tooltip adapter for the following placement rules:

  • Offet of (-10, 5)
  • Stop on bounds
  • Swap anchors horizontally with a tolerance of 25 pixel
  • Swap anchors vertically with a tolerance of 10 pixel

Additionally we set the autoHideAfter property to 2 seconds. After two seconds the tooltip will disappear automatically.

BoxToolTipAdapter.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package layer.tooltip.tooltiptutorial.step3 {
  import layer.tooltip.tooltiptutorial.final.Globals;
  import layer.tooltip.tooltiptutorial.step1.BoxToolTip;
  import org.as3commons.ui.layer.tooltip.ToolTipAdapter;
  import flash.display.DisplayObject;
  import flash.geom.Point;

  public class BoxToolTipAdapter extends ToolTipAdapter {
    override protected function onToolTip(toolTip : DisplayObject) : void {
      offset = new Point(-10, 5);
      autoHideAfter = 2000;
      bounds = Globals.bounds;
      autoSwapAnchorsH = true;
      autoSwapAnchorsHDiff = 25;
      autoSwapAnchorsV = true;
      autoSwapAnchorsVDiff = 10;
    }

    override protected function onContent(toolTip : DisplayObject, content : *) : void {
      BoxToolTip(toolTip).text = content;
    }
  }
}
ToolTipTutorialStep3b.swf

Our tooltip is already quite comfortable. The next improvement is to animate the tooltip’s show and hide events.

Animating tooltip show and hide events

Animation is not actually subject of this tutorial. However, some notes here:

When you override the onShow() adapter hook you are in charge to set the tooltip position and invoke the startAutoHide() command on your own.

When you override the onRemove() adapter hook you are in charge to invoke the commitRemove() command on your own.

Reason for this reversion of control is that only you as the adapter developer know the point of time the commands for auto hide or remove are safe to execute.

Setting the tooltips position

By default, onShow() sets the correct tooltip position. You might add certain asynchronous transitions and set the position at a later time.

startAutoHide()

Starts the auto hide delay if the autoHideAfter property is set. This command is invoked by the base implementation of onShow(). You might add certain asynchronous transitions and start the auto hide delay after the tooltip is entirely shown.

commitRemove()

Removes the tooltip eventually. This command is invoked by the base implementation of onRemove(). You might add certain asynchronous transitions and remove the tooltip after the transitions have been finished.

The following adapter animates the tooltip’s show and hide events.

BoxToolTipAdapter.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package layer.tooltip.tooltiptutorial.step4 {
  import layer.tooltip.tooltiptutorial.final.Globals;
  import layer.tooltip.tooltiptutorial.step1.BoxToolTip;
  import com.gskinner.motion.GTween;
  import com.gskinner.motion.easing.Cubic;
  import org.as3commons.ui.layer.tooltip.ToolTipAdapter;
  import flash.display.DisplayObject;
  import flash.geom.Point;
  import flash.utils.clearTimeout;
  import flash.utils.setTimeout;

  public class BoxToolTipAdapter extends ToolTipAdapter {
    private var _tween : GTween;
    private var _delay : uint;

    override protected function onToolTip(toolTip : DisplayObject) : void {
      offset = new Point(-10, 5);
      autoHideAfter = 2000;
      bounds = Globals.bounds;
      autoSwapAnchorsH = true;
      autoSwapAnchorsHDiff = 25;
      autoSwapAnchorsV = true;
      autoSwapAnchorsVDiff = 10;

      _tween = new GTween();
      _tween.target = toolTip;
      _tween.duration = .2;
      _tween.paused = true;

      toolTip.alpha = 0;
    }

    override protected function onContent(toolTip : DisplayObject, content : *) : void {
      BoxToolTip(toolTip).text = content;
    }

    override protected function onShow(toolTip : DisplayObject, local : Point) : void {
      BoxToolTip(toolTip).x = local.x;
      BoxToolTip(toolTip).y = local.y;
     
      pauseTweens();

      if (toolTip.alpha == 1) {
        startAutoHide();
      } else if (toolTip.alpha == 0) {
        _delay = setTimeout(showTween, 500);
      } else {
        showTween();
      }
    }
   
    override protected function onRemove(toolTip : DisplayObject) : void {
      pauseTweens();
     
      if (toolTip.alpha == 0) {
        commitRemove();
      } else if (toolTip.alpha == 1) {
        _delay = setTimeout(hideTween, 100);
      } else {
        hideTween();
      }
    }
   
    private function showTween() : void {
      _tween.duration = (1 - _layer.alpha) * .2;
      _tween.ease = Cubic.easeIn;
      _tween.setValue("alpha", 1);
      _tween.onComplete = function(tween : GTween) : void {
        startAutoHide();
      };
    }

    private function hideTween() : void {
      _tween.duration = (_layer.alpha) * .4;
      _tween.ease = Cubic.easeOut;
      _tween.setValue("alpha", 0);
      _tween.onComplete = function(tween : GTween) : void {
        commitRemove();
      };
    }
   
    private function pauseTweens() : void {
      clearTimeout(_delay);
      _tween.paused = true;
      _tween.onComplete = null;
    }
  }
}
ToolTipTutorialStep4.swf

Adavanced Tooltip

The last step of the tutorial is to update the tooltip to show a nose. The orientation and position of the nose depends on the used placement properties. By default the tooltip is shown top right of its owner. The nose here is bottom left. If the tooltip is swapped horizontally, the nose will be bottom right. Vertically: top-right or top-left. Another requirement is to respect a possible auto correct shift. If the tooltip is auto corrected at the right border and shifted by a value of 20px, the tooltip nose should also move by 20px to the right ;D

See the final code for such an advanced tooltip. It differs to our step 4 version only in the implementation of the drawing of the tooltip and the passing of used placement values to the tooltip.

override protected function onDraw(toolTip : DisplayObject, ownerAnchor : uint, toolTipAnchor : uint, hShift : int, vShift : int) : void {
      BoxToolTip(toolTip).setPlacementProperties(ownerAnchor, toolTipAnchor, hShift);
      BoxToolTip(toolTip).draw();
}

The final code and the compiled swf

ToolTipTutorial.swf
ToolTipTutorial.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package layer.tooltip.tooltiptutorial.final {
  import org.as3commons.ui.layer.ToolTipManager;
  import flash.display.Sprite;
  import flash.events.Event;
  import flash.geom.Rectangle;

  public class ToolTipTutorial extends Sprite {
    public function ToolTipTutorial() {
      addEventListener(Event.ADDED_TO_STAGE, init);
    }

    private function init(event : Event) : void {
      removeEventListener(Event.ADDED_TO_STAGE, init);

      // init bounds
      var bounds : Rectangle = new Rectangle(20, 20, 400, 300);
      with (graphics) {
        lineStyle(1, 0xCCCCCC);
        drawRect(bounds.x - 1, bounds.y - 1, bounds.width + 1, bounds.height + 1);
      }
      Globals.bounds = bounds;

      // init tooltips
      var container : Sprite = addChild(new Sprite()) as Sprite;
      Globals.toolTipManager = new ToolTipManager(container);
      Globals.toolTipManager.registerToolTip(
        new BoxToolTipSelector(),
        new BoxToolTipAdapter(),
        new BoxToolTip()
      );

      // add content
      var items : Sprite = addChildAt(new Sprite(), 0) as Sprite;
      items.addChild(new Box(60, 60, 80, 40, 0xAAAAAA));
      items.addChild(new Box(120, 230, 60, 60, 0x666666));
      items.addChild(new Box(180, 100, 40, 80, 0xEE4400));
      items.addChild(new Box(300, 50, 60, 60, 0x0044EE));
      items.addChild(new Box(280, 180, 40, 40, 0x44CC44));
    }
  }
}
BoxToolTip.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package layer.tooltip.tooltiptutorial.final {
  import common.ColorUtil;
  import org.as3commons.ui.layer.placement.PlacementAnchor;
  import flash.display.GradientType;
  import flash.display.Sprite;
  import flash.geom.Matrix;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.TextFormat;

  public class BoxToolTip extends Sprite {
    private var _tf : TextField;
    private var _noseSize : uint = 10;
   
    private var _ownerAnchor : uint;
    private var _toolTipAnchor : uint;
    private var _hShift : int;

    public function BoxToolTip() {
      _tf = new TextField();
      _tf.defaultTextFormat = new TextFormat("_sans", 10, 0x666666);
      _tf.autoSize = TextFieldAutoSize.LEFT;
      _tf.x = _tf.y = 2;
      addChild(_tf);
    }
   
    override public function get width() : Number {
      return _tf.textWidth + 8;
    }

    override public function get height() : Number {
      return _tf.textHeight + 8 + _noseSize;
    }

    internal function set text(text : String) : void {
      _tf.wordWrap = false;
      _tf.text = text;
      if (_tf.textWidth > 140) {
        _tf.width = 140;
        _tf.wordWrap = true;
      }
    }

    internal function setPlacementProperties(ownerAnchor : uint, toolTipAnchor : uint, hShift : int) : void {
      _ownerAnchor = ownerAnchor;
      _toolTipAnchor = toolTipAnchor;
      _hShift = hShift;
    }

    internal function draw() : void {
      _tf.y = PlacementAnchor.isTop(_ownerAnchor) ? 2 : _noseSize + 2;

      graphics.clear();
     
      // fill
      var matrix : Matrix = new Matrix();
      matrix.createGradientBox(width, height, Math.PI / 180 * 45, 0, 0);
      var gradient : Array = ColorUtil.getGradient(0xFFEE11);
      graphics.beginGradientFill(GradientType.LINEAR, gradient, [1, 1], [0, 255], matrix);

      // background
      var bgY : uint = 0;
      if (PlacementAnchor.isBottom(_ownerAnchor)) bgY = _noseSize;
      graphics.drawRoundRect(0, bgY, width, height - _noseSize, 6, 6);

      // nose
      if (!PlacementAnchor.isMiddle(_ownerAnchor)) {
        var noseX : int = _noseSize;
        if (PlacementAnchor.isCenter(_toolTipAnchor)) noseX = (width - _noseSize) / 2;
        else if (PlacementAnchor.isRight(_toolTipAnchor)) noseX = width - _noseSize * 2.5;
        noseX -= _hShift;
        noseX = Math.max(_noseSize, Math.min(noseX, width - _noseSize * 2.5));
       
        var noseY : uint = PlacementAnchor.isTop(_ownerAnchor) ? height - _noseSize : _noseSize;
        var noseHeight : int = PlacementAnchor.isTop(_ownerAnchor) ? _noseSize : -_noseSize;
       
        with (graphics) {
          moveTo(noseX, noseY);
          lineTo(noseX + _noseSize * .75, noseY + noseHeight);
          lineTo(noseX + _noseSize * 1.5, noseY);
          lineTo(noseX, noseY);
        }
      }
    }
  }
}
BoxToolTipAdapter.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package layer.tooltip.tooltiptutorial.final {
  import com.gskinner.motion.GTween;
  import com.gskinner.motion.easing.Cubic;
  import org.as3commons.ui.layer.tooltip.ToolTipAdapter;
  import flash.display.DisplayObject;
  import flash.geom.Point;
  import flash.utils.clearTimeout;
  import flash.utils.setTimeout;

  public class BoxToolTipAdapter extends ToolTipAdapter {
    private var _tween : GTween;
    private var _delay : uint;
   
    override protected function onToolTip(toolTip : DisplayObject) : void {
      offset = new Point(-25, 5);
      autoHideAfter = 2000;
      bounds = Globals.bounds;
      autoSwapAnchorsH = true;
      autoSwapAnchorsHDiff = 25;
      autoSwapAnchorsV = true;
      autoSwapAnchorsVDiff = 10;

      _tween = new GTween();
      _tween.target = toolTip;
      _tween.duration = .2;
      _tween.paused = true;

      toolTip.alpha = 0;
    }

    override protected function onContent(toolTip : DisplayObject, content : *) : void {
      BoxToolTip(toolTip).text = content;
    }
   
    override protected function onDraw(toolTip : DisplayObject, ownerAnchor : uint, toolTipAnchor : uint, hShift : int, vShift : int) : void {
      BoxToolTip(toolTip).setPlacementProperties(ownerAnchor, toolTipAnchor, hShift);
      BoxToolTip(toolTip).draw();
    }

    override protected function onShow(toolTip : DisplayObject, local : Point) : void {
      BoxToolTip(toolTip).x = local.x;
      BoxToolTip(toolTip).y = local.y;
     
      pauseTweens();

      if (toolTip.alpha == 1) {
        startAutoHide();
      } else if (toolTip.alpha == 0) {
        _delay = setTimeout(showTween, 500);
      } else {
        showTween();
      }
    }
   
    override protected function onRemove(toolTip : DisplayObject) : void {
      pauseTweens();
     
      if (toolTip.alpha == 0) {
        commitRemove();
      } else if (toolTip.alpha == 1) {
        _delay = setTimeout(hideTween, 100);
      } else {
        hideTween();
      }
    }
   
    private function showTween() : void {
      _tween.duration = (1 - _layer.alpha) * .2;
      _tween.ease = Cubic.easeIn;
      _tween.setValue("alpha", 1);
      _tween.onComplete = function(tween : GTween) : void {
        startAutoHide();
      };
    }

    private function hideTween() : void {
      _tween.duration = (_layer.alpha) * .4;
      _tween.ease = Cubic.easeOut;
      _tween.setValue("alpha", 0);
      _tween.onComplete = function(tween : GTween) : void {
        commitRemove();
      };
    }
   
    private function pauseTweens() : void {
      clearTimeout(_delay);
      _tween.paused = true;
      _tween.onComplete = null;
    }
  }
}
BoxToolTipSelector.as
1
2
3
4
5
6
7
8
9
10
package layer.tooltip.tooltiptutorial.final {
  import org.as3commons.ui.layer.tooltip.IToolTipSelector;
  import flash.display.DisplayObject;

  public class BoxToolTipSelector implements IToolTipSelector {
    public function approve(displayObject : DisplayObject) : Boolean {
      return displayObject is Box;
    }
  }
}
Box.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package layer.tooltip.tooltiptutorial.final {
  import common.ColorUtil;
  import flash.display.GradientType;
  import flash.display.Sprite;
  import flash.events.MouseEvent;
  import flash.geom.Matrix;
  import flash.geom.Point;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.TextFormat;

  public class Box extends Sprite {
    private static var _boxId : uint;
    private var _color : uint;
    private var _id : uint;
    private var _mousePosition : Point;

    public function Box(x : uint, y : uint, width : uint, height : uint, color : uint) {
      this.x = x;
      this.y = y;
      _color = color;
      _id = ++_boxId;

      var matrix : Matrix = new Matrix();
      matrix.createGradientBox(width, height, Math.PI / 180 * 45, 0, 0);
      var gradient : Array = ColorUtil.getGradient(_color);

      with (graphics) {
        beginGradientFill(GradientType.LINEAR, gradient, [1, 1], [0, 255], matrix);
        drawRoundRect(0, 0, width, height, 6, 6);
      }
     
      var tf : TextField = new TextField();
      tf.defaultTextFormat = new TextFormat("_sans", 12, 0xFFFFFF);
      tf.text = "" + _id;
      tf.x = tf.y = 4;
      tf.autoSize = TextFieldAutoSize.LEFT;
      addChild(tf);

      mouseChildren = false;
      addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
      addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
    }

    private function mouseOverHandler(event : MouseEvent) : void {
      parent.addChild(this); // move to top
      removeEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
      addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
     
      showToolTip();
    }

    private function mouseOutHandler(event : MouseEvent) : void {
      removeEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
      addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);

      hideToolTip();
    }
   
    private function mouseDownHandler(event : MouseEvent) : void {
      _mousePosition = new Point(mouseX, mouseY);
      stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
      stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    }

    private function mouseUpHandler(event : MouseEvent) : void {
      stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
      stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    }
   
    private function mouseMoveHandler(event : MouseEvent) : void {
      var point : Point = new Point(event.stageX, event.stageY);
      point.x -= _mousePosition.x;
      point.y -= _mousePosition.y;
      point = parent.globalToLocal(point);

      point.x = Math.max(Globals.bounds.left, point.x);
      point.x = Math.min(Globals.bounds.right - width, point.x);
      point.y = Math.max(Globals.bounds.top, point.y);
      point.y = Math.min(Globals.bounds.bottom - height, point.y);

      x = point.x;
      y = point.y;
      showToolTip(); // move tooltip
    }
   
    private function showToolTip() : void {
      var text : String = "Box" + _id + " " + ColorUtil.hexToString(_color);
      if (_id == 2) {
        text = "Box" + "\n\n"
          + "ID: " + _id + "\n"
          + "Color: " + ColorUtil.hexToString(_color) + "\n\n"
          + "Info: This is a large tooltip that will be autosized to a max width of 140 px.";
      }
      Globals.toolTipManager.show(this, text);
    }

    private function hideToolTip() : void {
      Globals.toolTipManager.hide();
    }
  }
}
Globals.as
1
2
3
4
5
6
7
8
9
package layer.tooltip.tooltiptutorial.final {
  import org.as3commons.ui.layer.ToolTipManager;
  import flash.geom.Rectangle;

  public class Globals {
    public static var toolTipManager : ToolTipManager;
    public static var bounds : Rectangle;
  }
}


5 Comments

  1. toytonic

    22. Juni 2011

    hi, thanks for publishing such a cool layout lib.

    I’m currently testing your tooltip and popupmanager implementations.

    Maybe you can guide me a bit for specific scenario:
    I got a control bar with loads of little buttons in a two row layout. The buttons of the upper row need to display the tooltip above the lower low below themselves.

    thanks again for the lib!

  2. Jens Struwe

    22. Juni 2011

    Don’t get the problem. Where are the tooltips right now? Did you try to setup specific anchors (owner=bottom, tooltip=top) in your tooltip adapter implementation?

  3. toytonic

    22. Juni 2011

    sorry for being a bit unclear. I’m aware of setting custom anchors. I need to customize those depending on the button that triggers the display of the tooltip. some need a top placement others a bottom placement.

    I was thinking of providing this information in the content object when triggering show:

    tm.show(this, {text: “test”, placement: “bottom”});

    hope that clarifies my question.

  4. Jens Struwe

    22. Juni 2011

    I was thinking about the ability to override the tooltip adapter settings on demand. I didn’t find it simple enough ;-) Nevertheless, there is an easy solution. You may place some distinction in the tooltip adapter onContent() hook. I’ve created an example that shows how does it work.

  5. Topcoldairintake.Com

    5. Dezember 2017

    Your style is so unique compared to other people I’ve read
    stuff from. Thank you for posting when you have
    the opportunity, Guess I’ll just bookmark this blog.

Leave a Comment

You have a question or have experienced an issue? Please post it in the forum: http://sibirjak.tenderapp.com/ in order to make the discussion available at a more central place.