Enlarge requested bbox by 20% on each side to make for easier panning; keep list...
[potlatch2.git] / net / systemeD / halcyon / NodeUI.as
1 package net.systemeD.halcyon {
2
3         import flash.display.*;
4         import flash.events.*;
5         import flash.text.AntiAliasType;
6         import flash.text.GridFitType;
7         import flash.text.TextField;
8         import flash.text.TextFormat;
9         import flash.geom.Matrix;
10         import flash.geom.Point;
11         import net.systemeD.halcyon.styleparser.*;
12     import net.systemeD.halcyon.connection.*;
13         
14         /** The graphical representation of a Node (including POIs and nodes that are part of Ways). */
15         public class NodeUI extends EntityUI {
16                 
17                 public var loaded:Boolean=false;
18                 private var iconnames:Object={};                        // name of icon on each sublayer
19                 private var heading:Number=0;                           // heading within way
20                 private var rotation:Number=0;                          // rotation applied to this POI
21                 private static const NO_LAYER:int=-99999;
22
23                 /**
24                  * @param node The corresponding Node.
25                  * @param paint MapPaint object to attach this NodeUI to.
26                  * @param heading Optional angle.
27                  * @param layer Which layer on the MapPaint object it sits on. @default Top layer
28                  * @param stateClasses A settings object definining the initial state of the node (eg, highlighted, hover...) */
29                 public function NodeUI(node:Node, paint:MapPaint, heading:Number=0, layer:int=NO_LAYER, stateClasses:Object=null) {
30                         super(node,paint);
31                         if (layer==NO_LAYER) { this.layer=paint.maxlayer; } else { this.layer=layer; }
32                         this.heading = heading;
33                         if (stateClasses) {
34                                 for (var state:String in stateClasses) {
35                                         if (stateClasses[state]) { this.stateClasses[state]=stateClasses[state]; }
36                                 }
37                         }
38                         entity.addEventListener(Connection.NODE_MOVED, nodeMoved, false, 0, true);
39             entity.addEventListener(Connection.NODE_ALTERED, nodeAltered, false, 0, true);
40             entity.addEventListener(Connection.ENTITY_DRAGGED, nodeDragged, false, 0, true);
41             attachRelationListeners();
42                         redraw();
43                 }
44                 
45                 public function removeEventListeners():void {
46                         removeGenericEventListeners();
47                         entity.removeEventListener(Connection.NODE_MOVED, nodeMoved);
48             entity.removeEventListener(Connection.NODE_ALTERED, nodeAltered);
49             entity.removeEventListener(Connection.ENTITY_DRAGGED, nodeDragged);
50                         removeRelationListeners();
51                 }
52
53                 /** Respond to movement event. */
54                 public function nodeMoved(event:Event):void {
55                     updatePosition();
56                 }
57
58         private function nodeAltered(event:Event):void {
59             redraw();
60         }
61
62                 private function nodeDragged(event:EntityDraggedEvent):void {
63                         updatePosition(event.xDelta,event.yDelta);
64                 }
65
66                 /** Update settings then draw node. */
67                 override public function doRedraw():Boolean {
68                         if (!paint.ready) { return false; }
69                         if (entity.deleted) { return false; }
70
71                         var tags:Object = entity.getTagsCopy();
72                         setStateClass('poi', !entity.hasParentWays);
73             setStateClass('hasTags', entity.hasInterestingTags());
74             setStateClass('dupe', Node(entity).isDupe());
75                         tags=applyStateClasses(tags);
76                         if (!styleList || !styleList.isValidAt(paint.map.scale)) {
77                                 styleList=paint.ruleset.getStyles(entity,tags,paint.map.scale); 
78                         }
79
80                         var suggestedLayer:Number=styleList.layerOverride();
81                         if (!isNaN(suggestedLayer)) { layer=suggestedLayer; }
82
83                         var inWay:Boolean=entity.hasParentWays;
84                         var hasStyles:Boolean=styleList.hasStyles();
85                         
86                         removeSprites(); iconnames={};
87                         return renderFromStyle(tags);
88                 }
89
90                 /** Assemble the layers of icons to draw the node, with hit zone if interactive. */
91                 private function renderFromStyle(tags:Object):Boolean {
92                         var r:Boolean=false;                    // ** rendered
93                         var maxwidth:Number=4;                  // biggest width
94                         var w:Number;
95                         var icon:Sprite;
96                         interactive=false;
97                         for each (var sublayer:Number in styleList.sublayers) {
98
99                                 if (styleList.pointStyles[sublayer]) {
100                                         var s:PointStyle=styleList.pointStyles[sublayer];
101                                         interactive||=s.interactive;
102                                         r=true;
103                                         if (s.rotation) { rotation=s.rotation; }
104                                         if (s.icon_image!=iconnames[sublayer]) {
105                                                 if (s.icon_image=='square') {
106                                                         // draw square
107                                                         icon=new Sprite();
108                                                         addToLayer(icon,STROKESPRITE,sublayer);
109                                                         w=styleIcon(icon,sublayer);
110                                                         icon.graphics.drawRect(0,0,w,w);
111                                                         if (s.interactive) { maxwidth=Math.max(w,maxwidth); }
112                                                         iconnames[sublayer]='_square';
113
114                                                 } else if (s.icon_image=='circle') {
115                                                         // draw circle
116                                                         icon=new Sprite();
117                                                         addToLayer(icon,STROKESPRITE,sublayer);
118                                                         w=styleIcon(icon,sublayer);
119                                                         icon.graphics.drawCircle(w,w,w);
120                                                         if (s.interactive) { maxwidth=Math.max(w,maxwidth); }
121                                                         iconnames[sublayer]='_circle';
122
123                                                 } else if (paint.ruleset.images[s.icon_image]) {
124                                                         // 'load' icon (actually just from library)
125                                                         var loader:ExtendedLoader = new ExtendedLoader();
126                                                         loader.info['sublayer']=sublayer;
127                                                         loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadedIcon, false, 0, true);
128                                                         loader.loadBytes(paint.ruleset.images[s.icon_image]);
129                                                         iconnames[sublayer]=s.icon_image;
130                                                 }
131                                         }
132                                 }
133
134                                 // name sprite
135                                 var a:String='', t:TextStyle;
136                                 if (styleList.textStyles[sublayer]) {
137                                         t=styleList.textStyles[sublayer];
138                                         interactive||=t.interactive;
139                                         a=tags[t.text];
140                                 }
141
142                                 if (a) { 
143                                         var name:Sprite=new Sprite();
144                                         addToLayer(name,NAMESPRITE);
145                                         t.writeNameLabel(name,a,0,0);
146                     loaded=true;
147                                 }
148                         }
149                         if (!r) { return false; }
150                         if (interactive) { addHitSprite(maxwidth); }
151                         updatePosition();
152                         return true;
153                 }
154
155
156                 private function styleIcon(icon:Sprite, sublayer:Number):Number {
157                         loaded=true;
158
159                         // get colours
160                         if (styleList.shapeStyles[sublayer]) {
161                                 var s:ShapeStyle=styleList.shapeStyles[sublayer];
162                                 if (!isNaN(s.color)) { icon.graphics.beginFill(s.color, s.opacity ? s.opacity : 1);
163                                         }
164                                 if (s.casing_width || !isNaN(s.casing_color)) {
165                                         icon.graphics.lineStyle(s.casing_width ? s.casing_width : 1,
166                                                                                         s.casing_color ? s.casing_color : 0,
167                                                                                         s.casing_opacity ? s.casing_opacity : 1);
168                                 }
169                         }
170
171                         // return width
172                         return styleList.pointStyles[sublayer].icon_width;
173                 }
174
175                 private function addHitSprite(w:uint):void {
176             hitzone = new Sprite();
177             hitzone.graphics.lineStyle(4, 0x000000, 1, false, "normal", CapsStyle.ROUND, JointStyle.ROUND);
178                         hitzone.graphics.beginFill(0);
179                         hitzone.graphics.drawRect(0,0,w,w);
180                         hitzone.visible = false;
181                         setListenSprite();
182                 }
183
184                 private function loadedIcon(event:Event):void {
185                         var icon:Sprite=new Sprite();
186                         var sublayer:Number=event.target.loader.info['sublayer'];
187                         addToLayer(icon,STROKESPRITE,sublayer);
188                         icon.addChild(Bitmap(event.target.content));
189                         addHitSprite(icon.width);
190                         loaded=true;
191                         updatePosition();
192                 }
193
194                 private function updatePosition(xDelta:Number=0,yDelta:Number=0):void {
195                         if (!loaded) { return; }
196
197                         var baseX:Number=paint.map.lon2coord(Node(entity).lon);
198                         var baseY:Number=paint.map.latp2coord(Node(entity).latp);
199                         for (var i:uint=0; i<sprites.length; i++) {
200                                 var d:DisplayObject=sprites[i];
201                                 d.x=0; d.y=0; d.rotation=0;
202
203                                 var m:Matrix=new Matrix();
204                                 m.translate(-d.width/2,-d.height/2);
205                                 m.rotate(rotation);
206                                 m.translate(baseX+xDelta,baseY+yDelta);
207                                 d.transform.matrix=m;
208                         }
209                 }
210         public function hitTest(x:Number, y:Number):Node {
211             if (hitzone && hitzone.hitTestPoint(x,y,true)) { return entity as Node; }
212             return null;
213         }
214                 
215         }
216 }