Marker UIs, implemented using copy+paste
authorAndy Allan <gravitystorm@gmail.com>
Tue, 16 Nov 2010 15:01:06 +0000 (15:01 +0000)
committerAndy Allan <gravitystorm@gmail.com>
Tue, 16 Nov 2010 15:01:06 +0000 (15:01 +0000)
net/systemeD/halcyon/MapPaint.as
net/systemeD/halcyon/MarkerUI.as [new file with mode: 0644]
net/systemeD/halcyon/VectorLayer.as
net/systemeD/potlatch2/utils/BugLoader.as

index b2e3697..42c75aa 100644 (file)
@@ -16,6 +16,7 @@ package net.systemeD.halcyon {
                public var ruleset:RuleSet;                                             // rules
                public var wayuis:Object=new Object();                  // sprites for ways and (POI/tagged) nodes
                public var nodeuis:Object=new Object();                 //      |
+        public var markeruis:Object=new Object();
                public var isBackground:Boolean = true;                 // is it a background layer or the core paint object?
                public var sublayerIndex:Object={};                             // hash of index->position
 
@@ -111,8 +112,20 @@ package net.systemeD.halcyon {
                        return ((o as Sprite).getChildAt(sublayerIndex[sublayer]) as Sprite);
                }
 
+        /**
+        * Update, and if necessary, create / remove UIs for the given objects.
+        * The object is effectively lists of objects split into inside/outside pairs, e.g.
+        * { waysInside: [], waysOutside: [] } where each is a array of entities either inside
+        * or outside this current view window. UIs for the entities on "inside" lists will be created if necessary.
+        * Flags control redrawing existing entities and removing UIs from entities no longer in view.
+        *
+        * @param o The object containing all the relevant entites.
+        * @param redraw If true, all UIs for entities on "inside" lists will be redrawn
+        * @param remove If true, all UIs for entites on "outside" lists will be removed. The purgable flag on UIs
+                        can override this, for example for selected objects.
+        */
                public function updateEntityUIs(o:Object, redraw:Boolean, remove:Boolean):void {
-                       var way:Way, poi:Node;
+                       var way:Way, poi:Node, marker:Marker;
 
                        for each (way in o.waysInside) {
                                if (!wayuis[way.id]) { createWayUI(way); }
@@ -143,6 +156,21 @@ package net.systemeD.halcyon {
                                        }
                                }
                        }
+
+            for each (marker in o.markersInside) {
+                if (!markeruis[marker.id]) { createMarkerUI(marker); }
+                else if (redraw) { markeruis[marker.id].redraw(); }
+            }
+
+            if (remove) {
+                for each (marker in o.markersOutside) {
+                    if (markeruis[marker.id] && !markeruis[marker.id].purgable) {
+                        if (redraw) { markeruis[marker.id].redraw(); }
+                    } else {
+                        deleteMarkerUI(marker);
+                    }
+                }
+            }
                }
 
                public function createWayUI(way:Way):WayUI {
@@ -195,6 +223,31 @@ package net.systemeD.halcyon {
                        nodeuis[node.id].removeEventListeners();
                        delete nodeuis[node.id];
                }
+
+        public function createMarkerUI(marker:Marker,rotation:Number=0,layer:int=NO_LAYER,stateClasses:Object=null):MarkerUI {
+            if (!markeruis[marker.id]) {
+                markeruis[marker.id]=new MarkerUI(marker,this,rotation,layer,stateClasses);
+                marker.addEventListener(Connection.NODE_DELETED, markerDeleted);
+            } else {
+                for (var state:String in stateClasses) {
+                    markeruis[marker.id].setStateClass(state,stateClasses[state]);
+                }
+                markeruis[marker.id].redraw();
+            }
+            return markeruis[marker.id];
+        }
+
+        public function markerDeleted(event:EntityEvent):void {
+            deleteMarkerUI(event.entity as Marker);
+        }
+
+        public function deleteMarkerUI(marker:Marker):void {
+            if (!markeruis[marker.id]) { return; }
+            marker.removeEventListener(Connection.NODE_DELETED, markerDeleted);
+            markeruis[marker.id].removeSprites();
+            markeruis[marker.id].removeEventListeners();
+            delete markeruis[marker.id];
+        }
                
                public function renumberWayUI(way:Way,oldID:Number):void {
                        if (!wayuis[oldID]) { return; }
diff --git a/net/systemeD/halcyon/MarkerUI.as b/net/systemeD/halcyon/MarkerUI.as
new file mode 100644 (file)
index 0000000..8f6c319
--- /dev/null
@@ -0,0 +1,192 @@
+package net.systemeD.halcyon {
+
+    import flash.display.*;
+    import flash.events.*;
+    import flash.text.AntiAliasType;
+    import flash.text.GridFitType;
+    import flash.text.TextField;
+    import flash.text.TextFormat;
+    import flash.geom.Matrix;
+    import flash.geom.Point;
+    import net.systemeD.halcyon.styleparser.*;
+    import net.systemeD.halcyon.connection.*;
+    import net.systemeD.halcyon.Globals;
+
+    public class MarkerUI extends EntityUI {
+
+        public var loaded:Boolean=false;
+        private var iconnames:Object={};            // name of icon on each sublayer
+        private var heading:Number=0;               // heading within way
+        private var rotation:Number=0;              // rotation applied to this POI
+        private static const NO_LAYER:int=-99999;
+
+        public function MarkerUI(marker:Marker, paint:MapPaint, heading:Number=0, layer:int=NO_LAYER, stateClasses:Object=null) {
+            super(marker,paint);
+            if (layer==NO_LAYER) { this.layer=paint.maxlayer; } else { this.layer=layer; }
+            this.heading = heading;
+            if (stateClasses) {
+                for (var state:String in stateClasses) {
+                    if (stateClasses[state]) { this.stateClasses[state]=stateClasses[state]; }
+                }
+            }
+            entity.addEventListener(Connection.NODE_MOVED, markerMoved);
+            entity.addEventListener(Connection.NODE_ALTERED, markerAltered);
+            attachRelationListeners();
+            redraw();
+        }
+
+        public function removeEventListeners():void {
+            removeGenericEventListeners();
+            entity.removeEventListener(Connection.NODE_MOVED, markerMoved);
+            entity.removeEventListener(Connection.NODE_ALTERED, markerAltered);
+        }
+
+        public function markerMoved(event:Event):void {
+            updatePosition();
+        }
+
+        private function markerAltered(event:Event):void {
+            redraw();
+        }
+
+        override public function doRedraw():Boolean {
+            if (!paint.ready) { return false; }
+            if (entity.deleted) { return false; }
+
+            var tags:Object = entity.getTagsCopy();
+            setStateClass('poi', !entity.hasParentWays);
+            setStateClass('hasTags', entity.hasInterestingTags());
+            tags=applyStateClasses(tags);
+            if (!styleList || !styleList.isValidAt(paint.map.scale)) {
+                styleList=paint.ruleset.getStyles(entity,tags,paint.map.scale);
+            }
+
+            var suggestedLayer:Number=styleList.layerOverride();
+            if (!isNaN(suggestedLayer)) { layer=suggestedLayer; }
+
+            var inWay:Boolean=entity.hasParentWays;
+            var hasStyles:Boolean=styleList.hasStyles();
+
+            removeSprites(); iconnames={};
+            return renderFromStyle(tags);
+        }
+
+        private function renderFromStyle(tags:Object):Boolean {
+            var r:Boolean=false;            // ** rendered
+            var maxwidth:Number=4;          // biggest width
+            var w:Number;
+            var icon:Sprite;
+            interactive=false;
+            for each (var sublayer:Number in styleList.sublayers) {
+
+                if (styleList.pointStyles[sublayer]) {
+                    var s:PointStyle=styleList.pointStyles[sublayer];
+                    interactive||=s.interactive;
+                    r=true;
+                    if (s.rotation) { rotation=s.rotation; }
+                    if (s.icon_image!=iconnames[sublayer]) {
+                        if (s.icon_image=='square') {
+                            // draw square
+                            icon=new Sprite();
+                            addToLayer(icon,STROKESPRITE,sublayer);
+                            w=styleIcon(icon,sublayer);
+                            icon.graphics.drawRect(0,0,w,w);
+                            if (s.interactive) { maxwidth=Math.max(w,maxwidth); }
+                            iconnames[sublayer]='_square';
+
+                        } else if (s.icon_image=='circle') {
+                            // draw circle
+                            icon=new Sprite();
+                            addToLayer(icon,STROKESPRITE,sublayer);
+                            w=styleIcon(icon,sublayer);
+                            icon.graphics.drawCircle(w,w,w);
+                            if (s.interactive) { maxwidth=Math.max(w,maxwidth); }
+                            iconnames[sublayer]='_circle';
+
+                        } else if (paint.ruleset.images[s.icon_image]) {
+                            // 'load' icon (actually just from library)
+                            var loader:ExtendedLoader = new ExtendedLoader();
+                            loader.info['sublayer']=sublayer;
+                            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadedIcon);
+                            loader.loadBytes(paint.ruleset.images[s.icon_image]);
+                            iconnames[sublayer]=s.icon_image;
+                        }
+                    }
+                }
+
+                // name sprite
+                var a:String='', t:TextStyle;
+                if (styleList.textStyles[sublayer]) {
+                    t=styleList.textStyles[sublayer];
+                    interactive||=t.interactive;
+                    a=tags[t.text];
+                }
+
+                if (a) {
+                    var name:Sprite=new Sprite();
+                    addToLayer(name,NAMESPRITE);
+                    t.writeNameLabel(name,a,0,0);
+                    loaded=true;
+                }
+            }
+            if (!r) { return false; }
+            if (interactive) { addHitSprite(maxwidth); }
+            updatePosition();
+            return true;
+        }
+
+
+        private function styleIcon(icon:Sprite, sublayer:Number):Number {
+            loaded=true;
+
+            // get colours
+            if (styleList.shapeStyles[sublayer]) {
+                var s:ShapeStyle=styleList.shapeStyles[sublayer];
+                if (!isNaN(s.color)) { icon.graphics.beginFill(s.color);
+                    }
+                if (s.casing_width || !isNaN(s.casing_color)) {
+                    icon.graphics.lineStyle(s.casing_width ? s.casing_width : 1,
+                                            s.casing_color ? s.casing_color : 0,
+                                            s.casing_opacity ? s.casing_opacity : 1);
+                }
+            }
+
+            // return width
+            return styleList.pointStyles[sublayer].icon_width;
+        }
+
+        private function addHitSprite(w:uint):void {
+            hitzone = new Sprite();
+            hitzone.graphics.lineStyle(4, 0x000000, 1, false, "normal", CapsStyle.ROUND, JointStyle.ROUND);
+            hitzone.graphics.beginFill(0);
+            hitzone.graphics.drawRect(0,0,w,w);
+            hitzone.visible = false;
+            setListenSprite();
+        }
+
+        private function loadedIcon(event:Event):void {
+            var icon:Sprite=new Sprite();
+            var sublayer:Number=event.target.loader.info['sublayer'];
+            addToLayer(icon,STROKESPRITE,sublayer);
+            icon.addChild(Bitmap(event.target.content));
+            addHitSprite(icon.width);
+            loaded=true;
+            updatePosition();
+        }
+
+        private function updatePosition():void {
+            if (!loaded) { return; }
+
+            for (var i:uint=0; i<sprites.length; i++) {
+                var d:DisplayObject=sprites[i];
+                d.x=0; d.y=0; d.rotation=0;
+
+                var m:Matrix=new Matrix();
+                m.translate(-d.width/2,-d.height/2);
+                m.rotate(rotation);
+                m.translate(paint.map.lon2coord(Marker(entity).lon),paint.map.latp2coord(Marker(entity).latp));
+                d.transform.matrix=m;
+            }
+        }
+    }
+}
index 51ea006..7b4a8dc 100644 (file)
@@ -95,7 +95,9 @@ package net.systemeD.halcyon {
                public function getObjectsByBbox(left:Number, right:Number, top:Number, bottom:Number):Object {
                        // ** FIXME: this is just copied-and-pasted from Connection.as, which really isn't very
                        // good practice. Is there a more elegant way of doing it?
-                       var o:Object = { poisInside: [], poisOutside: [], waysInside: [], waysOutside: [] };
+                       var o:Object = { poisInside: [], poisOutside: [], waysInside: [], waysOutside: [],
+                              markersInside: [], markersOutside: [] };
+                              
                        for each (var way:Way in ways) {
                                if (way.within(left,right,top,bottom)) { o.waysInside.push(way); }
                                                                  else { o.waysOutside.push(way); }
@@ -104,6 +106,10 @@ package net.systemeD.halcyon {
                                if (poi.within(left,right,top,bottom)) { o.poisInside.push(poi); }
                                                                  else { o.poisOutside.push(poi); }
                        }
+            for each (var marker:Marker in markers) {
+                if (marker.within(left,right,top,bottom)) { o.markersInside.push(marker); }
+                                                     else { o.markersOutside.push(marker); }
+            }
                        return o;
                }
 
index 6c486fd..0ea67e6 100644 (file)
@@ -43,7 +43,7 @@ package net.systemeD.potlatch2.utils {
               var marker:Marker = layer.createMarker({"name":feature.properties.description,"bug_id":feature.id}, lat, lon);
               //layer.registerPOI(node);
             }
-            layer.paint.updateEntityUIs(layer.getObjectsByBbox(map.edge_l,map.edge_r,map.edge_t,map.edge_b), false, false);
+            layer.paint.updateEntityUIs(layer.getObjectsByBbox(map.edge_l,map.edge_r,map.edge_t,map.edge_b), true, false);
             //var json:Array =
         }