412d4eaa797668421da547a793ec4b6db96a435f
[potlatch2.git] / net / systemeD / halcyon / MapPaint.as
1 package net.systemeD.halcyon {
2
3         import flash.display.Sprite;
4         import flash.display.DisplayObject;
5         import net.systemeD.halcyon.NodeUI;
6         import net.systemeD.halcyon.WayUI;
7         import net.systemeD.halcyon.connection.*;
8         import net.systemeD.halcyon.styleparser.RuleSet;
9         import net.systemeD.halcyon.Globals;
10
11         /** Manages the drawing of map entities, allocating their sprites etc. */
12         public class MapPaint extends Sprite {
13                 
14                 /** Access Map object */
15                 public var map:Map;
16                 /** Entities on layers below minlayer will not be shown by this paint object. (Confirm?) */
17                 public var minlayer:int;
18                 /** Entities on layers above maxlayer will not be shown by this paint object. (Confirm?) */
19                 public var maxlayer:int;
20                 /** The MapCSS rules used for drawing entities. */
21                 public var ruleset:RuleSet;                                             
22                 /** WayUI objects attached to Way entities that are currently visible. */
23                 public var wayuis:Object=new Object();                  // sprites for ways and (POI/tagged) nodes
24                 /** NodeUI objects attached to POI/tagged node entities that are currently visible. */
25                 // confirm this - it seems to me all nodes in ways get nodeuis? --Steve B
26                 public var nodeuis:Object=new Object();
27                 /** MarkerUI objects attached to Marker entities that are currently visible. */
28         public var markeruis:Object=new Object();
29         /** Is this a background layer or the core paint object? */
30                 public var isBackground:Boolean = true;
31                 /** Hash of index->position */
32                 public var sublayerIndex:Object={};
33
34                 private const VERYBIG:Number=Math.pow(2,16);
35                 private static const NO_LAYER:int=-99999;               // same as NodeUI
36
37                 // Set up layering
38
39                 /** Creates paint sprites and hit sprites for all layers in range. This object ends up with a series of child sprites
40                  * as follows: p0,p1,p2..px, h0,h1,h2..hx where p are "paint sprites" and "h" are "hit sprites". There is one of each type for each layer.
41                  * <p>Each paint sprite has 4 child sprites (fill, casing, stroke, names). Each hit sprite has 2 child sprites (way hit tests, node hit tests).</p>  
42                  * <p>Thus if layers range from -5 to +5, there will be 11 top level paint sprites followed by 11 top level hit sprites.</p>
43                  * 
44                  * @param map The map to be rendered.
45                  * @param minlayer The lowest layer in that map that will be rendered.
46                  * @param maxlayer The top layer in that map that will be rendered.
47                  * */ 
48                 public function MapPaint(map:Map,minlayer:int,maxlayer:int) {
49                         mouseEnabled=false;
50
51                         this.map=map;
52                         this.minlayer=minlayer;
53                         this.maxlayer=maxlayer;
54                         sublayerIndex[1]=0;
55                         var s:Sprite, l:int;
56
57                         // Add paint sprites
58                         for (l=minlayer; l<=maxlayer; l++) {                    // each layer (10 is +5, 0 is -5)
59                                 s = getPaintSprite();                                           //      |
60                                 s.addChild(getPaintSprite());                           //      | 0 fill
61                                 s.addChild(getPaintSprite());                           //      | 1 casing
62                                 var t:Sprite = getPaintSprite();                        //      | 2 stroke
63                                 t.addChild(getPaintSprite());                           //      |  | sublayer
64                                 s.addChild(t);                                                          //      |  |
65                                 s.addChild(getPaintSprite());                           //      | 3 names
66                                 addChild(s);                                                            //      |
67                         }
68                         
69                         // Add hit sprites
70                         for (l=minlayer; l<=maxlayer; l++) {                    // each layer (21 is +5, 11 is -5)
71                                 s = getHitSprite();                                                     //      |
72                                 s.addChild(getHitSprite());                                     //      | 0 way hit tests
73                                 s.addChild(getHitSprite());                                     //      | 1 node hit tests
74                                 addChild(s);
75                         }
76                 }
77                 
78                 /** Returns the paint surface for the given layer. */
79                 public function getPaintSpriteAt(l:int):Sprite {
80                         return getChildAt(l-minlayer) as Sprite;
81                 }
82
83                 /** Returns the hit sprite for the given layer. */
84                 public function getHitSpriteAt(l:int):Sprite {
85                         return getChildAt((l-minlayer) + (maxlayer-minlayer+1)) as Sprite;
86                 }
87                 
88                 /** Is ruleset loaded? */
89                 public function get ready():Boolean {
90                         if (!ruleset) { return false; }
91                         if (!ruleset.loaded) { return false; }
92                         return true;
93                 }
94
95                 public function sublayer(layer:int,sublayer:Number):Sprite {
96                         var l:DisplayObject;
97                         var o:DisplayObject;
98                         var index:String, ix:Number;
99                         if (!sublayerIndex.hasOwnProperty(sublayer)) {
100                                 // work out which position to add at
101                                 var lowestAbove:Number=VERYBIG;
102                                 var lowestAbovePos:int=-1;
103                                 var indexLength:uint=0;
104                                 for (index in sublayerIndex) {
105                                         ix=Number(index);
106                                         if (ix>sublayer && ix<lowestAbove) {
107                                                 lowestAbove=ix;
108                                                 lowestAbovePos=sublayerIndex[index];
109                                         }
110                                         indexLength++;
111                                 }
112                                 if (lowestAbovePos==-1) { lowestAbovePos=indexLength; }
113                         
114                                 // add sprites
115                                 for (var i:int=minlayer; i<=maxlayer; i++) {
116                                         l=getChildAt(i-minlayer);
117                                         o=(l as Sprite).getChildAt(2);
118                                         (o as Sprite).addChildAt(getPaintSprite(),lowestAbovePos);
119                                 }
120                         
121                                 // update index
122                                 // (we do it in this rather indirect way because if you alter sublayerIndex directly
123                                 //      within the loop, it confuses the iterator)
124                                 var toUpdate:Array=[];
125                                 for (index in sublayerIndex) {
126                                         ix=Number(index);
127                                         if (ix>sublayer) { toUpdate.push(index); }
128                                 }
129                                 for each (index in toUpdate) { sublayerIndex[index]++; }
130                                 sublayerIndex[sublayer]=lowestAbovePos;
131                         }
132
133                         l=getChildAt(layer-minlayer);
134                         o=(l as Sprite).getChildAt(2);
135                         return ((o as Sprite).getChildAt(sublayerIndex[sublayer]) as Sprite);
136                 }
137
138         /**
139         * Update, and if necessary, create / remove UIs for the given objects.
140         * The object is effectively lists of objects split into inside/outside pairs, e.g.
141         * { waysInside: [], waysOutside: [] } where each is a array of entities either inside
142         * or outside this current view window. UIs for the entities on "inside" lists will be created if necessary.
143         * Flags control redrawing existing entities and removing UIs from entities no longer in view.
144         *
145         * @param o The object containing all the relevant entites.
146         * @param redraw If true, all UIs for entities on "inside" lists will be redrawn
147         * @param remove If true, all UIs for entites on "outside" lists will be removed. The purgable flag on UIs
148                         can override this, for example for selected objects.
149         * fixme? add smarter behaviour for way nodes - remove NodeUIs from way nodes off screen, create them for ones
150         * that scroll onto screen (for highlights etc)
151         */
152                 public function updateEntityUIs(o:Object, redraw:Boolean, remove:Boolean):void {
153                         var way:Way, poi:Node, marker:Marker;
154
155                         for each (way in o.waysInside) {
156                                 if (!wayuis[way.id]) { createWayUI(way); }
157                                 else if (redraw) { wayuis[way.id].recalculate(); wayuis[way.id].redraw(); }
158                                 else wayuis[way.id].updateHighlights();//dubious
159                         }
160
161                         if (remove) {
162                                 for each (way in o.waysOutside) {
163                                         if (wayuis[way.id] && !wayuis[way.id].purgable) {
164                                                 if (redraw) { wayuis[way.id].recalculate(); wayuis[way.id].redraw(); }
165                                         } else {
166                                                 deleteWayUI(way);
167                                         }
168                                 }
169                         }
170
171                         for each (poi in o.poisInside) {
172                                 if (!nodeuis[poi.id]) { createNodeUI(poi); }
173                                 else if (redraw) { nodeuis[poi.id].redraw(); }
174                         }
175
176                         if (remove) {
177                                 for each (poi in o.poisOutside) { 
178                                         if (nodeuis[poi.id] && !nodeuis[poi.id].purgable) {
179                                                 if (redraw) { nodeuis[poi.id].redraw(); }
180                                         } else {
181                                                 deleteNodeUI(poi);
182                                         }
183                                 }
184                         }
185
186             for each (marker in o.markersInside) {
187                 if (!markeruis[marker.id]) { createMarkerUI(marker); }
188                 else if (redraw) { markeruis[marker.id].redraw(); }
189             }
190
191             if (remove) {
192                 for each (marker in o.markersOutside) {
193                     if (markeruis[marker.id] && !markeruis[marker.id].purgable) {
194                         if (redraw) { markeruis[marker.id].redraw(); }
195                     } else {
196                         deleteMarkerUI(marker);
197                     }
198                 }
199             }
200                 }
201
202                 /** Make a UI object representing a way. */
203                 public function createWayUI(way:Way):WayUI {
204                         if (!wayuis[way.id]) {
205                                 wayuis[way.id]=new WayUI(way,this);
206                                 way.addEventListener(Connection.WAY_DELETED, wayDeleted);
207                         }
208                         return wayuis[way.id];
209                 }
210
211                 /** Respond to event by removing the WayUI. */
212                 public function wayDeleted(event:EntityEvent):void {
213                         deleteWayUI(event.entity as Way);
214                 }
215
216                 /** Remove a way's UI object. */
217                 public function deleteWayUI(way:Way):void {
218                         way.removeEventListener(Connection.WAY_DELETED, wayDeleted);
219                         if (wayuis[way.id]) {
220                                 wayuis[way.id].redrawMultis();
221                                 wayuis[way.id].removeSprites();
222                                 wayuis[way.id].removeEventListeners();
223                                 delete wayuis[way.id];
224                         }
225                         for (var i:uint=0; i<way.length; i++) {
226                                 var node:Node=way.getNode(i);
227                                 if (nodeuis[node.id]) { deleteNodeUI(node); }
228                         }
229                 }
230
231                 /** Make a UI object representing a node. */
232                 public function createNodeUI(node:Node,rotation:Number=0,layer:int=NO_LAYER,stateClasses:Object=null):NodeUI {
233                         if (!nodeuis[node.id]) {
234                                 nodeuis[node.id]=new NodeUI(node,this,rotation,layer,stateClasses);
235                                 node.addEventListener(Connection.NODE_DELETED, nodeDeleted);
236                         } else {
237                                 for (var state:String in stateClasses) {
238                                         nodeuis[node.id].setStateClass(state,stateClasses[state]);
239                                 }
240                                 nodeuis[node.id].redraw();
241                         }
242                         return nodeuis[node.id];
243                 }
244
245                 /** Respond to event by deleting NodeUI. */
246                 public function nodeDeleted(event:EntityEvent):void {
247                         deleteNodeUI(event.entity as Node);
248                 }
249
250                 /** Remove a node's UI object. */
251                 public function deleteNodeUI(node:Node):void {
252                         if (!nodeuis[node.id]) { return; }
253                         node.removeEventListener(Connection.NODE_DELETED, nodeDeleted);
254                         nodeuis[node.id].removeSprites();
255                         nodeuis[node.id].removeEventListeners();
256                         delete nodeuis[node.id];
257                 }
258
259         /** Make a UI object representing a marker. */
260         public function createMarkerUI(marker:Marker,rotation:Number=0,layer:int=NO_LAYER,stateClasses:Object=null):MarkerUI {
261             if (!markeruis[marker.id]) {
262                 markeruis[marker.id]=new MarkerUI(marker,this,rotation,layer,stateClasses);
263                 marker.addEventListener(Connection.NODE_DELETED, markerDeleted);
264             } else {
265                 for (var state:String in stateClasses) {
266                     markeruis[marker.id].setStateClass(state,stateClasses[state]);
267                 }
268                 markeruis[marker.id].redraw();
269             }
270             return markeruis[marker.id];
271         }
272
273         /** Respond to event by deleting MarkerUI. */
274         public function markerDeleted(event:EntityEvent):void {
275             deleteMarkerUI(event.entity as Marker);
276         }
277
278         /** Remove a marker's UI object. */
279         public function deleteMarkerUI(marker:Marker):void {
280             if (!markeruis[marker.id]) { return; }
281             marker.removeEventListener(Connection.NODE_DELETED, markerDeleted);
282             markeruis[marker.id].removeSprites();
283             markeruis[marker.id].removeEventListeners();
284             delete markeruis[marker.id];
285         }
286                 
287                 public function renumberWayUI(way:Way,oldID:Number):void {
288                         if (!wayuis[oldID]) { return; }
289                         wayuis[way.id]=wayuis[oldID];
290                         delete wayuis[oldID];
291                 }
292
293                 public function renumberNodeUI(node:Node,oldID:Number):void {
294                         if (!nodeuis[oldID]) { return; }
295                         nodeuis[node.id]=nodeuis[oldID];
296                         delete nodeuis[oldID];
297                 }
298
299                 /** Make a new sprite for painting on */
300                 private function getPaintSprite():Sprite {
301                         var s:Sprite = new Sprite();
302                         s.mouseEnabled = false;
303                         s.mouseChildren = false;
304                         return s;
305                 }
306
307                 private function getHitSprite():Sprite {
308                         var s:Sprite = new Sprite();
309                         return s;
310                 }
311
312                 public function redraw():void {
313                         for each (var w:WayUI in wayuis) { w.recalculate(); w.invalidateStyleList(); w.redraw(); }
314                         /* sometimes (e.g. in Map.setStyle) Mappaint.redrawPOIs() is called immediately afterwards anyway. FIXME? */
315                         for each (var p:NodeUI in nodeuis) { p.invalidateStyleList(); p.redraw(); }
316             for each (var m:MarkerUI in markeruis) { m.invalidateStyleList(); m.redraw(); }
317                 }
318
319                 /** Redraw nodes and markers */
320                 public function redrawPOIs():void {
321                         for each (var p:NodeUI in nodeuis) { p.invalidateStyleList(); p.redraw(); }
322             for each (var m:MarkerUI in markeruis) { m.invalidateStyleList(); m.redraw(); }
323                 }
324                 
325                 public function findSource():VectorLayer {
326                         var v:VectorLayer;
327                         for each (v in map.vectorlayers) {
328                                 if (v.paint==this) { return v; }
329                         }
330                         return null;
331                 }
332         }
333 }