use EventDispatcher for Input- and Map-Events

This commit is contained in:
Hannes Janetzek 2014-02-14 14:50:19 +01:00
parent 940a753b41
commit e15163bb08
13 changed files with 153 additions and 168 deletions

View File

@ -17,12 +17,13 @@ package org.oscim.android;
*/ */
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import org.oscim.android.canvas.AndroidBitmap; import org.oscim.android.canvas.AndroidBitmap;
import org.oscim.core.MapPosition; import org.oscim.core.MapPosition;
import org.oscim.core.MercatorProjection; import org.oscim.core.MercatorProjection;
import org.oscim.event.Event;
import org.oscim.layers.Layer; import org.oscim.layers.Layer;
import org.oscim.map.Map;
import org.oscim.map.Map.UpdateListener; import org.oscim.map.Map.UpdateListener;
import org.oscim.renderer.BitmapRenderer; import org.oscim.renderer.BitmapRenderer;
@ -71,7 +72,7 @@ public class MapScaleBar extends Layer implements UpdateListener {
private boolean mRedrawNeeded; private boolean mRedrawNeeded;
private double mPrevLatitude = -1; private double mPrevLatitude = -1;
private final double mPrevScale = -1; private final double mPrevScale = -1;
private final Map<TextField, String> mTextFields; private final HashMap<TextField, String> mTextFields;
private final Bitmap mBitmap; private final Bitmap mBitmap;
// passed to BitmapRenderer - need to sync on this object. // passed to BitmapRenderer - need to sync on this object.
@ -102,7 +103,9 @@ public class MapScaleBar extends Layer implements UpdateListener {
} }
@Override @Override
public void onMapUpdate(MapPosition mapPosition, boolean changed, boolean clear) { public void onMapEvent(Event e, MapPosition mapPosition) {
if (e == Map.UPDATE_EVENT)
return;
double latitude = MercatorProjection.toLatitude(mapPosition.y); double latitude = MercatorProjection.toLatitude(mapPosition.y);

View File

@ -152,8 +152,7 @@ public class MapView extends RelativeLayout {
if (mGestureDetector.onTouchEvent(motionEvent)) if (mGestureDetector.onTouchEvent(motionEvent))
return true; return true;
mMap.handleMotionEvent(mMotionEvent.wrap(motionEvent)); mMap.input.fire(null, mMotionEvent.wrap(motionEvent));
return true; return true;
} }

View File

@ -0,0 +1,9 @@
package org.oscim.event;
/**
* The Class Event to be sub-classed by event-producers. Should be used
* to distinguish the type of event when more than one is produced.
*/
public class Event {
}

View File

@ -5,70 +5,32 @@ import org.oscim.utils.pool.LList;
/** /**
* The Class EventDispatcher. * The Class EventDispatcher.
* *
* Events MUST be dispached from main-loop! To add events from other * Events MUST be dispatched from main-loop! To add events from other
* threads use: * threads use:
* Map.post(new Runnable(){ public void run(tell(event,data);)};); * Map.post(new Runnable(){ public void run(tell(event,data);)};);
* *
* @param <T> the event source type * @param <T> the event source type
* @param <E> the event 'data' type * @param <E> the event 'data' type
*/ */
public class EventDispatcher<T, E> { public abstract class EventDispatcher<E extends EventListener, T> {
/**
* Generic event-listener interface.
*
* @param <E> the type of event 'data'
*/
public interface Listener<E> {
/**
* The onEvent handler to be implemented by Listeners.
*
* @param source the event source
* @param event the event object/type
* @param data some object involved in this event
*/
public void onEvent(Object source, Event event, E data);
}
/**
* The Class Event to be subclassed by event-producers. Should be used
* to distinguish the type of event when more than one is produced.
*/
public static class Event {
// nothing here
}
/** The event source object. */
protected final T mSource;
/** The list of listeners. */ /** The list of listeners. */
protected LList<Listener<E>> mListeners; protected LList<E> mListeners;
/**
* Instantiates a new event dispatcher.
*
* @param source the event-source
*/
public EventDispatcher(T source) {
mSource = source;
}
/** /**
* Bind listener for event notifications. * Bind listener for event notifications.
*/ */
public void bind(Listener<E> listener) { public void bind(E listener) {
if (LList.find(mListeners, listener) != null) { if (LList.find(mListeners, listener) != null) {
// throw some poo?
return; return;
} }
mListeners = LList.push(mListeners, new LList<Listener<E>>(listener)); mListeners = LList.push(mListeners, new LList<E>(listener));
} }
/** /**
* Unbind listener. * Remove listener.
*/ */
public void unbind(Listener<E> listener) { public void unbind(E listener) {
mListeners = LList.remove(mListeners, listener); mListeners = LList.remove(mListeners, listener);
} }
@ -78,9 +40,11 @@ public class EventDispatcher<T, E> {
* @param event the event * @param event the event
* @param data the data * @param data the data
*/ */
public void tell(Event event, E data) { public abstract void tell(E listener, Event event, T data);
for (LList<Listener<E>> l = mListeners; l != null; l = l.next) {
l.data.onEvent(mSource, event, data); public void fire(Event event, T data) {
for (LList<E> l = mListeners; l != null; l = l.next) {
tell(l.data, event, data);
} }
} }
} }

View File

@ -0,0 +1,8 @@
package org.oscim.event;
/**
* Event-listener interface.
*/
public interface EventListener {
}

View File

@ -17,6 +17,7 @@
package org.oscim.layers; package org.oscim.layers;
import org.oscim.core.MapPosition; import org.oscim.core.MapPosition;
import org.oscim.event.Event;
import org.oscim.map.Map; import org.oscim.map.Map;
import org.oscim.map.Map.UpdateListener; import org.oscim.map.Map.UpdateListener;
import org.oscim.renderer.GLViewport; import org.oscim.renderer.GLViewport;
@ -61,7 +62,7 @@ public class CustomRenderLayer extends Layer implements UpdateListener {
private int someConccurentVariable; private int someConccurentVariable;
@Override @Override
public void onMapUpdate(MapPosition mapPosition, boolean changed, boolean clear) { public void onMapEvent(Event e, MapPosition mapPosition) {
synchronized (mRenderer) { synchronized (mRenderer) {
someConccurentVariable++; someConccurentVariable++;

View File

@ -18,10 +18,12 @@ package org.oscim.layers;
import org.oscim.backend.CanvasAdapter; import org.oscim.backend.CanvasAdapter;
import org.oscim.core.Tile; import org.oscim.core.Tile;
import org.oscim.event.Event;
import org.oscim.event.Gesture; import org.oscim.event.Gesture;
import org.oscim.event.GestureListener; import org.oscim.event.GestureListener;
import org.oscim.event.MotionEvent; import org.oscim.event.MotionEvent;
import org.oscim.map.Map; import org.oscim.map.Map;
import org.oscim.map.Map.InputListener;
import org.oscim.map.ViewController; import org.oscim.map.ViewController;
import org.oscim.utils.FastMath; import org.oscim.utils.FastMath;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -30,7 +32,7 @@ import org.slf4j.LoggerFactory;
/** /**
* Changes Viewport by handling move, fling, scale, rotation and tilt gestures. * Changes Viewport by handling move, fling, scale, rotation and tilt gestures.
*/ */
public class MapEventLayer extends Layer implements Map.InputListener, GestureListener { public class MapEventLayer extends Layer implements InputListener, GestureListener {
static final Logger log = LoggerFactory.getLogger(MapEventLayer.class); static final Logger log = LoggerFactory.getLogger(MapEventLayer.class);
@ -84,8 +86,8 @@ public class MapEventLayer extends Layer implements Map.InputListener, GestureLi
} }
@Override @Override
public void onMotionEvent(MotionEvent event) { public void onInputEvent(Event e, MotionEvent motionEvent) {
onTouchEvent(event); onTouchEvent(motionEvent);
} }
public void enableRotation(boolean enable) { public void enableRotation(boolean enable) {
@ -496,4 +498,5 @@ public class MapEventLayer extends Layer implements Map.InputListener, GestureLi
// return sum; // return sum;
//} //}
} }
} }

View File

@ -24,6 +24,7 @@ import org.oscim.backend.canvas.Bitmap;
import org.oscim.core.MapElement; import org.oscim.core.MapElement;
import org.oscim.core.MapPosition; import org.oscim.core.MapPosition;
import org.oscim.core.Tile; import org.oscim.core.Tile;
import org.oscim.event.Event;
import org.oscim.map.Map; import org.oscim.map.Map;
import org.oscim.renderer.elements.BitmapLayer; import org.oscim.renderer.elements.BitmapLayer;
import org.oscim.renderer.elements.ElementLayers; import org.oscim.renderer.elements.ElementLayers;
@ -71,8 +72,11 @@ public class BitmapTileLayer extends TileLayer {
} }
@Override @Override
public void onMapUpdate(MapPosition pos, boolean changed, boolean clear) { public void onMapEvent(Event event, MapPosition pos) {
super.onMapUpdate(pos, changed, clear); super.onMapEvent(event, pos);
if (event != Map.POSITION_EVENT)
return;
FadeStep[] fade = mTileSource.getFadeSteps(); FadeStep[] fade = mTileSource.getFadeSteps();

View File

@ -17,6 +17,7 @@
package org.oscim.layers.tile; package org.oscim.layers.tile;
import org.oscim.core.MapPosition; import org.oscim.core.MapPosition;
import org.oscim.event.Event;
import org.oscim.layers.Layer; import org.oscim.layers.Layer;
import org.oscim.map.Map; import org.oscim.map.Map;
import org.oscim.map.Map.UpdateListener; import org.oscim.map.Map.UpdateListener;
@ -69,19 +70,22 @@ public abstract class TileLayer extends Layer implements UpdateListener {
} }
@Override @Override
public void onMapUpdate(MapPosition mapPosition, boolean changed, boolean clear) { public void onMapEvent(Event event, MapPosition mapPosition) {
if (clear) {
if (event == Map.CLEAR_EVENT) {
// sync with TileRenderer // sync with TileRenderer
synchronized (mRenderer) { synchronized (mRenderer) {
tileRenderer().clearTiles(); tileRenderer().clearTiles();
mTileManager.init(); mTileManager.init();
} }
changed = true; if (mTileManager.update(mapPosition))
} notifyLoaders();
if (changed && mTileManager.update(mapPosition)) } else if (event == Map.POSITION_EVENT) {
notifyLoaders(); if (mTileManager.update(mapPosition))
notifyLoaders();
}
} }
@Override @Override

View File

@ -17,9 +17,7 @@
package org.oscim.layers.tile.vector.labeling; package org.oscim.layers.tile.vector.labeling;
import org.oscim.core.MapPosition; import org.oscim.core.MapPosition;
import org.oscim.event.EventDispatcher.Event; import org.oscim.event.Event;
import org.oscim.event.EventDispatcher.Listener;
import org.oscim.event.MotionEvent;
import org.oscim.layers.Layer; import org.oscim.layers.Layer;
import org.oscim.layers.tile.vector.VectorTileLayer; import org.oscim.layers.tile.vector.VectorTileLayer;
import org.oscim.map.Map; import org.oscim.map.Map;
@ -29,8 +27,7 @@ import org.oscim.utils.async.SimpleWorker;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class LabelLayer extends Layer implements Map.InputListener, Map.UpdateListener, public class LabelLayer extends Layer implements Map.UpdateListener, TileManager.Listener {
Listener<MapTile> {
static final Logger log = LoggerFactory.getLogger(LabelLayer.class); static final Logger log = LoggerFactory.getLogger(LabelLayer.class);
@ -94,33 +91,34 @@ public class LabelLayer extends Layer implements Map.InputListener, Map.UpdateLi
} }
@Override @Override
public void onMapUpdate(MapPosition mapPosition, boolean changed, boolean clear) { public void onMapEvent(Event event, MapPosition mapPosition) {
if (clear)
if (event == Map.CLEAR_EVENT)
mWorker.cancel(true); mWorker.cancel(true);
if (changed || clear) if (event == Map.POSITION_EVENT)
mWorker.submit(MAX_RELABEL_DELAY); mWorker.submit(MAX_RELABEL_DELAY);
} }
@Override // @Override
public void onMotionEvent(MotionEvent e) { // public void onMotionEvent(MotionEvent e) {
// int action = e.getAction() & MotionEvent.ACTION_MASK; // // int action = e.getAction() & MotionEvent.ACTION_MASK;
// if (action == MotionEvent.ACTION_POINTER_DOWN) { // // if (action == MotionEvent.ACTION_POINTER_DOWN) {
// multi++; // // multi++;
// mTextRenderer.hold(true); // // mTextRenderer.hold(true);
// } else if (action == MotionEvent.ACTION_POINTER_UP) { // // } else if (action == MotionEvent.ACTION_POINTER_UP) {
// multi--; // // multi--;
// if (multi == 0) // // if (multi == 0)
// mTextRenderer.hold(false); // // mTextRenderer.hold(false);
// } else if (action == MotionEvent.ACTION_CANCEL) { // // } else if (action == MotionEvent.ACTION_CANCEL) {
// multi = 0; // // multi = 0;
// log.debug("cancel " + multi); // // log.debug("cancel " + multi);
// mTextRenderer.hold(false); // // mTextRenderer.hold(false);
// } // // }
} // }
@Override @Override
public void onEvent(Object source, Event e, MapTile tile) { public void onTileManagerEvent(Event e, MapTile tile) {
if (e == TileManager.TILE_LOADED) { if (e == TileManager.TILE_LOADED) {
if (tile.isVisible) if (tile.isVisible)
mWorker.submit(MAX_RELABEL_DELAY / 4); mWorker.submit(MAX_RELABEL_DELAY / 4);

View File

@ -57,9 +57,10 @@ public final class Layers extends AbstractList<Layer> {
throw new IllegalArgumentException("layer added twice"); throw new IllegalArgumentException("layer added twice");
if (layer instanceof UpdateListener) if (layer instanceof UpdateListener)
mMap.bind((UpdateListener) layer); mMap.events.bind((UpdateListener) layer);
if (layer instanceof InputListener) if (layer instanceof InputListener)
mMap.bind((InputListener) layer); mMap.input.bind((InputListener) layer);
mLayerList.add(index, layer); mLayerList.add(index, layer);
mDirtyLayers = true; mDirtyLayers = true;
@ -72,9 +73,9 @@ public final class Layers extends AbstractList<Layer> {
Layer remove = mLayerList.remove(index); Layer remove = mLayerList.remove(index);
if (remove instanceof UpdateListener) if (remove instanceof UpdateListener)
mMap.unbind((UpdateListener) remove); mMap.events.unbind((UpdateListener) remove);
if (remove instanceof InputListener) if (remove instanceof InputListener)
mMap.unbind((InputListener) remove); mMap.input.unbind((InputListener) remove);
return remove; return remove;
} }
@ -89,9 +90,9 @@ public final class Layers extends AbstractList<Layer> {
// unbind replaced layer // unbind replaced layer
if (remove instanceof UpdateListener) if (remove instanceof UpdateListener)
mMap.unbind((UpdateListener) remove); mMap.events.unbind((UpdateListener) remove);
if (remove instanceof InputListener) if (remove instanceof InputListener)
mMap.unbind((InputListener) remove); mMap.input.unbind((InputListener) remove);
return remove; return remove;
} }

View File

@ -16,10 +16,10 @@
*/ */
package org.oscim.map; package org.oscim.map;
import java.util.LinkedHashSet;
import java.util.Set;
import org.oscim.core.MapPosition; import org.oscim.core.MapPosition;
import org.oscim.event.Event;
import org.oscim.event.EventDispatcher;
import org.oscim.event.EventListener;
import org.oscim.event.Gesture; import org.oscim.event.Gesture;
import org.oscim.event.GestureDetector; import org.oscim.event.GestureDetector;
import org.oscim.event.MotionEvent; import org.oscim.event.MotionEvent;
@ -45,8 +45,8 @@ public abstract class Map {
* register when the layer is added to the map and unregistered when * register when the layer is added to the map and unregistered when
* the layer is removed. * the layer is removed.
*/ */
public interface UpdateListener { public interface UpdateListener extends EventListener {
void onMapUpdate(MapPosition mapPosition, boolean positionChanged, boolean clear); void onMapEvent(Event e, MapPosition mapPosition);
} }
/** /**
@ -55,41 +55,67 @@ public abstract class Map {
* register when the layer is added to the map and unregistered when * register when the layer is added to the map and unregistered when
* the layer is removed. * the layer is removed.
*/ */
public interface InputListener {
void onMotionEvent(MotionEvent e); public interface InputListener extends EventListener {
void onInputEvent(Event e, MotionEvent motionEvent);
} }
/***/
public static Event UPDATE_EVENT = new Event();
/**
* Map state has changed in a way that all layers should clear their state
* e.g. the theme or the TilesSource has changed
*/
public static Event CLEAR_EVENT = new Event();
public static Event POSITION_EVENT = new Event();
public final EventDispatcher<InputListener, MotionEvent> input;
public final EventDispatcher<UpdateListener, MapPosition> events;
private final Layers mLayers; private final Layers mLayers;
private final ViewController mViewport; private final ViewController mViewport;
private final Animator mAnimator; private final Animator mAnimator;
private final MapPosition mMapPosition; private final MapPosition mMapPosition;
private final AsyncExecutor mAsyncExecutor; private final AsyncExecutor mAsyncExecutor;
protected final MapEventLayer mEventLayer; protected final MapEventLayer mEventLayer;
protected GestureDetector mGestureDetector; protected GestureDetector mGestureDetector;
/**
* Listener interface for map update notifications.
* Layers implementing this interface they will be automatically
* register when the layer is added to the map and unregistered when
* the layer is removed.
*/
private VectorTileLayer mBaseLayer; private VectorTileLayer mBaseLayer;
private Set<InputListener> mInputListenerSet = new LinkedHashSet<InputListener>();
private InputListener[] mInputListeners;
private Set<UpdateListener> mUpdateListenerSet = new LinkedHashSet<UpdateListener>();
private UpdateListener[] mUpdateListeners;
protected boolean mClearMap = true; protected boolean mClearMap = true;
public Map() { public Map() {
mViewport = new ViewController(); mViewport = new ViewController();
mAnimator = new Animator(this); mAnimator = new Animator(this);
mLayers = new Layers(this); mLayers = new Layers(this);
input = new EventDispatcher<InputListener, MotionEvent>() {
@Override
public void tell(InputListener l, Event e, MotionEvent d) {
l.onInputEvent(e, d);
}
};
events = new EventDispatcher<UpdateListener, MapPosition>() {
@Override
public void tell(UpdateListener l, Event e, MapPosition d) {
l.onMapEvent(e, d);
}
};
mAsyncExecutor = new AsyncExecutor(2); mAsyncExecutor = new AsyncExecutor(2);
mMapPosition = new MapPosition(); mMapPosition = new MapPosition();
mEventLayer = new MapEventLayer(this); mEventLayer = new MapEventLayer(this);
mLayers.add(0, mEventLayer); mLayers.add(0, mEventLayer);
} }
public MapEventLayer getEventLayer() { public MapEventLayer getEventLayer() {
@ -248,22 +274,6 @@ public abstract class Map {
return mAnimator; return mAnimator;
} }
/**
* Register UpdateListener
*/
public void bind(UpdateListener l) {
if (mUpdateListenerSet.add(l))
mUpdateListeners = null;
}
/**
* Unregister UpdateListener
*/
public void unbind(UpdateListener l) {
if (mUpdateListenerSet.remove(l))
mUpdateListeners = null;
}
/** /**
* This function is run on main-loop before rendering a frame. * This function is run on main-loop before rendering a frame.
* Caution: Do not call directly! * Caution: Do not call directly!
@ -274,44 +284,16 @@ public abstract class Map {
changed |= mViewport.getMapPosition(pos); changed |= mViewport.getMapPosition(pos);
if (mUpdateListeners == null) { if (mClearMap)
mUpdateListeners = new UpdateListener[mUpdateListenerSet.size()]; events.fire(CLEAR_EVENT, pos);
mUpdateListenerSet.toArray(mUpdateListeners); else if (changed)
} events.fire(POSITION_EVENT, pos);
else
for (UpdateListener l : mUpdateListeners) events.fire(UPDATE_EVENT, pos);
l.onMapUpdate(pos, changed, mClearMap);
mClearMap = false; mClearMap = false;
} }
/**
* Register InputListener
*/
public void bind(InputListener listener) {
if (mInputListenerSet.add(listener))
mInputListeners = null;
}
/**
* Unregister InputListener
*/
public void unbind(InputListener listener) {
if (mInputListenerSet.remove(listener))
mInputListeners = null;
}
public void handleMotionEvent(MotionEvent e) {
if (mInputListeners == null) {
mInputListeners = new InputListener[mInputListenerSet.size()];
mInputListenerSet.toArray(mInputListeners);
}
for (InputListener l : mInputListeners)
l.onMotionEvent(e);
}
public boolean handleGesture(Gesture g, MotionEvent e) { public boolean handleGesture(Gesture g, MotionEvent e) {
return mLayers.handleGesture(g, e); return mLayers.handleGesture(g, e);
} }

View File

@ -28,8 +28,9 @@ import java.util.Arrays;
import org.oscim.core.MapPosition; import org.oscim.core.MapPosition;
import org.oscim.core.Tile; import org.oscim.core.Tile;
import org.oscim.event.Event;
import org.oscim.event.EventDispatcher; import org.oscim.event.EventDispatcher;
import org.oscim.event.EventDispatcher.Event; import org.oscim.event.EventListener;
import org.oscim.map.Map; import org.oscim.map.Map;
import org.oscim.map.Viewport; import org.oscim.map.Viewport;
import org.oscim.renderer.BufferObject; import org.oscim.renderer.BufferObject;
@ -46,7 +47,17 @@ public class TileManager {
public final static Event TILE_LOADED = new Event(); public final static Event TILE_LOADED = new Event();
public final static Event TILE_REMOVED = new Event(); public final static Event TILE_REMOVED = new Event();
public final EventDispatcher<TileManager, MapTile> events; public final EventDispatcher<Listener, MapTile> events = new EventDispatcher<Listener, MapTile>() {
@Override
public void tell(Listener l, Event event, MapTile tile) {
l.onTileManagerEvent(event, tile);
}
};
public interface Listener extends EventListener {
void onTileManagerEvent(Event event, MapTile tile);
};
private int mCacheLimit; private int mCacheLimit;
private int mCacheReduce; private int mCacheReduce;
@ -139,8 +150,6 @@ public class TileManager {
mTilesSize = 0; mTilesSize = 0;
mTilesForUpload = 0; mTilesForUpload = 0;
mUpdateSerial = 0; mUpdateSerial = 0;
events = new EventDispatcher<TileManager, MapTile>(this);
} }
private int[] mZoomTable; private int[] mZoomTable;
@ -392,7 +401,7 @@ public class TileManager {
private void removeFromCache(MapTile t) { private void removeFromCache(MapTile t) {
if (t.state == NEW_DATA || t.state == READY) if (t.state == NEW_DATA || t.state == READY)
events.tell(TILE_REMOVED, t); events.fire(TILE_REMOVED, t);
synchronized (t) { synchronized (t) {
/* still belongs to TileLoader thread, defer clearing to /* still belongs to TileLoader thread, defer clearing to
@ -518,7 +527,7 @@ public class TileManager {
} }
tile.state = NEW_DATA; tile.state = NEW_DATA;
events.tell(TILE_LOADED, tile); events.fire(TILE_LOADED, tile);
mTilesForUpload += 1; mTilesForUpload += 1;