import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule;
import com.android.ide.eclipse.adt.editors.layout.gscripts.Point;
import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas.ScaleTransform;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
/** The cached pixel height of the default current font. */
private int mFontHeight = 0;
- /** The offset of the canvas in X. */
- private final int mX;
- /** The offset of the canvas in Y. */
- private final int mY;
+ /** The scaling of the canvas in X. */
+ private final ScaleTransform mHScale;
+ /** The scaling of the canvas in Y. */
+ private final ScaleTransform mVScale;
- public GCWrapper(int offsetX, int offsetY) {
- mX = offsetX;
- mY = offsetY;
+ public GCWrapper(ScaleTransform hScale, ScaleTransform vScale) {
+ mHScale = hScale;
+ mVScale = vScale;
mGc = null;
}
public void drawLine(int x1, int y1, int x2, int y2) {
checkGC();
- getGc().drawLine(x1 + mX, y1 + mY, x2 + mX, y2 + mY);
+ x1 = mHScale.translate(x1);
+ y1 = mVScale.translate(y1);
+ x2 = mHScale.translate(x2);
+ y2 = mVScale.translate(y2);
+ getGc().drawLine(x1, y1, x2, y2);
}
public void drawLine(Point p1, Point p2) {
public void drawRect(int x1, int y1, int x2, int y2) {
checkGC();
- getGc().drawRectangle(x1 + mX, y1 + mY, x2-x1, y2-y1);
+ int x = mHScale.translate(x1);
+ int y = mVScale.translate(y1);
+ int w = mHScale.scale(x2 - x1);
+ int h = mVScale.scale(y2 - y1);
+ getGc().drawRectangle(x, y, w, h);
}
public void drawRect(Point p1, Point p2) {
public void drawRect(Rect r) {
checkGC();
- getGc().drawRectangle(r.x + mX, r.y + mY, r.w, r.h);
+ int x = mHScale.translate(r.x);
+ int y = mVScale.translate(r.y);
+ int w = mHScale.scale(r.w);
+ int h = mVScale.scale(r.h);
+ getGc().drawRectangle(x, y, w, h);
}
public void fillRect(int x1, int y1, int x2, int y2) {
checkGC();
- getGc().fillRectangle(x1 + mX, y1 + mY, x2-x1, y2-y1);
+ int x = mHScale.translate(x1);
+ int y = mVScale.translate(y1);
+ int w = mHScale.scale(x2 - x1);
+ int h = mVScale.scale(y2 - y1);
+ getGc().fillRectangle(x, y, w, h);
}
public void fillRect(Point p1, Point p2) {
public void fillRect(Rect r) {
checkGC();
- getGc().fillRectangle(r.x + mX, r.y + mY, r.w, r.h);
+ int x = mHScale.translate(r.x);
+ int y = mVScale.translate(r.y);
+ int w = mHScale.scale(r.w);
+ int h = mVScale.scale(r.h);
+ getGc().fillRectangle(x, y, w, h);
}
public void drawString(String string, int x, int y) {
checkGC();
- getGc().drawString(string, x + mX, y + mY, true /*isTransparent*/);
+ x = mHScale.translate(x);
+ y = mVScale.translate(y);
+ getGc().drawString(string, x, y, true /*isTransparent*/);
}
public void drawString(String string, Point topLeft) {
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.ScrollBar;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
/** GC wrapper given to the IViewRule methods. The GC itself is only defined in the
* context of {@link #onPaint(PaintEvent)}; otherwise it is null. */
- private GCWrapper mGCWrapper;
+ private final GCWrapper mGCWrapper;
/** Default font used on the canvas. Do not dispose, it's a system font. */
private Font mFont;
/** Current hover view info. Null when no mouse hover. */
private CanvasViewInfo mHoverViewInfo;
- /** Current mouse hover border rectangle. Null when there's no mouse hover. */
+ /** Current mouse hover border rectangle. Null when there's no mouse hover.
+ * The rectangle coordinates do not take account of the translation, which must
+ * be applied to the rectangle when drawing.
+ */
private Rectangle mHoverRect;
/** Hover border color. Must be disposed, it's NOT a system color. */
/** Factory that can create {@link INode} proxies. */
private final NodeFactory mNodeFactory = new NodeFactory();
+ /** Vertical scaling & scrollbar information. */
+ private ScaleInfo mVScale;
+
+ /** Horizontal scaling & scrollbar information. */
+ private ScaleInfo mHScale;
public LayoutCanvas(RulesEngine rulesEngine, Composite parent, int style) {
super(parent, style | SWT.DOUBLE_BUFFERED | SWT.V_SCROLL | SWT.H_SCROLL);
mRulesEngine = rulesEngine;
- mGCWrapper = new GCWrapper(IMAGE_MARGIN, IMAGE_MARGIN);
+ mHScale = new ScaleInfo(getHorizontalBar());
+ mVScale = new ScaleInfo(getVerticalBar());
+
+ mGCWrapper = new GCWrapper(mHScale, mVScale);
Display d = getDisplay();
mSelectionFgColor = d.getSystemColor(SWT.COLOR_RED);
}
});
+ addControlListener(new ControlAdapter() {
+ @Override
+ public void controlResized(ControlEvent e) {
+ super.controlResized(e);
+ mHScale.setClientSize(getClientArea().width);
+ mVScale.setClientSize(getClientArea().height);
+ }
+ });
+
addMouseMoveListener(new MouseMoveListener() {
public void mouseMove(MouseEvent e) {
onMouseMove(e);
// remove the current alternate selection views
mAltSelection = null;
+
+ mHScale.setSize(mImage.getImageData().width, getClientArea().width);
+ mVScale.setSize(mImage.getImageData().height, getClientArea().height);
}
redraw();
redraw();
}
+ public double getScale() {
+ return mHScale.getScale();
+ }
+
+ public void setScale(double scale) {
+ mHScale.setScale(scale);
+ mVScale.setScale(scale);
+ redraw();
+ }
+
/**
* Called by the {@link GraphicalEditorPart} when the Copy action is requested.
*
//---
+ public interface ScaleTransform {
+ /**
+ * Computes the transformation from a X/Y canvas image coordinate
+ * to client pixel coordinate.
+ * <p/>
+ * This takes into account the {@link LayoutCanvas#IMAGE_MARGIN},
+ * the current scaling and the current translation.
+ *
+ * @param canvasX A canvas image coordinate (X or Y).
+ * @return The transformed coordinate in client pixel space.
+ */
+ public int translate(int canvasX);
+
+ /**
+ * Computes the transformation from a canvas image size (width or height) to
+ * client pixel coordinates.
+ *
+ * @param canwasW A canvas image size (W or H).
+ * @return The transformed coordinate in client pixel space.
+ */
+ public int scale(int canwasW);
+
+ /**
+ * Computes the transformation from a X/Y client pixel coordinate
+ * to canvas image coordinate.
+ * <p/>
+ * This takes into account the {@link LayoutCanvas#IMAGE_MARGIN},
+ * the current scaling and the current translation.
+ * <p/>
+ * This is the inverse of {@link #translate(int)}.
+ *
+ * @param screenX A client pixel space coordinate (X or Y).
+ * @return The transformed coordinate in canvas image space.
+ */
+ public int inverseTranslate(int screenX);
+ }
+
+ private class ScaleInfo implements ScaleTransform {
+ /** Canvas image size (original, before zoom), in pixels */
+ private int mImgSize;
+
+ /** Client size, in pixels */
+ private int mClientSize;
+
+ /** Left-top offset in client pixel coordinates */
+ private int mTranslate;
+
+ /** Scaling factor, > 0 */
+ private double mScale;
+
+ /** Scrollbar widget */
+ ScrollBar mScrollbar;
+
+ public ScaleInfo(ScrollBar scrollbar) {
+ mScrollbar = scrollbar;
+ mScale = 1.0;
+ mTranslate = 0;
+
+ mScrollbar.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ // User requested scrolling. Changes translation and redraw canvas.
+ mTranslate = mScrollbar.getSelection();
+ redraw();
+ }
+ });
+ }
+
+ /**
+ * Sets the new scaling factor. Recomputes scrollbars.
+ * @param scale Scaling factor, > 0.
+ */
+ public void setScale(double scale) {
+ if (mScale != scale) {
+ mScale = scale;
+ resizeScrollbar();
+ }
+ }
+
+ /** Returns current scaling factor. */
+ public double getScale() {
+ return mScale;
+ }
+
+ /** Returns Canvas image size (original, before zoom), in pixels. */
+ public int getImgSize() {
+ return mImgSize;
+ }
+
+ /** Returns the scaled image size in pixels. */
+ public int getScalledImgSize() {
+ return (int) (mImgSize * mScale);
+ }
+
+ /** Changes the size of the canvas image and the client size. Recomputes scrollbars. */
+ public void setSize(int imgSize, int clientSize) {
+ mImgSize = imgSize;
+ setClientSize(clientSize);
+ }
+
+ /** Changes the size of the client size. Recomputes scrollbars. */
+ public void setClientSize(int clientSize) {
+ mClientSize = clientSize;
+ resizeScrollbar();
+ }
+
+ private void resizeScrollbar() {
+ // scaled image size
+ int sx = (int) (mImgSize * mScale);
+
+ // actual client area is always reduced by the margins
+ int cx = mClientSize - 2 * IMAGE_MARGIN;
+
+ if (sx < cx) {
+ mScrollbar.setEnabled(false);
+ } else {
+ mScrollbar.setEnabled(true);
+
+ // max scroll value is the scaled image size
+ // thumb value is the actual viewable area out of the scaled img size
+ mScrollbar.setMaximum(sx);
+ mScrollbar.setThumb(cx);
+ }
+ }
+
+ public int translate(int canvasX) {
+ return IMAGE_MARGIN - mTranslate + (int)(mScale * canvasX);
+ }
+
+ public int scale(int canwasW) {
+ return (int)(mScale * canwasW);
+ }
+
+ public int inverseTranslate(int screenX) {
+ return (int) ((screenX - IMAGE_MARGIN + mTranslate) / mScale);
+ }
+ }
/**
* Creates or updates the node proxy for this canvas view info.
}
/**
+ * Sets the non-text antialias flag for the given GC.
+ * <p/>
+ * Antialias may not work on all platforms and may fail with an exception.
+ *
+ * @param gc the GC to change
+ * @param alias One of {@link SWT#DEFAULT}, {@link SWT#ON}, {@link SWT#OFF}.
+ * @return The previous aliasing mode if the operation worked,
+ * or -2 if it failed with an exception.
+ *
+ * @see GC#setAntialias(int)
+ */
+ private int gc_setAntialias(GC gc, int alias) {
+ try {
+ int old = gc.getAntialias();
+ gc.setAntialias(alias);
+ return old;
+ } catch (SWTException e) {
+ return -2;
+ }
+ }
+
+ /**
* Paints the canvas in response to paint events.
*/
private void onPaint(PaintEvent e) {
gc_setAlpha(gc, 128); // half-transparent
}
- gc.drawImage(mImage, IMAGE_MARGIN, IMAGE_MARGIN);
+ ScaleInfo hi = mHScale;
+ ScaleInfo vi = mVScale;
+
+ // we only anti-alias when reducing the image size.
+ int oldAlias = -2;
+ if (hi.getScale() < 1.0) {
+ oldAlias = gc_setAntialias(gc, SWT.ON);
+ }
+
+ gc.drawImage(mImage,
+ 0, // srcX
+ 0, // srcY
+ hi.getImgSize(), // srcWidth
+ vi.getImgSize(), // srcHeight
+ hi.translate(0), // destX
+ vi.translate(0), // destY
+ hi.getScalledImgSize(), // destWidth
+ vi.getScalledImgSize() // destHeight
+ );
+
+ if (oldAlias != -2) {
+ gc_setAntialias(gc, oldAlias);
+ }
if (!mIsResultValid) {
gc_setAlpha(gc, 255); // opaque
if (mHoverRect != null) {
gc.setForeground(mHoverFgColor);
gc.setLineStyle(SWT.LINE_DOT);
- gc.drawRectangle(mHoverRect);
+
+ int x = mHScale.translate(mHoverRect.x);
+ int y = mVScale.translate(mHoverRect.y);
+ int w = mHScale.scale(mHoverRect.width);
+ int h = mVScale.scale(mHoverRect.height);
+
+ gc.drawRectangle(x, y, w, h);
}
int n = mSelections.size();
private void drawOutline(GC gc, CanvasViewInfo info) {
Rectangle r = info.getAbsRect();
- gc.drawRectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height);
+
+ int x = mHScale.translate(r.x);
+ int y = mVScale.translate(r.y);
+ int w = mHScale.scale(r.width);
+ int h = mVScale.scale(r.height);
+
+ gc.drawRectangle(x, y, w, h);
for (CanvasViewInfo vi : info.getChildren()) {
drawOutline(gc, vi);
private void onMouseMove(MouseEvent e) {
if (mLastValidResult != null) {
CanvasViewInfo root = mLastValidViewInfoRoot;
- CanvasViewInfo vi = findViewInfoAt(e.x - IMAGE_MARGIN, e.y - IMAGE_MARGIN);
+
+ int x = mHScale.inverseTranslate(e.x);
+ int y = mVScale.inverseTranslate(e.y);
+
+ CanvasViewInfo vi = findViewInfoAt(x, y);
// We don't hover on the root since it's not a widget per see and it is always there.
if (vi == root) {
mHoverRect = null;
} else {
Rectangle r = vi.getSelectionRect();
- mHoverRect = new Rectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN,
- r.width, r.height);
+ mHoverRect = new Rectangle(r.x, r.y, r.width, r.height);
}
if (needsUpdate) {
boolean isShift = (e.stateMask & SWT.SHIFT) != 0;
boolean isAlt = (e.stateMask & SWT.ALT) != 0;
- int x = e.x - IMAGE_MARGIN;
- int y = e.y - IMAGE_MARGIN;
+ int x = mHScale.inverseTranslate(e.x);
+ int y = mVScale.inverseTranslate(e.y);
+
CanvasViewInfo vi = findViewInfoAt(x, y);
if (isShift && !isAlt) {