]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java
Optimization of district scene graph node rendering
[simantics/district.git] / org.simantics.district.maps / src / org / simantics / maps / eclipse / MapPainter.java
1 /*******************************************************************************
2  * Copyright (c) 2012 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.maps.eclipse;
13
14 import java.awt.Color;
15 import java.awt.geom.AffineTransform;
16 import java.util.concurrent.ScheduledFuture;
17 import java.util.concurrent.TimeUnit;
18
19 import org.simantics.g2d.canvas.Hints;
20 import org.simantics.g2d.canvas.ICanvasContext;
21 import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
22 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
23 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
24 import org.simantics.g2d.participant.MouseUtil;
25 import org.simantics.maps.sg.MapAttributionNode;
26 import org.simantics.maps.sg.MapInfoNode;
27 import org.simantics.maps.sg.MapLocationInfoNode;
28 import org.simantics.maps.sg.MapNode;
29 import org.simantics.maps.sg.MapScaleNode;
30 import org.simantics.maps.sg.commands.MapCommands;
31 import org.simantics.scenegraph.g2d.G2DParentNode;
32 import org.simantics.scenegraph.g2d.events.Event;
33 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
34 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
35 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
36 import org.simantics.scenegraph.g2d.events.command.Commands;
37 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
38 import org.simantics.utils.datastructures.hints.IHintContext.Key;
39 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
40 import org.simantics.utils.datastructures.hints.IHintListener;
41 import org.simantics.utils.datastructures.hints.IHintObservable;
42 import org.simantics.utils.threads.AWTThread;
43 import org.simantics.utils.threads.ThreadUtils;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * MapPainter is an ICanvasContext participant that uses the scene graph
49  * {@link MapNode} to draw tiled maps in the background of the canvas.
50  * 
51  * @author J-P Laine
52  * 
53  * @see MapNode
54  */
55 public class MapPainter extends AbstractCanvasParticipant {
56
57     private static final Logger LOGGER = LoggerFactory.getLogger(MapPainter.class);
58
59     /**
60      * Grid enabled status. Default value is True
61      */
62     public static final Key KEY_MAP_ENABLED  = new KeyOf(Boolean.class);
63
64     public static final Key KEY_MAP_BACKGROUND_COLOR  = new KeyOf(Object.class);
65
66     public static final double ZOOM_IN_LIMIT = 10000000.0;
67
68     public static final double ZOOM_OUT_LIMIT = 10.0;
69
70     IHintListener mapListener = new HintListenerAdapter() {
71         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
72             ICanvasContext cc = getContext();
73             if (cc != null) {
74                 updateNode();
75                 cc.getContentContext().setDirty();
76             }
77         }
78     };
79
80     private MapNode mapNode;
81     private MapLocationInfoNode locationInfoNode;
82     private MapScaleNode scaleNode;
83     private MapInfoNode attributionNode;
84
85     private AffineTransform transform;
86
87     private ScheduledFuture<?> schedule;
88
89     public MapPainter(AffineTransform transform) {
90         this.transform = transform;
91     }
92
93     @Override
94     public void addedToContext(ICanvasContext ctx) {
95         super.addedToContext(ctx);
96         getHintStack().addKeyHintListener(getThread(), KEY_MAP_ENABLED, mapListener);
97         getHintStack().addKeyHintListener(getThread(), KEY_MAP_BACKGROUND_COLOR, mapListener);
98     }
99
100     @Override
101     public void removedFromContext(ICanvasContext ctx) {
102         getHintStack().removeKeyHintListener(getThread(), KEY_MAP_ENABLED, mapListener);
103         getHintStack().removeKeyHintListener(getThread(), KEY_MAP_BACKGROUND_COLOR, mapListener);
104         super.removedFromContext(ctx);
105     }
106
107     @EventHandler(priority = 0)
108     public boolean handleKeyEvent(CommandEvent e) {
109         if (e.command.equals( Commands.MAP_ENABLE )) {
110             setEnabled(true);
111             updateNode();
112             setDirty();
113             return true;
114         } else if (e.command.equals( MapCommands.MAP_BACKGROUND_COLOR_CHANGE )) {
115             ICanvasContext context = (ICanvasContext) e.getContext();
116             Color s = context.getHintStack().getHint(MapCommands.KEY_MAP_BACKGROUND_COLOR);
117             setBackgroundColor(s);
118             setDirty();
119             return true;
120         } else if (e.command.equals( Commands.MAP_DISABLE )) {
121             setEnabled(false);
122             updateNode();
123             setDirty();
124             return true;
125         } else if (e.command.equals( Commands.MAP_TOGGLE )) {
126             setEnabled(!isMapEnabled());
127             updateNode();
128             setDirty();
129             return true;
130         } else if (e.command.equals( Commands.ENABLE_PAINTING )) {
131             enablePainting();
132             updateNode();
133             setDirty();
134             return true;
135         }
136         return false;
137     }
138
139     @EventHandler(priority = 31)
140     public boolean handleEvent(Event e) {
141         if (e instanceof MouseMovedEvent) {
142             // here we should somehow re-render ?
143             if (locationInfoNode != null && locationInfoNode.isEnabled()) {
144                 if (schedule == null || schedule.isDone()) {
145                     LOGGER.debug("current setDirty time" + System.currentTimeMillis());
146                     schedule = ThreadUtils.getNonBlockingWorkExecutor().schedule(() -> {
147                         AWTThread.getThreadAccess().asyncExec(this::setDirty);
148                     }, 100, TimeUnit.MILLISECONDS);
149                 } else {
150                     //LOGGER.debug("ingoring setDirty time" + System.currentTimeMillis());
151                 }
152             }
153         }
154         return false;
155     }
156
157     @SGInit
158     public void initSG(G2DParentNode parent) {
159         // Just under the grid
160         mapNode = parent.addNode("map", MapNode.class);
161         mapNode.setTransform(transform);
162         mapNode.setEnabled(true);
163         mapNode.setZIndex(Integer.MIN_VALUE + 999);
164
165         // On top of pretty much everything
166         attributionNode = parent.addNode("mapAttribution", MapAttributionNode.class);
167         attributionNode.setTransform(transform);
168         attributionNode.setZIndex(Integer.MAX_VALUE - 999);
169         attributionNode.setEnabled(true);
170
171         scaleNode = parent.addNode("mapScale", MapScaleNode.class);
172         scaleNode.setTransform(transform);
173         scaleNode.setZIndex(Integer.MAX_VALUE - 998);
174         scaleNode.setEnabled(true);
175
176         locationInfoNode = parent.addNode("mapLocationInfo", MapLocationInfoNode.class);
177         locationInfoNode.setTransform(transform);
178         locationInfoNode.setZIndex(Integer.MAX_VALUE - 997);
179         locationInfoNode.setEnabled(true);
180         locationInfoNode.setMouseUtil(getContext().getAtMostOneItemOfClass(MouseUtil.class));
181     }
182
183     @SGCleanup
184     public void cleanupSG() {
185         mapNode.remove();
186         attributionNode.remove();
187         scaleNode.remove();
188         locationInfoNode.remove();
189     }
190
191     protected void updateNode() {
192         mapNode.setEnabled(isPaintingEnabled());
193         mapNode.setEnabled(isMapEnabled());
194         mapNode.setBackgroundColor(getBackgroundColor());
195     }
196
197     boolean isPaintingEnabled() {
198         boolean enabled = isMapEnabled();
199         Boolean globalDisable = getHint(Hints.KEY_DISABLE_PAINTING);
200         return enabled && !Boolean.TRUE.equals(globalDisable);
201     }
202
203     public boolean isMapEnabled() {
204         Boolean enabled = getHint(KEY_MAP_ENABLED);
205         return !Boolean.FALSE.equals(enabled);
206     }
207
208     public void setEnabled(boolean enabled) {
209         setHint(KEY_MAP_ENABLED, enabled);
210     }
211
212     private void enablePainting() {
213         setHint(Hints.KEY_DISABLE_PAINTING, false);
214     }
215     
216     private void setBackgroundColor(Color backgroundColor) {
217         setHint(KEY_MAP_BACKGROUND_COLOR, backgroundColor);
218     }
219     
220     private Color getBackgroundColor() {
221         return getHint(KEY_MAP_BACKGROUND_COLOR);
222     }
223 }