modules/graphics/src/main/java/javafx/scene/layout/Region.java

Print this page

        

@@ -75,10 +75,12 @@
 import com.sun.javafx.scene.DirtyBits;
 import com.sun.javafx.scene.input.PickResultChooser;
 import com.sun.javafx.sg.prism.NGNode;
 import com.sun.javafx.sg.prism.NGRegion;
 import com.sun.javafx.tk.Toolkit;
+import javafx.scene.Scene;
+import javafx.stage.Window;
 import sun.util.logging.PlatformLogger;
 import sun.util.logging.PlatformLogger.Level;
 
 /**
  * Region is the base class for all JavaFX Node-based UI Controls, and all layout containers.

@@ -206,19 +208,76 @@
     double adjustWidthByMargin(double width, Insets margin) {
         if (margin == null || margin == Insets.EMPTY) {
             return width;
         }
         boolean isSnapToPixel = isSnapToPixel();
-        return width - snapSpace(margin.getLeft(), isSnapToPixel) - snapSpace(margin.getRight(), isSnapToPixel);
+        return width - snapSpaceX(margin.getLeft(), isSnapToPixel) - snapSpaceX(margin.getRight(), isSnapToPixel);
     }
 
     double adjustHeightByMargin(double height, Insets margin) {
         if (margin == null || margin == Insets.EMPTY) {
             return height;
         }
         boolean isSnapToPixel = isSnapToPixel();
-        return height - snapSpace(margin.getTop(), isSnapToPixel) - snapSpace(margin.getBottom(), isSnapToPixel);
+        return height - snapSpaceY(margin.getTop(), isSnapToPixel) - snapSpaceY(margin.getBottom(), isSnapToPixel);
+    }
+
+    private static final boolean snapVerbose = false;
+    private static double getSnapScaleX(Node n) {
+        final double sx = _getSnapScaleXimpl(n.getScene());
+        if (snapVerbose) System.out.println("static:"+n+" scale is "+sx);
+        return sx;
+    }
+    private static double _getSnapScaleXimpl(Scene scene) {
+        if (scene == null) return 1.0;
+        Window window = scene.getWindow();
+        if (window == null) return 1.0;
+        return window.getRenderScaleX();
+    }
+
+    private static double getSnapScaleY(Node n) {
+        double sy = _getSnapScaleYimpl(n.getScene());
+        if (snapVerbose) System.out.println("static:"+n+" scale is "+sy);
+        return sy;
+    }
+    private static double _getSnapScaleYimpl(Scene scene) {
+        if (scene == null) return 1.0;
+        Window window = scene.getWindow();
+        if (window == null) return 1.0;
+        return window.getRenderScaleY();
+    }
+
+    private double lastScaleX = -1;
+    private double getSnapScaleX() {
+        double sx = _getSnapScaleXimpl(getScene());
+        if (snapVerbose && sx != lastScaleX) {
+            System.out.println(this+" scale is "+sx);
+            lastScaleX = sx;
+        }
+        return sx;
+    }
+
+    private double lastScaleY = -1;
+    private double getSnapScaleY() {
+        double sy = _getSnapScaleYimpl(getScene());
+        if (snapVerbose && sy != lastScaleY) {
+            System.out.println(this+" scale is "+sy);
+            lastScaleY = sy;
+        }
+        return sy;
+    }
+
+    private static double scaledRound(double value, double scale) {
+        return Math.round(value * scale) / scale;
+    }
+
+    private static double scaledFloor(double value, double scale) {
+        return Math.floor(value * scale) / scale;
+    }
+
+    private static double scaledCeil(double value, double scale) {
+        return Math.ceil(value * scale) / scale;
     }
 
     /**
      * If snapToPixel is true, then the value is rounded using Math.round. Otherwise,
      * the value is simply returned. This method will surely be JIT'd under normal

@@ -229,43 +288,80 @@
      *
      * @param value The value that needs to be snapped
      * @param snapToPixel Whether to snap to pixel
      * @return value either as passed in or rounded based on snapToPixel
      */
