From bc195f999a0a3f4571ff45342327223b4b480750 Mon Sep 17 00:00:00 2001 From: Tuukka Lehtonen Date: Mon, 1 Oct 2018 20:20:30 +0300 Subject: [PATCH] HiDPI scaling for diagram ruler and model activity tracker gitlab #137 Change-Id: Icc3cf5cd828aa4580699dde01635b1ea669b0a0c --- .../DiagramModelActivityTracker.java | 45 +++- .../g2d/participant/RulerPainter.java | 17 ++ .../scenegraph/g2d/nodes/RulerNode.java | 54 ++-- .../simantics/scenegraph/utils/DPIUtil.java | 254 ++++++++++++++++++ 4 files changed, 337 insertions(+), 33 deletions(-) create mode 100644 bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/DPIUtil.java diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/DiagramModelActivityTracker.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/DiagramModelActivityTracker.java index 630032bae..e27877438 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/DiagramModelActivityTracker.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/DiagramModelActivityTracker.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * Copyright (c) 2007, 2018 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -35,10 +35,12 @@ import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant; import org.simantics.g2d.canvas.impl.HintReflection.HintListener; import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup; import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit; +import org.simantics.g2d.participant.RulerPainter; import org.simantics.g2d.utils.Alignment; import org.simantics.layer0.Layer0; import org.simantics.modeling.ModelingResources; import org.simantics.scenegraph.g2d.G2DParentNode; +import org.simantics.scenegraph.utils.DPIUtil; import org.simantics.structural.stubs.StructuralResource2; import org.simantics.utils.datastructures.hints.IHintContext; import org.simantics.utils.datastructures.hints.IHintContext.Key; @@ -67,6 +69,9 @@ public class DiagramModelActivityTracker extends AbstractCanvasParticipant { Resource input; IsInActiveModelListener listener; TextNode bannerNode; + Rectangle2D bounds; + boolean rulerVisible = false; + double rulerSize = 0; public DiagramModelActivityTracker(Resource input) { if (input == null) @@ -76,18 +81,34 @@ public class DiagramModelActivityTracker extends AbstractCanvasParticipant { @HintListener(Class=Hints.class, Field="KEY_CONTROL_BOUNDS") public void controlBoundsChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { + bounds = (Rectangle2D) newValue; + resetBannerNode(bounds, rulerVisible, rulerSize); + } + + @HintListener(Class=RulerPainter.class, Field="KEY_RULER_ENABLED") + public void rulerToggled(IHintObservable sender, Key key, Object oldValue, Object newValue) { + rulerVisible = Boolean.TRUE.equals(newValue); + resetBannerNode(bounds, rulerVisible, rulerSize); + } + + @HintListener(Class=RulerPainter.class, Field="KEY_RULER_SIZE") + public void rulerSizeChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { + rulerSize = newValue != null ? (Double) newValue : 0.0; + resetBannerNode(bounds, rulerVisible, rulerSize); + } + + private void resetBannerNode(Rectangle2D bounds, boolean rulerVisible, double rulerSize) { if (bannerNode == null) return; - if (newValue != null) { - Rectangle2D bounds = (Rectangle2D) newValue; + if (bounds != null) { AffineTransform at = new AffineTransform(); at.translate(5,5); -// at.translate(bounds.getWidth() / 2, bounds.getHeight() / 2); -// at.scale(2, 2); -// at.rotate(-Math.PI/6); -// at.translate(bounds.getWidth() - 150, bounds.getHeight() - 30); -// at.scale(1, 1); + if (rulerVisible) { + double s = DPIUtil.upscale(rulerSize); + at.translate(s, s); + } bannerNode.setTransform(at); + bannerNode.setColor(Color.BLACK); } else { // Disable rendering bannerNode.setColor(null); @@ -112,6 +133,10 @@ public class DiagramModelActivityTracker extends AbstractCanvasParticipant { public void addedToContext(ICanvasContext ctx) { super.addedToContext(ctx); + rulerVisible = Boolean.TRUE.equals(getHint(RulerPainter.KEY_RULER_ENABLED)); + Double rs = getHint(RulerPainter.KEY_RULER_SIZE); + rulerSize = rs != null ? rs : 0; + listener = new IsInActiveModelListener(ctx); Simantics.getSession().async(new IsActiveDiagram(input), listener); @@ -139,12 +164,14 @@ public class DiagramModelActivityTracker extends AbstractCanvasParticipant { bannerNode.setVerticalAlignment((byte) Alignment.LEADING.ordinal()); //bannerNode.setText("Model is not active."); bannerNode.setText(""); - bannerNode.setFont(Font.decode("Arial 16")); + int fontSize = DPIUtil.upscale(16); + bannerNode.setFont(new Font("Tahoma", Font.PLAIN, fontSize)); //bannerNode.setBackgroundColor(new Color(192, 192, 192, 128)); bannerNode.setPadding(10, 10); bannerNode.setBorderColor(Color.GRAY); bannerNode.setBorderWidth(0); bannerNode.setEditable(false); + bannerNode.setColor(Color.BLACK); } @SGCleanup diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/RulerPainter.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/RulerPainter.java index bffe0c9df..dc9f5f5a7 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/RulerPainter.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/participant/RulerPainter.java @@ -56,6 +56,11 @@ public class RulerPainter extends AbstractCanvasParticipant { public static final Key KEY_RULER_ENABLED = new KeyOf(Boolean.class, "RULER_ENABLED"); + /** + * Size of ruler in normalized (non-zoomed or 100% zoom) coordinates, not screen pixel coordinates. + */ + public static final Key KEY_RULER_SIZE = new KeyOf(Double.class, "RULER_SIZE"); + /** Background color */ public static final Key KEY_RULER_BACKGROUND_COLOR = new KeyOf(Color.class, "RULER_BACKGROUND_COLOR"); @@ -78,7 +83,12 @@ public class RulerPainter extends AbstractCanvasParticipant { @Override public void addedToContext(ICanvasContext ctx) { super.addedToContext(ctx); + + if (!hasHint(KEY_RULER_SIZE)) + setHint(KEY_RULER_SIZE, RULER_WIDTH); + getHintStack().addKeyHintListener(getThread(), KEY_RULER_ENABLED, hintListener); + getHintStack().addKeyHintListener(getThread(), KEY_RULER_SIZE, hintListener); getHintStack().addKeyHintListener(getThread(), KEY_RULER_BACKGROUND_COLOR, hintListener); getHintStack().addKeyHintListener(getThread(), KEY_RULER_TEXT_COLOR, hintListener); getHintStack().addKeyHintListener(getThread(), GridPainter.KEY_GRID_SIZE, hintListener); @@ -88,6 +98,7 @@ public class RulerPainter extends AbstractCanvasParticipant { @Override public void removedFromContext(ICanvasContext ctx) { getHintStack().removeKeyHintListener(getThread(), KEY_RULER_ENABLED, hintListener); + getHintStack().removeKeyHintListener(getThread(), KEY_RULER_SIZE, hintListener); getHintStack().removeKeyHintListener(getThread(), KEY_RULER_BACKGROUND_COLOR, hintListener); getHintStack().removeKeyHintListener(getThread(), KEY_RULER_TEXT_COLOR, hintListener); getHintStack().removeKeyHintListener(getThread(), GridPainter.KEY_GRID_SIZE, hintListener); @@ -119,6 +130,7 @@ public class RulerPainter extends AbstractCanvasParticipant { protected void updateNode() { node.setEnabled(isPaintingEnabled()); node.setGridSize(getGridSize()); + node.setRulerSize(getRulerSize()); } private double getGridSize() { @@ -139,6 +151,11 @@ public class RulerPainter extends AbstractCanvasParticipant { return !Boolean.FALSE.equals(b); } + private double getRulerSize() { + Double d = getHint(KEY_RULER_SIZE); + return d != null ? d : RULER_WIDTH; + } + public Color getRulerTextColor() { Color c = getHint(KEY_RULER_TEXT_COLOR); diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/RulerNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/RulerNode.java index 7e962976e..785c246b4 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/RulerNode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/RulerNode.java @@ -22,6 +22,7 @@ import java.awt.geom.Rectangle2D; import java.util.Locale; import org.simantics.scenegraph.g2d.G2DNode; +import org.simantics.scenegraph.utils.DPIUtil; import org.simantics.scenegraph.utils.GridUtils; public class RulerNode extends G2DNode { @@ -30,17 +31,14 @@ public class RulerNode extends G2DNode { */ private static final long serialVersionUID = 2490944880914577411L; - /** - * FIXME: this is a hack for the map UI that has to be solved some other way. - */ - private static final boolean MAP_Y_SCALING = false; - private static final Color GRAY = new Color(100, 100, 100); protected Boolean enabled = true; protected double gridSize = 1.0; + protected double rulerSize = 20; + @SyncField("enabled") public void setEnabled(Boolean enabled) { this.enabled = enabled; @@ -53,11 +51,16 @@ public class RulerNode extends G2DNode { this.gridSize = gridSize; } + @SyncField("rulerSize") + public void setRulerSize(double rulerSize) { + this.rulerSize = rulerSize; + } + @Override public void render(Graphics2D g) { if (!enabled) return; - + AffineTransform tr = g.getTransform(); double scaleX = Math.abs(tr.getScaleX()); double scaleY = Math.abs(tr.getScaleY()); @@ -69,7 +72,7 @@ public class RulerNode extends G2DNode { double offsetY = tr.getTranslateY(); g.setTransform(new AffineTransform()); - Font rulerFont = new Font("Tahoma", Font.PLAIN, 9); + Font rulerFont = new Font("Tahoma", Font.PLAIN, DPIUtil.upscale(9)); //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setStroke(new BasicStroke(1)); @@ -80,10 +83,15 @@ public class RulerNode extends G2DNode { g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f)); - Rectangle2D vertical = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMinY()+20); + int rulerPixelSize = (int) DPIUtil.upscale(rulerSize); + int u7 = DPIUtil.upscale(7); + int u4 = DPIUtil.upscale(4); + int u2 = DPIUtil.upscale(2); + + Rectangle2D vertical = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMinY()+rulerPixelSize); g.fill(vertical); - Rectangle2D horizontal = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY()+20, bounds.getMinX()+20, bounds.getMaxY()); + Rectangle2D horizontal = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY()+rulerPixelSize, bounds.getMinX()+rulerPixelSize, bounds.getMaxY()); g.fill(horizontal); // stepX and stepY should be something between 50 and 100 @@ -103,16 +111,16 @@ public class RulerNode extends G2DNode { g.setColor(GRAY); g.setFont(rulerFont); + FontMetrics fm = g.getFontMetrics(); double previousText = -100; // Vertical ruler for(double x = offsetX%stepX-stepX; x < bounds.getMaxX(); x+=stepX) { - if(x > 20) { + if(x > rulerPixelSize) { double val = (x-offsetX)/scaleX / getTransform().getScaleX(); double modifiedValue = modifyHorizontalValue(val); String str = formatValue(modifiedValue, getMaxDigits()); - FontMetrics fm = g.getFontMetrics(); Rectangle2D r = fm.getStringBounds(str, g); if((x-r.getWidth()/2) > previousText) { g.setColor(Color.BLACK); @@ -121,31 +129,29 @@ public class RulerNode extends G2DNode { } g.setColor(GRAY); - g.drawLine((int)x, (int)bounds.getMinY()+12, (int)x, (int)bounds.getMinY()+19); + g.drawLine((int)x, (int)bounds.getMinY()+rulerPixelSize-1-u7, (int)x, (int)bounds.getMinY()+rulerPixelSize-1); } if(stepX/5 > 2) { for(double x2 = x+stepX/5; x2 < x+stepX; x2+=stepX/5) { - if(x2 > 20) { - g.drawLine((int)x2, (int)bounds.getMinY()+15, (int)x2, (int)bounds.getMinY()+19); + if(x2 > rulerPixelSize) { + g.drawLine((int)x2, (int)bounds.getMinY()+rulerPixelSize-1-u4, (int)x2, (int)bounds.getMinY()+rulerPixelSize-1); } } for(double x2 = x+stepX/10; x2 < x+stepX; x2+=stepX/5) { - if(x2 > 20) { - g.drawLine((int)x2, (int)bounds.getMinY()+17, (int)x2, (int)bounds.getMinY()+19); + if(x2 > rulerPixelSize) { + g.drawLine((int)x2, (int)bounds.getMinY()+rulerPixelSize-1-u2, (int)x2, (int)bounds.getMinY()+rulerPixelSize-1); } } - } } // Horizontal ruler previousText = -100; for(double y = offsetY%stepY-stepY; y < bounds.getMaxY(); y+=stepY) { - if(y > 20) { + if(y > rulerPixelSize) { double val = (y-offsetY)/scaleY / getTransform().getScaleY(); double modifiedValue = modifyVerticalValue(val); String str = formatValue(modifiedValue, getMaxDigits()); - FontMetrics fm = g.getFontMetrics(); Rectangle2D r = fm.getStringBounds(str, g); if(y-1+r.getHeight()/2 > previousText) { g.setColor(Color.BLACK); @@ -157,17 +163,17 @@ public class RulerNode extends G2DNode { previousText = y-1+r.getHeight(); } g.setColor(GRAY); - g.drawLine((int)bounds.getMinX()+12, (int)y, (int)bounds.getMinX()+19, (int)y); + g.drawLine((int)bounds.getMinX()+rulerPixelSize-1-u7, (int)y, (int)bounds.getMinX()+rulerPixelSize-1, (int)y); } if(stepY/5 > 2) { for(double y2 = y+stepY/5; y2 < y+stepY; y2+=stepY/5) { - if(y2 > 20) { - g.drawLine((int)bounds.getMinX()+15, (int)y2, (int)bounds.getMinX()+19, (int)y2); + if(y2 > rulerPixelSize) { + g.drawLine((int)bounds.getMinX()+rulerPixelSize-1-u4, (int)y2, (int)bounds.getMinX()+rulerPixelSize-1, (int)y2); } } for(double y2 = y+stepY/10; y2 < y+stepY; y2+=stepY/5) { - if(y2 > 20) { - g.drawLine((int)bounds.getMinX()+17, (int)y2, (int)bounds.getMinX()+19, (int)y2); + if(y2 > rulerPixelSize) { + g.drawLine((int)bounds.getMinX()+rulerPixelSize-1-u2, (int)y2, (int)bounds.getMinX()+rulerPixelSize-1, (int)y2); } } } diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/DPIUtil.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/DPIUtil.java new file mode 100644 index 000000000..0a138b305 --- /dev/null +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/DPIUtil.java @@ -0,0 +1,254 @@ +/******************************************************************************* + * Copyright (c) 2018 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.scenegraph.utils; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +/** + * @author Tuukka Lehtonen + * @since 1.36.0 + */ +public class DPIUtil { + + private static boolean initialized = false; + private static boolean hasZoom; + + private static float upscaleFactorF; + private static double upscaleFactorD; + private static float downscaleFactorF; + private static double downscaleFactorD; + + private static void initialize() { + if (initialized) + return; + + double dpi = Toolkit.getDefaultToolkit().getScreenResolution(); + double baseDpi = 96; + int zoom = (int) Math.round(100.0 * dpi / baseDpi); + hasZoom = zoom != 100; + + upscaleFactorD = dpi / baseDpi; + upscaleFactorF = (float) upscaleFactorD; + downscaleFactorD = baseDpi / dpi; + downscaleFactorF = (float) downscaleFactorD; + +// System.out.format("DPIUtil:%n\tswt zoom = %d%n\tdownscale factor = %f%n\tupscale factor = %f%n", +// zoom, +// downscaleFactorD, +// upscaleFactorD +// ); + + initialized = true; + } + + // Internals + + private static Rectangle scale(float s, Rectangle r, Rectangle target) { + if (s == 1.0f) { + if (r == target) + return r; + if (target == null) { + return new Rectangle(r.x, r.y, r.width, r.height); + } else { + target.x = r.x; + target.y = r.y; + target.width = r.width; + target.height = r.height; + return target; + } + } + if (target == null) { + return new Rectangle( + Math.round(r.x*s), + Math.round(r.y*s), + Math.round(r.width*s), + Math.round(r.height*s)); + } else { + target.x = Math.round(r.x*s); + target.y = Math.round(r.y*s); + target.width = Math.round(r.width*s); + target.height = Math.round(r.height*s); + return target; + } + } + + private static Rectangle2D scale(double s, Rectangle2D r, Rectangle2D target) { + if (s == 1.0) { + if (r == target) + return r; + if (target == null) + return (Rectangle2D) r.clone(); + target.setFrame(r); + return target; + } + if (target == null) + target = (Rectangle2D) r.clone(); + target.setFrame(r.getX()*s, r.getY()*s, r.getWidth()*s, r.getHeight()*s); + return target; + } + + private static double downscale0(double x) { + return hasZoom ? x * downscaleFactorD : x; + } + + private static int downscaleToInteger0(double x) { + return (int)(hasZoom ? Math.round((double) x * downscaleFactorD) : x); + } + + private static int downscale0(int x) { + return hasZoom ? (int) Math.round((double) x * downscaleFactorD) : x; + } + + private static double upscale0(double x) { + return hasZoom ? x * upscaleFactorD : x; + } + + private static int upscaleToInteger0(double x) { + return (int)(hasZoom ? Math.round((double) x * upscaleFactorD) : x); + } + + private static int upscale0(int x) { + return hasZoom ? (int) Math.round((double) x * upscaleFactorD) : x; + } + + // Downscaling + + public static double downscale(double x) { + initialize(); + return downscale0(x); + } + + public static int downscale(int x) { + initialize(); + return downscale0(x); + } + + public static Point2D downscale(double x, double y) { + initialize(); + if (!hasZoom) + return new Point2D.Double(x, y); + double s = downscaleFactorD; + return new Point2D.Double(x * s, y * s); + } + + public static Point downscale(int x, int y) { + initialize(); + return new Point(downscale0(x), downscale0(y)); + } + + public static Point2D downscale(Point2D p) { + return downscale(p.getX(), p.getY()); + } + + public static Point downscaleToInteger(Point2D p) { + initialize(); + return new Point(downscaleToInteger0(p.getX()), downscaleToInteger0(p.getY())); + } + + public static Rectangle2D downscale(Rectangle2D r, Rectangle2D target) { + initialize(); + return scale(downscaleFactorD, r, target); + } + + public static Rectangle2D downscale(Rectangle2D r) { + return downscale(r, null); + } + + public static Rectangle downscale(Rectangle r, Rectangle target) { + initialize(); + return scale(downscaleFactorF, r, target); + } + + public static Rectangle downscale(Rectangle r) { + return downscale(r, null); + } + + public static Rectangle downscaleToInteger(Rectangle2D r) { + initialize(); + return new Rectangle( + downscaleToInteger0(r.getMinX()), + downscaleToInteger0(r.getMinY()), + downscaleToInteger0(r.getWidth()), + downscaleToInteger0(r.getHeight())); + } + + // Upscaling + + public static double upscale(double x) { + initialize(); + return upscale0(x); + } + + public static int upscale(int x) { + initialize(); + return upscale0(x); + } + + public static Point2D upscale(double x, double y) { + initialize(); + if (!hasZoom) + return new Point2D.Double(x, y); + double s = upscaleFactorD; + return new Point2D.Double(x * s, y * s); + } + + public static Point upscale(int x, int y) { + initialize(); + return new Point(upscale0(x), upscale0(y)); + } + + public static Point2D upscale(Point2D p) { + initialize(); + return (hasZoom && p != null) ? upscale(p.getX(), p.getY()) : p; + } + + public static Point upscaleToInteger(Point2D p) { + initialize(); + return new Point(upscaleToInteger0(p.getX()), upscaleToInteger0(p.getY())); + } + + public static Point upscale(Point p) { + initialize(); + return (hasZoom && p != null) ? upscale(p.x, p.y) : p; + } + + public static Rectangle2D upscale(Rectangle2D r, Rectangle2D target) { + initialize(); + return scale(upscaleFactorD, r, target); + } + + public static Rectangle upscale(Rectangle r, Rectangle target) { + initialize(); + return scale(upscaleFactorF, r, target); + } + + public static Rectangle2D upscale(Rectangle2D r) { + return upscale(r, null); + } + + public static Rectangle upscale(Rectangle r) { + return upscale(r, null); + } + + public static Rectangle upscaleToInteger(Rectangle2D r) { + return new Rectangle( + upscaleToInteger0(r.getMinX()), + upscaleToInteger0(r.getMinY()), + upscaleToInteger0(r.getWidth()), + upscaleToInteger0(r.getHeight())); + } + +} -- 2.43.2