modules/graphics/src/main/java/javafx/stage/Window.java

Print this page

        

@@ -26,14 +26,11 @@
 package javafx.stage;
 
 import java.security.AllPermission;
 import java.security.AccessControlContext;
 import java.security.AccessController;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
 
 import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
 import javafx.beans.property.DoubleProperty;
 import javafx.beans.property.DoublePropertyBase;
 import javafx.beans.property.ObjectProperty;

@@ -43,10 +40,11 @@
 import javafx.beans.property.ReadOnlyObjectProperty;
 import javafx.beans.property.ReadOnlyObjectWrapper;
 import javafx.beans.property.ReadOnlyDoubleProperty;
 import javafx.beans.property.ReadOnlyDoubleWrapper;
 import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.SimpleDoubleProperty;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.collections.ObservableMap;
 import javafx.event.Event;
 import javafx.event.EventDispatchChain;

@@ -56,19 +54,20 @@
 import javafx.event.EventType;
 import javafx.geometry.Rectangle2D;
 import javafx.scene.Scene;
 
 import com.sun.javafx.util.Utils;
-import com.sun.javafx.util.WeakReferenceQueue;
 import com.sun.javafx.css.StyleManager;
 import com.sun.javafx.stage.WindowEventDispatcher;
 import com.sun.javafx.stage.WindowHelper;
 import com.sun.javafx.stage.WindowPeerListener;
 import com.sun.javafx.tk.TKPulseListener;
 import com.sun.javafx.tk.TKScene;
 import com.sun.javafx.tk.TKStage;
 import com.sun.javafx.tk.Toolkit;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
 
 
 /**
  * <p>
  *     A top level window within which a scene is hosted, and with which the user

@@ -106,29 +105,24 @@
                                                   double height) {
                         window.notifySizeChanged(width, height);
                     }
 
                     @Override
+                    public void notifyScaleChanged(Window window,
+                                                   double newOutputScaleX,
+                                                   double newOutputScaleY) {
+                        window.updateOutputScales(newOutputScaleX, newOutputScaleY);
+                    }
+
+                    @Override
                     public void notifyScreenChanged(Window window,
                                                   Object from,
                                                   Object to) {
                         window.notifyScreenChanged(from, to);
                     }
 
                     @Override
-                    public float getUIScale(Window window) {
-                        TKStage peer = window.impl_peer;
-                        return peer == null ? 1.0f : peer.getUIScale();
-                    }
-
-                    @Override
-                    public float getRenderScale(Window window) {
-                        TKStage peer = window.impl_peer;
-                        return peer == null ? 1.0f : peer.getRenderScale();
-                    }
-
-                    @Override
                     public ReadOnlyObjectProperty<Screen> screenProperty(Window window) {
                         return window.screenProperty();
                     }
 
                     @Override

@@ -287,10 +281,157 @@
                                                CENTER_ON_SCREEN_Y_FRACTION);
             applyBounds();
         }
     }
 
+    private void updateOutputScales(double sx, double sy) {
+        // We call updateRenderScales() before updating the property
+        // values so that an application can listen to the properties
+        // and set their own values overriding the default values we set.
+        updateRenderScales(sx, sy);
+        // Now set the properties and trigger any potential listeners.
+        outputScaleX.set(sx);
+        outputScaleY.set(sy);
+    }
+
+    void updateRenderScales(double sx, double sy) {
+        boolean forceInt = forceIntegerRenderScale.get();
+        if (!renderScaleX.isBound()) {
+            renderScaleX.set(forceInt ? Math.ceil(sx) : sx);
+        }
+        if (!renderScaleY.isBound()) {
+            renderScaleY.set(forceInt ? Math.ceil(sy) : sy);
+        }
+    }
+
+    /**
+     * The scale that the {@code Window} will apply to horizontal scene
+     * coordinates in all stages of rendering and compositing the output
+     * to the screen or other destination device.
+     * This property is updated asynchronously by the system at various
+     * times including:
+     * <ul>
+     * <li>Window creation
+     * <li>At some point during moving a window to a new {@code Screen}
+     * which may be before or after the {@link Screen} property is updated.
+     * <li>In response to a change in user preferences for output scaling.
+     * </ul>
+     * @see outputScaleThreshold
+     */
+    private ReadOnlyDoubleWrapper outputScaleX =
+        new ReadOnlyDoubleWrapper(this, "outputScaleX", 1.0);
+    public final double getOutputScaleX() {
+        return outputScaleX.get();
+    }
+    public final ReadOnlyDoubleProperty outputScaleXProperty() {
+        return outputScaleX.getReadOnlyProperty();
+    }
+
+    /**
+     * The scale that the {@code Window} will apply to vertical scene
+     * coordinates in all stages of rendering and compositing the output
+     * to the screen or other destination device.
+     * This property is updated asynchronously by the system at various
+     * times including:
+     * <ul>
+     * <li>Window creation
+     * <li>At some point during moving a window to a new {@code Screen}
+     * which may be before or after the {@link Screen} property is updated.
+     * <li>In response to a change in user preferences for output scaling.
+     * </ul>
+     * @see outputScaleThreshold
+     */
+    private ReadOnlyDoubleWrapper outputScaleY =
+        new ReadOnlyDoubleWrapper(this, "outputScaleY", 1.0);
+    public final double getOutputScaleY() {
+        return outputScaleY.get();
+    }
+    public final ReadOnlyDoubleProperty outputScaleYProperty() {
+        return outputScaleY.getReadOnlyProperty();
+    }
+
+    /**
+     * Boolean property that controls whether only integer render scales
+     * are set by default by the system when there is a change in the
+     * associated output scale.
+     * The {@code renderScale} properties will be updated directly and
+     * simultaneously with any changes in the associated {@code outputScale}
+     * properties, but the values can be overridden by subsequent calls to
+     * the {@code setRenderScale} setters or through appropriate use of
+     * binding.
+     * This property will not prevent setting non-integer scales
+     * directly using the {@code renderScale} property object or the
+     * convenience setter method.
+     */
+    private BooleanProperty forceIntegerRenderScale =
+        new SimpleBooleanProperty(this, "forceIntegerRenderScale", false) {
+            @Override
+            protected void invalidated() {
+                updateRenderScales(getOutputScaleX(),
+                                   getOutputScaleY());
+            }
+        };
+    public final void setForceIntegerRenderScale(boolean forced) {
+        forceIntegerRenderScale.set(forced);
+    }
+    public final boolean isForceIntegerRenderScale() {
+        return forceIntegerRenderScale.get();
+    }
+    public final BooleanProperty forceIntegerRenderScaleProperty() {
+        return forceIntegerRenderScale;
+    }
+
+    /**
+     * The horizontal scale that the {@code Window} will use when rendering
+     * its {@code Scene} to the rendering buffer.
+     * This property is automatically updated whenever there is a change in
+     * the {@link outputScaleX} property and can be overridden either by
+     * calling {@code setRenderScaleX()} in response to a listener on the
+     * {@code outputScaleX} property or by binding it appropriately.
+     */
+    private DoubleProperty renderScaleX =
+        new SimpleDoubleProperty(this, "renderScaleX", 1.0) {
+            @Override
+            protected void invalidated() {
+                peerBoundsConfigurator.setRenderScaleX(get());
+            }
+        };
+    public final void setRenderScaleX(double scale) {
+        renderScaleX.set(scale);
+    }
+    public final double getRenderScaleX() {
+        return renderScaleX.get();
+    }
+    public final DoubleProperty renderScaleXProperty() {
+        return renderScaleX;
+    }
+
+    /**
+     * The vertical scale that the {@code Window} will use when rendering
+     * its {@code Scene} to the rendering buffer.
+     * This property is automatically updated whenever there is a change in
+     * the {@link outputScaleY} property and can be overridden either by
+     * calling {@code setRenderScaleY()} in response to a listener on the
+     * {@code outputScaleY} property or by binding it appropriately.
+     */
+    private DoubleProperty renderScaleY =
+        new SimpleDoubleProperty(this, "renderScaleY", 1.0) {
+            @Override
+            protected void invalidated() {
+                peerBoundsConfigurator.setRenderScaleY(get());
+            }
+        };
+    public final void setRenderScaleY(double scale) {
+        renderScaleY.set(scale);
+    }
+    public final double getRenderScaleY() {
+        return renderScaleY.get();
+    }
+    public final DoubleProperty renderScaleYProperty() {
+        return renderScaleY;
+    }
+
     private boolean xExplicit = false;
     /**
      * The horizontal location of this {@code Stage} on the screen. Changing
      * this attribute will move the {@code Stage} horizontally. Changing this
      * attribute will not visually affect a {@code Stage} while

@@ -840,10 +981,11 @@
                         peerListener = new WindowPeerListener(Window.this);
                     }
 
                     // Setup listener for changes coming back from peer
                     impl_peer.setTKStageListener(peerListener);
+                    updateOutputScales(impl_peer.getOutputScaleX(), impl_peer.getOutputScaleY());
                     // Register pulse listener
                     tk.addStageTkPulseListener(peerBoundsConfigurator);
 
                     if (getScene() != null) {
                         getScene().impl_initPeer();

@@ -1203,10 +1345,12 @@
     /**
      * Caches all requested bounds settings and applies them at once during
      * the next pulse.
      */
     private final class TKBoundsConfigurator implements TKPulseListener {
+        private double renderScaleX;
+        private double renderScaleY;
         private double x;
         private double y;
         private float xGravity;
         private float yGravity;
         private double windowWidth;

@@ -1218,10 +1362,20 @@
 
         public TKBoundsConfigurator() {
             reset();
         }
 
+        public void setRenderScaleX(final double renderScaleX) {
+            this.renderScaleX = renderScaleX;
+            setDirty();
+        }
+
+        public void setRenderScaleY(final double renderScaleY) {
+            this.renderScaleY = renderScaleY;
+            setDirty();
+        }
+
         public void setX(final double x, final float xGravity) {
             this.x = x;
             this.xGravity = xGravity;
             setDirty();
         }

@@ -1274,30 +1428,41 @@
             setDirty();
         }
 
         public void apply() {
             if (dirty) {
-                impl_peer.setBounds((float) (Double.isNaN(x) ? 0 : x),
-                                    (float) (Double.isNaN(y) ? 0 : y),
-                                    !Double.isNaN(x),
-                                    !Double.isNaN(y),
-                                    (float) windowWidth,
-                                    (float) windowHeight,
-                                    (float) clientWidth,
-                                    (float) clientHeight,
-                                    xGravity, yGravity);
-
+                // Snapshot values and then reset() before we call down
+                // as we may end up with recursive calls back up with
+                // new values that must be recorded as dirty.
+                boolean xSet = !Double.isNaN(x);
+                float newX = xSet ? (float) x : 0f;
+                boolean ySet = !Double.isNaN(y);
+                float newY = ySet ? (float) y : 0f;
+                float newWW = (float) windowWidth;
+                float newWH = (float) windowHeight;
+                float newCW = (float) clientWidth;
+                float newCH = (float) clientHeight;
+                float newXG = xGravity;
+                float newYG = yGravity;
+                float newRX = (float) renderScaleX;
+                float newRY = (float) renderScaleY;
                 reset();
+                impl_peer.setBounds(newX, newY, xSet, ySet,
+                                    newWW, newWH, newCW, newCH,
+                                    newXG, newYG,
+                                    newRX, newRY);
             }
         }
 
         @Override
         public void pulse() {
             apply();
         }
 
         private void reset() {
+            renderScaleX = 0.0;
+            renderScaleY = 0.0;
             x = Double.NaN;
             y = Double.NaN;
             xGravity = 0;
             yGravity = 0;
             windowWidth = -1;