-    private static double snapSpace(double value, boolean snapToPixel) {
-        return snapToPixel ? Math.round(value) : value;
+    private double snapSpaceX(double value, boolean snapToPixel) {
+        return snapToPixel ? scaledRound(value, getSnapScaleX()) : value;
+    }
+    private double snapSpaceY(double value, boolean snapToPixel) {
+        return snapToPixel ? scaledRound(value, getSnapScaleY()) : value;
+    }
+
+    private static double snapSpace(double value, boolean snapToPixel, double snapScale) {
+        return snapToPixel ? scaledRound(value, snapScale) : value;
     }
 
     /**
      * If snapToPixel is true, then the value is ceil'd using Math.ceil. Otherwise,
      * the value is simply returned.
      *
      * @param value The value that needs to be snapped
      * @param snapToPixel Whether to snap to pixel
      * @return value either as passed in or ceil'd based on snapToPixel
      */
-    private static double snapSize(double value, boolean snapToPixel) {
-        return snapToPixel ? Math.ceil(value) : value;
+    private double snapSizeX(double value, boolean snapToPixel) {
+        return snapToPixel ? scaledCeil(value, getSnapScaleX()) : value;
+    }
+    private double snapSizeY(double value, boolean snapToPixel) {
+        return snapToPixel ? scaledCeil(value, getSnapScaleY()) : value;
+    }
+
+    private static double snapSize(double value, boolean snapToPixel, double snapScale) {
+        return snapToPixel ? scaledCeil(value, snapScale) : value;
     }
 
     /**
      * If snapToPixel is true, then the value is rounded using Math.round. Otherwise,
      * the value is simply returned.
      *
      * @param value The value that needs to be snapped
      * @param snapToPixel Whether to snap to pixel
      * @return value either as passed in or rounded based on snapToPixel
      */
-    private static double snapPosition(double value, boolean snapToPixel) {
-        return snapToPixel ? Math.round(value) : value;
+    private double snapPositionX(double value, boolean snapToPixel) {
+        return snapToPixel ? scaledRound(value, getSnapScaleX()) : value;
+    }
+    private double snapPositionY(double value, boolean snapToPixel) {
+        return snapToPixel ? scaledRound(value, getSnapScaleY()) : value;
+    }
+
+    private static double snapPosition(double value, boolean snapToPixel, double snapScale) {
+        return snapToPixel ? scaledRound(value, snapScale) : value;
     }
 
-    private static double snapPortion(double value, boolean snapToPixel) {
-        if (snapToPixel) {
-            return value == 0 ? 0 :(value > 0 ? Math.max(1, Math.floor(value)) : Math.min(-1, Math.ceil(value)));
+    private double snapPortionX(double value, boolean snapToPixel) {
+        if (!snapToPixel || value == 0) return value;
+        double s = getSnapScaleX();
+        value *= s;
+        if (value > 0) {
+            value = Math.max(1, Math.floor(value));
+        } else {
+            value = Math.min(-1, Math.ceil(value));
+        }
+        return value / s;
+    }
+    private double snapPortionY(double value, boolean snapToPixel) {
+        if (!snapToPixel || value == 0) return value;
+        double s = getSnapScaleY();
+        value *= s;
+        if (value > 0) {
+            value = Math.max(1, Math.floor(value));
+        } else {
+            value = Math.min(-1, Math.ceil(value));
         }
-        return value;
+        return value / s;
     }
 
     double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
                                         Function<Integer, Double> positionToWidth,
                                         double areaHeight, boolean fillHeight) {

@@ -309,15 +405,18 @@
      */
     static double getAreaBaselineOffset(List<Node> children, Callback<Node, Insets> margins,
             Function<Integer, Double> positionToWidth,
             double areaHeight, Function<Integer, Boolean> fillHeight, double minComplement, boolean snapToPixel) {
         double b = 0;
+        double snapScaleV = 0.0;
         for (int i = 0;i < children.size(); ++i) {
             Node n = children.get(i);
+            // Note: all children should be coming from the same parent so they should all have the same snapScale
+            if (snapToPixel && i == 0) snapScaleV = getSnapScaleY(n.getParent());
             Insets margin = margins.call(n);
-            double top = margin != null? snapSpace(margin.getTop(), snapToPixel) : 0;
-            double bottom = (margin != null? snapSpace(margin.getBottom(), snapToPixel) : 0);
+            double top = margin != null ? snapSpace(margin.getTop(), snapToPixel, snapScaleV) : 0;
+            double bottom = (margin != null ? snapSpace(margin.getBottom(), snapToPixel, snapScaleV) : 0);
             final double bo = n.getBaselineOffset();
             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
                 double alt = -1;
                 if (n.getContentBias() == Orientation.HORIZONTAL) {
                     alt = positionToWidth.apply(i);

@@ -1555,41 +1654,125 @@
     protected double computeMaxHeight(double width) {
         return Double.MAX_VALUE;
     }
 
     /**
-     * If this region's snapToPixel property is true, returns a value rounded
-     * to the nearest pixel, else returns the same value.
+     * If this region's snapToPixel property is false, this method returns the
+     * same value, else it tries to return a value rounded to the nearest
+     * pixel, but since there is no indication if the value is a vertical
+     * or horizontal measurement then it may be snapped to the wrong pixel
+     * size metric on screens with different horizontal and vertical scales.
      * @param value the space value to be snapped
      * @return value rounded to nearest pixel
+     * @deprecated replaced by {@code snapSpaceX()} and {@code snapSpaceY()}
      */
+    @Deprecated
     protected double snapSpace(double value) {
-        return snapSpace(value, isSnapToPixel());
+        return snapSpaceX(value, isSnapToPixel());
     }
 
     /**
-     * If this region's snapToPixel property is true, returns a value ceiled
-     * to the nearest pixel, else returns the same value.
+     * If this region's snapToPixel property is true, returns a value rounded
+     * to the nearest pixel in the horizontal direction, else returns the
+     * same value.
+     * @param value the space value to be snapped
+     * @return value rounded to nearest pixel
+     */
+    protected double snapSpaceX(double value) {
+        return snapSpaceX(value, isSnapToPixel());
+    }
+
+    /**
+     * If this region's snapToPixel property is true, returns a value rounded
+     * to the nearest pixel in the vertical direction, else returns the
+     * same value.
+     * @param value the space value to be snapped
+     * @return value rounded to nearest pixel
+     */
+    protected double snapSpaceY(double value) {
+        return snapSpaceY(value, isSnapToPixel());
+    }
+
+    /**
+     * If this region's snapToPixel property is false, this method returns the
+     * same value, else it tries to return a value ceiled to the nearest
+     * pixel, but since there is no indication if the value is a vertical
+     * or horizontal measurement then it may be snapped to the wrong pixel
+     * size metric on screens with different horizontal and vertical scales.
      * @param value the size value to be snapped
      * @return value ceiled to nearest pixel
+     * @deprecated replaced by {@code snapSizeX()} and {@code snapSizeY()}
      */
+    @Deprecated
     protected double snapSize(double value) {
-        return snapSize(value, isSnapToPixel());
+        return snapSizeX(value, isSnapToPixel());
     }
 
     /**
-     * If this region's snapToPixel property is true, returns a value rounded
-     * to the nearest pixel, else returns the same value.
+     * If this region's snapToPixel property is true, returns a value ceiled
+     * to the nearest pixel in the horizontal direction, else returns the
+     * same value.
+     * @param value the size value to be snapped
+     * @return value ceiled to nearest pixel
+     */
+    protected double snapSizeX(double value) {
+        return snapSizeX(value, isSnapToPixel());
+    }
+
+    /**
+     * If this region's snapToPixel property is true, returns a value ceiled
+     * to the nearest pixel in the vertical direction, else returns the
+     * same value.
+     * @param value the size value to be snapped
+     * @return value ceiled to nearest pixel
+     */
+    protected double snapSizeY(double value) {
+        return snapSizeY(value, isSnapToPixel());
+    }
+
+    /**
+     * If this region's snapToPixel property is false, this method returns the
+     * same value, else it tries to return a value rounded to the nearest
+     * pixel, but since there is no indication if the value is a vertical
+     * or horizontal measurement then it may be snapped to the wrong pixel
+     * size metric on screens with different horizontal and vertical scales.
      * @param value the position value to be snapped
      * @return value rounded to nearest pixel
+     * @deprecated replaced by {@code snapPositionX()} and {@code snapPositionY()}
      */
+    @Deprecated
     protected double snapPosition(double value) {
-        return snapPosition(value, isSnapToPixel());
+        return snapPositionX(value, isSnapToPixel());
+    }
+
+    /**
+     * If this region's snapToPixel property is true, returns a value rounded
+     * to the nearest pixel in the horizontal direction, else returns the
+     * same value.
+     * @param value the position value to be snapped
+     * @return value rounded to nearest pixel
+     */
+    protected double snapPositionX(double value) {
+        return snapPositionX(value, isSnapToPixel());
+    }
+
+    /**
+     * If this region's snapToPixel property is true, returns a value rounded
+     * to the nearest pixel in the vertical direction, else returns the
+     * same value.
+     * @param value the position value to be snapped
+     * @return value rounded to nearest pixel
+     */
+    protected double snapPositionY(double value) {
+        return snapPositionY(value, isSnapToPixel());
     }
 
-    double snapPortion(double value) {
-        return snapPortion(value, isSnapToPixel());
+    double snapPortionX(double value) {
+        return snapPortionX(value, isSnapToPixel());
+    }
+    double snapPortionY(double value) {
+        return snapPortionY(value, isSnapToPixel());
     }
 
 
     /**
      * Utility method to get the top inset which includes padding and border

@@ -1640,191 +1823,191 @@
         return computeChildMinAreaWidth(child, -1, margin, -1, false);
     }
 
     double computeChildMinAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
         final boolean snap = isSnapToPixel();
-        double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
-        double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
+        double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
+        double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
         double alt = -1;
         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
-            double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
-            double bottom = (margin != null? snapSpace(margin.getBottom(), snap) : 0);
+            double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
+            double bottom = (margin != null? snapSpaceY(margin.getBottom(), snap) : 0);
             double bo = child.getBaselineOffset();
             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
                     height - top - bottom - baselineComplement :
                      height - top - bottom;
             if (fillHeight) {
-                alt = snapSize(boundedSize(
+                alt = snapSizeY(boundedSize(
                         child.minHeight(-1), contentHeight,
                         child.maxHeight(-1)));
             } else {
-                alt = snapSize(boundedSize(
+                alt = snapSizeY(boundedSize(
                         child.minHeight(-1),
                         child.prefHeight(-1),
                         Math.min(child.maxHeight(-1), contentHeight)));
             }
         }
-        return left + snapSize(child.minWidth(alt)) + right;
+        return left + snapSizeX(child.minWidth(alt)) + right;
     }
 
     double computeChildMinAreaHeight(Node child, Insets margin) {
         return computeChildMinAreaHeight(child, -1, margin, -1);
     }
 
     double computeChildMinAreaHeight(Node child, double minBaselineComplement, Insets margin, double width) {
         final boolean snap = isSnapToPixel();
-        double top =margin != null? snapSpace(margin.getTop(), snap) : 0;
-        double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
+        double top =margin != null? snapSpaceY(margin.getTop(), snap) : 0;
+        double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
 
         double alt = -1;
         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
-            double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
-            double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
-            alt = snapSize(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
+            double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
+            double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
+            alt = snapSizeX(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
                     child.maxWidth(-1));
         }
 
         // For explanation, see computeChildPrefAreaHeight
         if (minBaselineComplement != -1) {
             double baseline = child.getBaselineOffset();
             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
-                return top + snapSize(child.minHeight(alt)) + bottom
+                return top + snapSizeY(child.minHeight(alt)) + bottom
                         + minBaselineComplement;
             } else {
                 return baseline + minBaselineComplement;
             }
         } else {
-            return top + snapSize(child.minHeight(alt)) + bottom;
+            return top + snapSizeY(child.minHeight(alt)) + bottom;
         }
     }
 
     double computeChildPrefAreaWidth(Node child, Insets margin) {
         return computeChildPrefAreaWidth(child, -1, margin, -1, false);
     }
 
     double computeChildPrefAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
         final boolean snap = isSnapToPixel();
-        double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
-        double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
+        double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
+        double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
         double alt = -1;
         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
-            double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
-            double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
+            double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
+            double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
             double bo = child.getBaselineOffset();
             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
                     height - top - bottom - baselineComplement :
                      height - top - bottom;
             if (fillHeight) {
-                alt = snapSize(boundedSize(
+                alt = snapSizeY(boundedSize(
                         child.minHeight(-1), contentHeight,
                         child.maxHeight(-1)));
             } else {
-                alt = snapSize(boundedSize(
+                alt = snapSizeY(boundedSize(
                         child.minHeight(-1),
                         child.prefHeight(-1),
                         Math.min(child.maxHeight(-1), contentHeight)));
             }
         }
-        return left + snapSize(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right;
+        return left + snapSizeX(boundedSize(child.minWidth(alt), child.prefWidth(alt), child.maxWidth(alt))) + right;
     }
 
     double computeChildPrefAreaHeight(Node child, Insets margin) {
         return computeChildPrefAreaHeight(child, -1, margin, -1);
     }
 
     double computeChildPrefAreaHeight(Node child, double prefBaselineComplement, Insets margin, double width) {
         final boolean snap = isSnapToPixel();
-        double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
-        double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
+        double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
+        double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
 
         double alt = -1;
         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
-            double left = margin != null ? snapSpace(margin.getLeft(), snap) : 0;
-            double right = margin != null ? snapSpace(margin.getRight(), snap) : 0;
-            alt = snapSize(boundedSize(
+            double left = margin != null ? snapSpaceX(margin.getLeft(), snap) : 0;
+            double right = margin != null ? snapSpaceX(margin.getRight(), snap) : 0;
+            alt = snapSizeX(boundedSize(
                     child.minWidth(-1), width != -1 ? width - left - right
                     : child.prefWidth(-1), child.maxWidth(-1)));
         }
 
         if (prefBaselineComplement != -1) {
             double baseline = child.getBaselineOffset();
             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
                 // When baseline is same as height, the preferred height of the node will be above the baseline, so we need to add
                 // the preferred complement to it
-                return top + snapSize(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom
+                return top + snapSizeY(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom
                         + prefBaselineComplement;
             } else {
                 // For all other Nodes, it's just their baseline and the complement.
                 // Note that the complement already contain the Node's preferred (or fixed) height
                 return top + baseline + prefBaselineComplement + bottom;
             }
         } else {
-            return top + snapSize(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom;
+            return top + snapSizeY(boundedSize(child.minHeight(alt), child.prefHeight(alt), child.maxHeight(alt))) + bottom;
         }
     }
 
     double computeChildMaxAreaWidth(Node child, double baselineComplement, Insets margin, double height, boolean fillHeight) {
         double max = child.maxWidth(-1);
         if (max == Double.MAX_VALUE) {
             return max;
         }
         final boolean snap = isSnapToPixel();
-        double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
-        double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
+        double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
+        double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
         double alt = -1;
         if (height != -1 && child.isResizable() && child.getContentBias() == Orientation.VERTICAL) { // width depends on height
-            double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
-            double bottom = (margin != null? snapSpace(margin.getBottom(), snap) : 0);
+            double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
+            double bottom = (margin != null? snapSpaceY(margin.getBottom(), snap) : 0);
             double bo = child.getBaselineOffset();
             final double contentHeight = bo == BASELINE_OFFSET_SAME_AS_HEIGHT && baselineComplement != -1 ?
                     height - top - bottom - baselineComplement :
                      height - top - bottom;
             if (fillHeight) {
-                alt = snapSize(boundedSize(
+                alt = snapSizeY(boundedSize(
                         child.minHeight(-1), contentHeight,
                         child.maxHeight(-1)));
             } else {
-                alt = snapSize(boundedSize(
+                alt = snapSizeY(boundedSize(
                         child.minHeight(-1),
                         child.prefHeight(-1),
                         Math.min(child.maxHeight(-1), contentHeight)));
             }
             max = child.maxWidth(alt);
         }
         // if min > max, min wins, so still need to call boundedSize()
-        return left + snapSize(boundedSize(child.minWidth(alt), max, Double.MAX_VALUE)) + right;
+        return left + snapSizeX(boundedSize(child.minWidth(alt), max, Double.MAX_VALUE)) + right;
     }
 
     double computeChildMaxAreaHeight(Node child, double maxBaselineComplement, Insets margin, double width) {
         double max = child.maxHeight(-1);
         if (max == Double.MAX_VALUE) {
             return max;
         }
 
         final boolean snap = isSnapToPixel();
-        double top = margin != null? snapSpace(margin.getTop(), snap) : 0;
-        double bottom = margin != null? snapSpace(margin.getBottom(), snap) : 0;
+        double top = margin != null? snapSpaceY(margin.getTop(), snap) : 0;
+        double bottom = margin != null? snapSpaceY(margin.getBottom(), snap) : 0;
         double alt = -1;
         if (child.isResizable() && child.getContentBias() == Orientation.HORIZONTAL) { // height depends on width
-            double left = margin != null? snapSpace(margin.getLeft(), snap) : 0;
-            double right = margin != null? snapSpace(margin.getRight(), snap) : 0;
-            alt = snapSize(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
+            double left = margin != null? snapSpaceX(margin.getLeft(), snap) : 0;
+            double right = margin != null? snapSpaceX(margin.getRight(), snap) : 0;
+            alt = snapSizeX(width != -1? boundedSize(child.minWidth(-1), width - left - right, child.maxWidth(-1)) :
                 child.minWidth(-1));
             max = child.maxHeight(alt);
         }
         // For explanation, see computeChildPrefAreaHeight
         if (maxBaselineComplement != -1) {
             double baseline = child.getBaselineOffset();
             if (child.isResizable() && baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
-                return top + snapSize(boundedSize(child.minHeight(alt), child.maxHeight(alt), Double.MAX_VALUE)) + bottom
+                return top + snapSizeY(boundedSize(child.minHeight(alt), child.maxHeight(alt), Double.MAX_VALUE)) + bottom
                         + maxBaselineComplement;
             } else {
                 return top + baseline + maxBaselineComplement + bottom;
             }
         } else {
             // if min > max, min wins, so still need to call boundedSize()
-            return top + snapSize(boundedSize(child.minHeight(alt), max, Double.MAX_VALUE)) + bottom;
+            return top + snapSizeY(boundedSize(child.minHeight(alt), max, Double.MAX_VALUE)) + bottom;
         }
     }
 
     /* Max of children's minimum area widths */
 

@@ -1950,21 +2133,21 @@
             double maxBelow = 0;
             for (int i = 0, maxPos = children.size(); i < maxPos; i++) {
                 final Node child = children.get(i);
                 final double childWidth = Double.isNaN(singleChildWidth) ? childWidths[i] : singleChildWidth;
                 Insets margin = childMargins.call(child);
-                final double top = margin != null? snapSpace(margin.getTop()) : 0;
-                final double bottom = margin != null? snapSpace(margin.getBottom()) : 0;
+                final double top = margin != null? snapSpaceY(margin.getTop()) : 0;
+                final double bottom = margin != null? snapSpaceY(margin.getBottom()) : 0;
                 final double baseline = child.getBaselineOffset();
 
-                final double childHeight = minimum? snapSize(child.minHeight(childWidth)) : snapSize(child.prefHeight(childWidth));
+                final double childHeight = minimum? snapSizeY(child.minHeight(childWidth)) : snapSizeY(child.prefHeight(childWidth));
                 if (baseline == BASELINE_OFFSET_SAME_AS_HEIGHT) {
                     maxAbove = Math.max(maxAbove, childHeight + top);
                 } else {
                     maxAbove = Math.max(maxAbove, baseline + top);
                     maxBelow = Math.max(maxBelow,
-                            snapSpace(minimum?snapSize(child.minHeight(childWidth)) : snapSize(child.prefHeight(childWidth))) -
+                            snapSpaceY(minimum?snapSizeY(child.minHeight(childWidth)) : snapSizeY(child.prefHeight(childWidth))) -
                             baseline + bottom);
                 }
             }
             return maxAbove + maxBelow; //remind(aim): ceil this value?
         } else {

@@ -2060,16 +2243,18 @@
      * @since JavaFX 8.0
      */
     public static void positionInArea(Node child, double areaX, double areaY, double areaWidth, double areaHeight,
                                double areaBaselineOffset, Insets margin, HPos halignment, VPos valignment, boolean isSnapToPixel) {
         Insets childMargin = margin != null? margin : Insets.EMPTY;
+        double snapScaleX = isSnapToPixel ? getSnapScaleX(child) : 1.0;
+        double snapScaleY = isSnapToPixel ? getSnapScaleY(child) : 1.0;
 
         position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
-                snapSpace(childMargin.getTop(), isSnapToPixel),
-                snapSpace(childMargin.getRight(), isSnapToPixel),
-                snapSpace(childMargin.getBottom(), isSnapToPixel),
-                snapSpace(childMargin.getLeft(), isSnapToPixel),
+                snapSpace(childMargin.getTop(), isSnapToPixel, snapScaleY),
+                snapSpace(childMargin.getRight(), isSnapToPixel, snapScaleX),
+                snapSpace(childMargin.getBottom(), isSnapToPixel, snapScaleY),
+                snapSpace(childMargin.getLeft(), isSnapToPixel, snapScaleX),
                 halignment, valignment, isSnapToPixel);
     }
 
     /**
      * Utility method which lays out the child within an area of this

@@ -2294,36 +2479,39 @@
                                double areaBaselineOffset,
                                Insets margin, boolean fillWidth, boolean fillHeight,
                                HPos halignment, VPos valignment, boolean isSnapToPixel) {
 
         Insets childMargin = margin != null ? margin : Insets.EMPTY;
+        double snapScaleX = isSnapToPixel ? getSnapScaleX(child) : 1.0;
+        double snapScaleY = isSnapToPixel ? getSnapScaleY(child) : 1.0;
 
-        double top = snapSpace(childMargin.getTop(), isSnapToPixel);
-        double bottom = snapSpace(childMargin.getBottom(), isSnapToPixel);
-        double left = snapSpace(childMargin.getLeft(), isSnapToPixel);
-        double right = snapSpace(childMargin.getRight(), isSnapToPixel);
+        double top = snapSpace(childMargin.getTop(), isSnapToPixel, snapScaleY);
+        double bottom = snapSpace(childMargin.getBottom(), isSnapToPixel, snapScaleY);
+        double left = snapSpace(childMargin.getLeft(), isSnapToPixel, snapScaleX);
+        double right = snapSpace(childMargin.getRight(), isSnapToPixel, snapScaleX);
 
         if (valignment == VPos.BASELINE) {
             double bo = child.getBaselineOffset();
             if (bo == BASELINE_OFFSET_SAME_AS_HEIGHT) {
                 if (child.isResizable()) {
                     // Everything below the baseline is like an "inset". The Node with BASELINE_OFFSET_SAME_AS_HEIGHT cannot
                     // be resized to this area
-                    bottom += snapSpace(areaHeight - areaBaselineOffset, isSnapToPixel);
+                    bottom += snapSpace(areaHeight - areaBaselineOffset, isSnapToPixel, snapScaleY);
                 } else {
-                    top = snapSpace(areaBaselineOffset - child.getLayoutBounds().getHeight(), isSnapToPixel);
+                    top = snapSpace(areaBaselineOffset - child.getLayoutBounds().getHeight(), isSnapToPixel, snapScaleY);
                 }
             } else {
-                top = snapSpace(areaBaselineOffset - bo, isSnapToPixel);
+                top = snapSpace(areaBaselineOffset - bo, isSnapToPixel, snapScaleY);
             }
         }
 
 
         if (child.isResizable()) {
             Vec2d size = boundedNodeSizeWithBias(child, areaWidth - left - right, areaHeight - top - bottom,
                     fillWidth, fillHeight, TEMP_VEC2D);
-            child.resize(snapSize(size.x, isSnapToPixel),snapSize(size.y, isSnapToPixel));
+            child.resize(snapSize(size.x, isSnapToPixel, snapScaleX),
+                         snapSize(size.y, isSnapToPixel, snapScaleX));
         }
         position(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
                 top, right, bottom, left, halignment, valignment, isSnapToPixel);
     }
 

@@ -2344,12 +2532,16 @@
             }
         } else {
             yoffset = topMargin + computeYOffset(areaHeight - topMargin - bottomMargin,
                                          child.getLayoutBounds().getHeight(), vpos);
         }
-        final double x = snapPosition(areaX + xoffset, isSnapToPixel);
-        final double y = snapPosition(areaY + yoffset, isSnapToPixel);
+        double x = areaX + xoffset;
+        double y = areaY + yoffset;
+        if (isSnapToPixel) {
+            x = snapPosition(x, true, getSnapScaleX(child));
+            y = snapPosition(y, true, getSnapScaleY(child));
+        }
 
         child.relocate(x,y);
     }
 
      /**************************************************************************