Merge "Highlight regions on mouse hover."
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java
index b96cebb..eb179c0 100644
--- a/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java
@@ -23,12 +23,14 @@
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
+import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
+import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
@@ -45,6 +47,7 @@
import java.awt.geom.Line2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -191,6 +194,8 @@
@Override
public void mouseMoved(MouseEvent event) {
checkLockedRegion(event.getX(), event.getY());
+ updateHoverRegion(event.getX(), event.getY());
+ repaint();
}
});
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
@@ -215,6 +220,200 @@
});
}
+ private List<Rectangle> highlightRegions = new ArrayList<Rectangle>();
+
+ private enum UpdateRegion {
+ LEFT_PATCH,
+ TOP_PATCH,
+ RIGHT_PADDING,
+ BOTTOM_PADDING,
+ };
+
+ private static class UpdateRegionInfo {
+ public final UpdateRegion region;
+ public final Pair<Integer> segment;
+
+ private UpdateRegionInfo(UpdateRegion region, Pair<Integer> segment) {
+ this.region = region;
+ this.segment = segment;
+ }
+ }
+
+ private UpdateRegionInfo findVerticalPatch(int x, int y) {
+ // Given the mouse x location, we need to determine if we need to map this edit to
+ // the patch info at the left, or the padding info at the right. We make this decision
+ // based on whichever is closer, so if the mouse x is in the left half of the image,
+ // we are editing the left patch, else the right padding.
+ if (x < image.getWidth() / 2) {
+ return getVerticalPatchLeft(y);
+ } else {
+ return getVerticalPaddingRight(y);
+ }
+ }
+
+ private UpdateRegionInfo findHorizontalPatch(int x, int y) {
+ if (y < image.getHeight() / 2) {
+ return getHorizontalPatchTop(x);
+ } else {
+ return getHorizontalPaddingBottom(x);
+ }
+ }
+
+ private UpdateRegionInfo getVerticalPatchLeft(int y) {
+ return getContainingPatch(patchInfo.verticalPatchMarkers, y, UpdateRegion.LEFT_PATCH);
+ }
+
+ private UpdateRegionInfo getHorizontalPatchTop(int x) {
+ return getContainingPatch(patchInfo.horizontalPatchMarkers, x, UpdateRegion.TOP_PATCH);
+ }
+
+ private UpdateRegionInfo getContainingPatch(List<Pair<Integer>> patches, int a,
+ UpdateRegion region) {
+ for (Pair<Integer> p: patches) {
+ if (p.first <= a && p.second > a) {
+ return new UpdateRegionInfo(region, p);
+ }
+
+ if (p.first > a) {
+ break;
+ }
+ }
+
+ return new UpdateRegionInfo(region, null);
+ }
+
+ private UpdateRegionInfo getVerticalPaddingRight(int y) {
+ int top = patchInfo.verticalPadding.first + 1; // add 1 to offset for the 1 additional 9patch info pixel at top
+ int bottom = image.getHeight() - patchInfo.verticalPadding.second - 1; // similarly, remove 1 pixel from the bottom
+ return getContainingPadding(top, bottom, y, UpdateRegion.RIGHT_PADDING);
+ }
+
+ private UpdateRegionInfo getHorizontalPaddingBottom(int x) {
+ int left = patchInfo.horizontalPadding.first + 1; // add 1 to offset for the 1 additional pixel on the left
+ int right = image.getWidth() - patchInfo.horizontalPadding.second - 1; // similarly, remove 1 pixel from the right
+ return getContainingPadding(left, right, x, UpdateRegion.BOTTOM_PADDING);
+ }
+
+ private UpdateRegionInfo getContainingPadding(int start, int end, int x, UpdateRegion region) {
+ Pair<Integer> p = null;
+ if (x >= start && x <= end) {
+ p = new Pair<Integer>(start, end);
+ }
+
+ return new UpdateRegionInfo(region, p);
+ }
+
+ private void updateHoverRegion(int x, int y) {
+ x = imageXCoordinate(x);
+ y = imageYCoordinate(y);
+
+ UpdateRegionInfo verticalUpdateRegion = findVerticalPatch(x, y);
+ UpdateRegionInfo horizontalUpdateRegion = findHorizontalPatch(x, y);
+
+ // find regions to highlight
+ computeHighlightRegions(verticalUpdateRegion, horizontalUpdateRegion);
+
+ // change cursor if necessary
+ Cursor c = getCursor(x, y, verticalUpdateRegion, horizontalUpdateRegion);
+ setCursor(c);
+ }
+
+ private void computeHighlightRegions(UpdateRegionInfo verticalUpdateRegion,
+ UpdateRegionInfo horizontalUpdateRegion) {
+ highlightRegions.clear();
+ if (verticalUpdateRegion != null && verticalUpdateRegion.segment != null) {
+ Rectangle r = displayCoordinates(new Rectangle(0,
+ verticalUpdateRegion.segment.first,
+ image.getWidth(),
+ verticalUpdateRegion.segment.second - verticalUpdateRegion.segment.first));
+ // highlight the region within the image
+ highlightRegions.add(r);
+
+ // add a 1 pixel line at the top and bottom that extends outside the image
+ highlightRegions.add(new Rectangle(0, r.y, getWidth(), 1));
+ highlightRegions.add(new Rectangle(0, r.y + r.height, getWidth(), 1));
+ }
+ if (horizontalUpdateRegion != null && horizontalUpdateRegion.segment != null) {
+ Rectangle r = displayCoordinates(new Rectangle(horizontalUpdateRegion.segment.first,
+ 0,
+ horizontalUpdateRegion.segment.second - horizontalUpdateRegion.segment.first,
+ image.getHeight()));
+ // highlight the region within the image
+ highlightRegions.add(r);
+
+ // add a 1 pixel line at the top and bottom that extends outside the image
+ highlightRegions.add(new Rectangle(r.x, 0, 1, getHeight()));
+ highlightRegions.add(new Rectangle(r.x + r.width, 0, 1, getHeight()));
+ }
+ }
+
+ private Cursor getCursor(int x, int y, UpdateRegionInfo verticalUpdateRegion,
+ UpdateRegionInfo horizontalUpdateRegion) {
+ Cursor c = null;
+
+ if (verticalUpdateRegion != null && verticalUpdateRegion.segment != null) {
+ Edge e = getClosestEdge(y, verticalUpdateRegion.segment);
+ if (e == Edge.START) {
+ c = Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR);
+ } else if (e == Edge.END) {
+ c = Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR);
+ }
+ }
+ if (c == null && horizontalUpdateRegion != null && horizontalUpdateRegion.segment != null) {
+ Edge e = getClosestEdge(x, horizontalUpdateRegion.segment);
+ if (e == Edge.START) {
+ c = Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR);
+ } else if (e == Edge.END) {
+ c = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
+ }
+ }
+ return c == null ? Cursor.getDefaultCursor() : c;
+ }
+
+ private enum Edge {
+ START,
+ END,
+ NONE,
+ }
+
+ private static final int EDGE_DELTA = 1;
+ private Edge getClosestEdge(int x, Pair<Integer> range) {
+ if (x - range.first <= EDGE_DELTA) {
+ return Edge.START;
+ } else if (range.second - x <= EDGE_DELTA) {
+ return Edge.END;
+ } else {
+ return Edge.NONE;
+ }
+ }
+
+ private int imageYCoordinate(int y) {
+ int top = helpPanel.getHeight() + (getHeight() - size.height) / 2;
+ return (y - top) / zoom;
+ }
+
+ private int imageXCoordinate(int x) {
+ int left = (getWidth() - size.width) / 2;
+ return (x - left) / zoom;
+ }
+
+ private Point getImageOrigin() {
+ int left = (getWidth() - size.width) / 2;
+ int top = helpPanel.getHeight() + (getHeight() - size.height) / 2;
+ return new Point(left, top);
+ }
+
+ private Rectangle displayCoordinates(Rectangle r) {
+ Point imageOrigin = getImageOrigin();
+
+ int x = r.x * zoom + imageOrigin.x;
+ int y = r.y * zoom + imageOrigin.y;
+ int w = r.width * zoom;
+ int h = r.height * zoom;
+
+ return new Rectangle(x, y, w, h);
+ }
+
private void updatePatchInfo() {
patchInfo = new PatchInfo(image);
}
@@ -471,6 +670,14 @@
cursor.drawRect(lastPositionX - zoom / 2, lastPositionY - zoom / 2, zoom, zoom);
cursor.dispose();
}
+
+ g2 = (Graphics2D) g.create();
+ Color c = new Color(0.5f, 0.5f, 0.5f, 0.5f);
+ g2.setColor(c);
+ for (Rectangle r: highlightRegions) {
+ g2.fillRect(r.x, r.y, r.width, r.height);
+ }
+ g2.dispose();
}
private void paintStripes(Graphics2D g, int width, int height) {
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java b/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
index f68740b..8b92655 100644
--- a/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
@@ -42,6 +42,12 @@
/** Areas of image stretchable vertically. */
public final List<Rectangle> verticalPatches;
+ /** Bounds of horizontal patch markers. */
+ public final List<Pair<Integer>> horizontalPatchMarkers;
+
+ /** Bounds of vertical patch markers. */
+ public final List<Pair<Integer>> verticalPatchMarkers;
+
public final boolean verticalStartWithPatch;
public final boolean horizontalStartWithPatch;
@@ -64,9 +70,11 @@
P left = getPatches(column);
verticalStartWithPatch = left.startsWithPatch;
+ verticalPatchMarkers = left.patches;
P top = getPatches(row);
horizontalStartWithPatch = top.startsWithPatch;
+ horizontalPatchMarkers = top.patches;
fixed = getRectangles(left.fixed, top.fixed);
patches = getRectangles(left.patches, top.patches);