This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 7972946 Repaint the map after a zoom or pan gesture. It allows the viewer to fetch
new data for the new region and resolution.
7972946 is described below
commit 7972946251696ccaa6aecec84c5ae6a76bc4788c
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Apr 7 23:38:50 2020 +0200
Repaint the map after a zoom or pan gesture. It allows the viewer to fetch new data for
the new region and resolution.
---
.../java/org/apache/sis/gui/map/MapCanvas.java | 90 +++++++++++++++++-----
.../java/org/apache/sis/internal/map/Canvas.java | 2 +
.../org/apache/sis/internal/map/PlanarCanvas.java | 5 ++
3 files changed, 77 insertions(+), 20 deletions(-)
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
index d2da931..289bf42 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
@@ -20,6 +20,7 @@ import java.util.Locale;
import java.nio.IntBuffer;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
+import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.RenderedImage;
@@ -32,11 +33,13 @@ import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Affine;
+import javafx.scene.transform.Transform;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.Cursor;
import javafx.event.EventType;
import javafx.beans.Observable;
+import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.util.Callback;
import org.opengis.geometry.Envelope;
@@ -150,7 +153,7 @@ public abstract class MapCanvas extends PlanarCanvas {
* Whether a rendering task is in progress. Used for avoiding to send too many {@link
#repaint()} requests;
* we will wait for current repaint event to finish before to send another one.
*
- * @see #executeRendering(Task)
+ * @see #executeRendering(Affine, Task)
*/
private boolean isRendering;
@@ -166,11 +169,12 @@ public abstract class MapCanvas extends PlanarCanvas {
private boolean invalidObjectiveToDisplay;
/**
- * The zoom, translation or rotation to apply on the content. This is the identity transform
except during
- * the short time between a gesture (zoom, pan, <i>etc.</i>) and the completion
of new {@link #repaint()}.
- * This is used for giving immediate feedback to the user while waiting for the new image
to be ready.
+ * Whether a zoom, pan or rotation has been added to {@link #view} since last time the
image has been painted.
+ * If {@code true}, then the last element in {@link Pane#getTransforms()} list contains
transformation applied
+ * by JavaFX while we are waiting for the {@link #repaint()} to complete. After the repaint
event completed,
+ * the new image replaces the JavaFX transformation.
*/
- private final Affine transform;
+ private boolean isViewTransformed;
/**
* Previous cursor position during a pan event. This is used for computing the translation
to apply
@@ -187,7 +191,6 @@ public abstract class MapCanvas extends PlanarCanvas {
*/
public MapCanvas(final Locale locale) {
super(locale);
- transform = new Affine();
view = new Pane() {
@Override protected void layoutChildren() {
super.layoutChildren();
@@ -198,9 +201,8 @@ public abstract class MapCanvas extends PlanarCanvas {
};
image = new ImageView();
image.setPreserveRatio(true);
- image.getTransforms().add(transform);
- image.setOnScroll(this::onScroll); // Depends on cursor location (must be
on image).
- view.setOnMousePressed(this::onDrag); // We want it to be insensitive to image
transform.
+ view.setOnScroll(this::onScroll);
+ view.setOnMousePressed(this::onDrag);
view.setOnMouseDragged(this::onDrag);
view.setOnMouseReleased(this::onDrag);
view.getChildren().add(image);
@@ -240,14 +242,15 @@ public abstract class MapCanvas extends PlanarCanvas {
final EventType<? extends MouseEvent> type = event.getEventType();
if (type == MouseEvent.MOUSE_PRESSED) {
view.setCursor(Cursor.CLOSED_HAND);
+ lastXPan = x;
+ lastYPan = y;
} else {
if (type != MouseEvent.MOUSE_DRAGGED) {
- view.setCursor(Cursor.CROSSHAIR);
+ view.setCursor(isRendering ? Cursor.WAIT : Cursor.CROSSHAIR);
}
- transform.prependTranslation(x - lastXPan, y - lastYPan);
+ getTransform(true).appendTranslation(x - lastXPan, y - lastYPan);
+ requestRepaint();
}
- lastXPan = x;
- lastYPan = y;
}
/**
@@ -265,7 +268,34 @@ public abstract class MapCanvas extends PlanarCanvas {
if (delta < 0) {
zoom = 1/zoom;
}
- transform.appendScale(zoom, zoom, event.getX(), event.getY());
+ getTransform(true).appendScale(zoom, zoom, event.getX(), event.getY());
+ requestRepaint();
+ }
+
+ /**
+ * The zoom, translation or rotation to apply on the content. This is the identity transform
except during
+ * the short time between a gesture (zoom, pan, <i>etc.</i>) and the completion
of new {@link #repaint()}.
+ * This is used for giving immediate feedback to the user while waiting for the new image
to be ready.
+ *
+ * @param create whether to create the transform if not present.
+ * @return the transform, or {@code null} if none and {@code create} is {@code false}.
+ */
+ private Affine getTransform(final boolean create) {
+ /*
+ * This list may have more than one element if user continues zooming
+ * or panning while a repaint event is working in background thread.
+ */
+ final ObservableList<Transform> transforms = view.getTransforms();
+ final Affine at;
+ if (isViewTransformed) {
+ at = (Affine) transforms.get(transforms.size() - 1);
+ } else if (create) {
+ at = new Affine();
+ isViewTransformed = transforms.add(at);
+ } else {
+ at = null;
+ }
+ return at;
}
/**
@@ -316,11 +346,17 @@ public abstract class MapCanvas extends PlanarCanvas {
/**
* Executes a rendering task in a background thread. This method applies configurations
* specific to the rendering process before to delegate to the overrideable method.
+ *
+ * @param transform The JavaFX transform at the time the repaint event is trigged.
This transform
+ * will be removed from {@link #view} transform list after the image
is rendered.
+ * May be {@code null} if no zoom or pan have been applied since last
rendering.
*/
- private void executeRendering(final Task<?> task) {
+ private void executeRendering(final Affine transform, final Task<?> task) {
task.runningProperty().addListener((p,o,n) -> {
- view.setCursor(n ? Cursor.WAIT : Cursor.CROSSHAIR);
- isRendering = n;
+ final boolean b = n; // Unboxing
+ isRendering = b;
+ view.setCursor(b ? Cursor.WAIT : Cursor.CROSSHAIR);
+ if (!b) view.getTransforms().remove(transform);
});
execute(task);
}
@@ -462,12 +498,26 @@ public abstract class MapCanvas extends PlanarCanvas {
tr = MathTransforms.identity(BIDIMENSIONAL);
}
setObjectiveToDisplay(tr);
+ view.getTransforms().clear();
}
} catch (RenderException ex) {
errorOccurred(ex);
return;
}
/*
+ * If a temporary zoom, rotation or translation has been applied using JavaFX transform
API,
+ * replaced that temporary transform by a "permanent" adjustment of the `objectiveToDisplay`
+ * transform. It allows SIS to get new data for the new visible area and resolution.
+ */
+ final Affine transform = getTransform(false);
+ if (transform != null) {
+ isViewTransformed = false;
+ transformDisplayCoordinates(new AffineTransform(
+ transform.getMxx(), transform.getMyx(),
+ transform.getMxy(), transform.getMyy(),
+ transform.getTx(), transform.getTy()));
+ }
+ /*
* Invoke `createRenderer()` only after we finished above configuration, because
that method may take
* a snapshot of current canvas state in preparation for use in background threads.
*/
@@ -494,7 +544,7 @@ public abstract class MapCanvas extends PlanarCanvas {
doubleBuffer = null;
bufferWrapper = null;
bufferConfiguration = null;
- executeRendering(new Task<WritableImage>() {
+ executeRendering(transform, new Task<WritableImage>() {
/**
* The Java2D image where to do the rendering. This image will be created
in a background thread
* and assigned to the {@link MapCanvas#buffer} field in JavaFX thread if
rendering succeed.
@@ -641,7 +691,7 @@ public abstract class MapCanvas extends PlanarCanvas {
return null;
}
}
- executeRendering(new Updater());
+ executeRendering(transform, new Updater());
}
}
@@ -655,7 +705,7 @@ public abstract class MapCanvas extends PlanarCanvas {
bufferWrapper = null;
doubleBuffer = null;
bufferConfiguration = null;
- transform.setToIdentity();
+ view.getTransforms().clear();
}
/**
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java
index 56126de..a234e20 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Canvas.java
@@ -135,6 +135,8 @@ import org.apache.sis.coverage.grid.GridExtent;
*
* <h2>Multi-threading</h2>
* {@code Canvas} is not thread-safe. Synchronization, if desired, must be done by the caller.
+ * Another common strategy is to interact with {@code Canvas} from a single thread,
+ * for example the Swing or JavaFX event queue.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/PlanarCanvas.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/PlanarCanvas.java
index c8851c2..f4ed455 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/PlanarCanvas.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/PlanarCanvas.java
@@ -42,6 +42,11 @@ import static org.opengis.referencing.IdentifiedObject.NAME_KEY;
* A canvas for two-dimensional display device using a Cartesian coordinate system.
* Data are reduced to a two-dimensional slice before to be displayed.
*
+ * <h2>Multi-threading</h2>
+ * {@code PlanarCanvas} is not thread-safe. Synchronization, if desired, must be done by
the caller.
+ * Another common strategy is to interact with {@code PlanarCanvas} from a single thread,
+ * for example the Swing or JavaFX event queue.
+ *
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
|