All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jhotdraw8.draw.gui.ZoomableScrollPane Maven / Gradle / Ivy


/*
 * @(#)ZoomableScrollPane.java
 * Copyright © 2023 The authors and contributors of JHotDraw. MIT License.
 */
package org.jhotdraw8.draw.gui;

import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.StyleConverter;
import javafx.css.Styleable;
import javafx.css.StyleableBooleanProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.SubScene;
import javafx.scene.control.Control;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ScrollEvent;
import javafx.scene.input.ZoomEvent;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Affine;
import javafx.scene.transform.NonInvertibleTransformException;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import org.jhotdraw8.base.util.MathUtil;
import org.jhotdraw8.fxbase.binding.CustomBinding;
import org.jhotdraw8.geom.FXTransforms;
import org.jspecify.annotations.Nullable;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * A ScrollPane that also supports zooming.
 * 

* The ZoomScrollPane can zoom and scroll its content. *

* It also supports a background and a foreground that * scroll with the content, but that do not zoom on their own. *

* You can not set the background, foreground and content objects, * you can only access their children list. *

* The ZoomScrollPane has the following scene structure: *

    *
  • {@value #ZOOMABLE_SCROLL_PANE_STYLE_CLASS} – {@link GridPane}
      *
    • "scroll-bar:vertical" – {@link ScrollBar}
    • *
    • "scroll-bar:horizontal" – {@link ScrollBar}
    • *
    • {@value #ZOOMABLE_SCROLL_PANE_VIEWPORT_STYLE_CLASS} – {@link StackPane}
        *
      • {@value #ZOOMABLE_SCROLL_PANE_BACKGROUND_STYLE_CLASS} – {@link StackPane}
          *
        • background - getBackgroundChildren().add(...)
        • *
      • *
      • {@value #ZOOMABLE_SCROLL_PANE_SUBSCENE_STYLE_CLASS} – {@link SubScene}
          *
        • {@link StackPane}
            *
          • content - getContentChildren().add(...)
          • *
        • *
      • *
      • {@value #ZOOMABLE_SCROLL_PANE_FOREGROUND_STYLE_CLASS} – {@link StackPane}
          *
        • foreground - getForegroundChildren().add(...)
        • *
      • *
    • *
  • *
*/ public class ZoomableScrollPane extends GridPane { /** * The style class of the ZoomableScrollPane is {@value #ZOOMABLE_SCROLL_PANE_STYLE_CLASS}. */ public static final String ZOOMABLE_SCROLL_PANE_STYLE_CLASS = "jhotdraw8-zoomable-scroll-pane"; /** * The style class of the ZoomableScrollPane is {@value #ZOOMABLE_SCROLL_PANE_VIEWPORT_STYLE_CLASS}. */ public static final String ZOOMABLE_SCROLL_PANE_VIEWPORT_STYLE_CLASS = "jhotdraw8-zoomable-scroll-pane-viewpprt"; /** * The style class of the ZoomableScrollPane is {@value #ZOOMABLE_SCROLL_PANE_BACKGROUND_STYLE_CLASS}. */ public static final String ZOOMABLE_SCROLL_PANE_BACKGROUND_STYLE_CLASS = "jhotdraw8-zoomable-scroll-pane-background"; /** * The style class of the ZoomableScrollPane is {@value #ZOOMABLE_SCROLL_PANE_SUBSCENE_STYLE_CLASS}. */ public static final String ZOOMABLE_SCROLL_PANE_SUBSCENE_STYLE_CLASS = "jhotdraw8-zoomable-scroll-pane-subscene"; /** * The style class of the ZoomableScrollPane is {@value #ZOOMABLE_SCROLL_PANE_FOREGROUND_STYLE_CLASS}. */ public static final String ZOOMABLE_SCROLL_PANE_FOREGROUND_STYLE_CLASS = "jhotdraw8-zoomable-scroll-pane-foreground"; private final DoubleProperty zoomFactor = new SimpleDoubleProperty(this, "zoomFactor", 1.0); private final ObjectProperty visibleContentRect = new SimpleObjectProperty<>(this, "contentRect"); @FXML // ResourceBundle that was given to the FXMLLoader private ResourceBundle resources; @FXML // URL location of the FXML file that was given to the FXMLLoader private URL location; @FXML // fx:id="horizontalScrollBar" private ScrollBar horizontalScrollBar; // Value injected by FXMLLoader @FXML // fx:id="verticalScrollBar" private ScrollBar verticalScrollBar; // Value injected by FXMLLoader @FXML // fx:id="backgroundPane" private Pane background; // Value injected by FXMLLoader @FXML // fx:id="subScene" private SubScene subScene; // Value injected by FXMLLoader private Pane content; @FXML // fx:id="foregroundPane" private Pane foreground; // Value injected by FXMLLoader public ZoomableScrollPane() { } @FXML // This method is called by the FXMLLoader when initialization is complete void initialize() { assert horizontalScrollBar != null : "fx:id=\"horizontalScrollBar\" was not injected: check your FXML file 'ZoomableScrollPane.fxml'."; assert verticalScrollBar != null : "fx:id=\"verticalScrollBar\" was not injected: check your FXML file 'ZoomableScrollPane.fxml'."; assert background != null : "fx:id=\"backgroundPane\" was not injected: check your FXML file 'ZoomableScrollPane.fxml'."; assert subScene != null : "fx:id=\"subScene\" was not injected: check your FXML file 'ZoomableScrollPane.fxml'."; assert foreground != null : "fx:id=\"foregroundPane\" was not injected: check your FXML file 'ZoomableScrollPane.fxml'."; assert viewportPane != null : "fx:id=\"viewportPane\" was not injected: check your FXML file 'ZoomableScrollPane.fxml'."; // Initialize style classes // ------------------------ initStyle(); initLayout(); initBindings(); initBehavior(); } private void initStyle() { this.getStyleClass().add(ZOOMABLE_SCROLL_PANE_STYLE_CLASS); viewportPane.getStyleClass().add(ZOOMABLE_SCROLL_PANE_VIEWPORT_STYLE_CLASS); background.getStyleClass().add(ZOOMABLE_SCROLL_PANE_BACKGROUND_STYLE_CLASS); foreground.getStyleClass().add(ZOOMABLE_SCROLL_PANE_FOREGROUND_STYLE_CLASS); subScene.getStyleClass().add(ZOOMABLE_SCROLL_PANE_SUBSCENE_STYLE_CLASS); } private void initBehavior() { // - Scroll in chunks of 20 pixels horizontalScrollBar.setUnitIncrement(20); verticalScrollBar.setUnitIncrement(20); // - Try to keep the center of the viewRect fixed when zooming. zoomFactor.addListener(this::onZoomFactorChanged); // - Scroll on scroll event. viewportPane.addEventHandler(ScrollEvent.SCROLL, event -> { onScrollEvent(event, horizontalScrollBar, event.getDeltaX()); onScrollEvent(event, verticalScrollBar, event.getDeltaY()); }); // FIXME Zoom on zoom event. viewportPane.addEventHandler(ZoomEvent.ZOOM, event -> { // System.out.println("zoomEvent detected yay"); }); } private void onZoomFactorChanged(Observable o, Number oldv, Number newv) { double oldvv = oldv.doubleValue(), newvv = newv.doubleValue(), sf = oldvv / newvv, hmin = horizontalScrollBar.getMin(), hmax = horizontalScrollBar.getMax(), hvalue = horizontalScrollBar.getValue(), hvisible = horizontalScrollBar.getVisibleAmount(), holdmax = hmax * sf, holdmin = hmin * sf, vmin = verticalScrollBar.getMin(), vmax = verticalScrollBar.getMax(), vvalue = verticalScrollBar.getValue(), vvisible = verticalScrollBar.getVisibleAmount(), voldmax = vmax * sf, voldmin = vmin * sf; double osf = 1 / oldvv; double oldx = ((holdmax - hvisible) * (hvalue - holdmin) / (holdmax - holdmin)) * osf; double oldy = ((voldmax - vvisible) * (vvalue - voldmin) / (voldmax - voldmin)) * osf; double oldw = hvisible * osf; double oldh = vvisible * osf; scrollContentRectToVisible(oldx, oldy, oldw, oldh); } public ReadOnlyObjectProperty viewportRectProperty() { return viewportPane.boundsInParentProperty(); } private void initBindings() { // - Translate the viewRect and the background + foreground panes when // the scrollbars are moved. DoubleBinding contentTranslateXBinding = createContentRectTranslateBinding(horizontalScrollBar); DoubleBinding contentTranslateYBinding = createContentRectTranslateBinding(verticalScrollBar); visibleContentRect.bind(CustomBinding.compute(() -> { double invf = 1 / zoomFactor.get(); return new BoundingBox( contentTranslateXBinding.get() * invf, contentTranslateYBinding.get() * invf, horizontalScrollBar.getVisibleAmount() * invf, verticalScrollBar.getVisibleAmount() * invf ); }, contentTranslateXBinding, contentTranslateYBinding, horizontalScrollBar.visibleAmountProperty(), verticalScrollBar.visibleAmountProperty(), zoomFactor, layoutBoundsProperty())); // - Adjust the size of the sub-scene when the viewport is resized. subScene.widthProperty().bind(viewportWidthProperty()); subScene.heightProperty().bind(viewportHeightProperty()); // - Translate the subScenePane when the scrollbars are moved, // and scale the subScenePane when the zoomFactor is changed. Scale scale = new Scale(); scale.setPivotX(0); scale.setPivotY(0); zoomFactor.addListener((o, oldv, newv) -> { scale.setX(newv.doubleValue()); scale.setY(newv.doubleValue()); }); Translate translate = new Translate(); translate.xProperty().bind(contentTranslateXBinding.negate()); translate.yProperty().bind(contentTranslateYBinding.negate()); content.getTransforms().addAll(translate, scale); // - Adjust the scrollbar max, when the subScene is resized. horizontalScrollBar.maxProperty().bind( CustomBinding.compute(() -> getContentWidth() * getZoomFactor() + getInsets().getRight(), contentWidthProperty(), zoomFactor, insetsProperty())); horizontalScrollBar.minProperty().bind( CustomBinding.compute(() -> -getInsets().getLeft(), insetsProperty())); verticalScrollBar.maxProperty().bind( CustomBinding.compute(() -> getContentHeight() * getZoomFactor() + getInsets().getBottom(), contentHeightProperty(), zoomFactor, insetsProperty())); verticalScrollBar.minProperty().bind( CustomBinding.compute(() -> -getInsets().getTop(), insetsProperty())); // - Adjust the scrollbar visibleAmount when the viewport is resized. horizontalScrollBar.visibleAmountProperty().bind(viewportWidthProperty()); verticalScrollBar.visibleAmountProperty().bind(viewportHeightProperty()); horizontalScrollBar.blockIncrementProperty().bind(viewportWidthProperty()); verticalScrollBar.blockIncrementProperty().bind(viewportHeightProperty()); // - Only show the scrollbars if their visible amount is less than their // extent (we can use the max value here, because we let min=0). onlyShowHorizontalScrollBarIfNeeded(horizontalScrollBar, this.getRowConstraints().get(1), hbarPolicyProperty()); onlyShowVerticalScrollBarIfNeeded(verticalScrollBar, this.getColumnConstraints().get(1), vbarPolicyProperty()); contentToView.bind(CustomBinding.compute(() -> { double sf = getZoomFactor(); Bounds vcRect = getVisibleContentRect(); double x, y; x = vcRect.getMinX(); y = vcRect.getMinY(); return new Affine( sf, 0, -x * sf, 0, sf, -y * sf); }, visibleContentRect )); } private static class StyleableProperties { @SuppressWarnings("unchecked") private static final CssMetaData HBAR_POLICY = new CssMetaData<>("-fx-hbar-policy", StyleConverter.getEnumConverter(ScrollPane.ScrollBarPolicy.class), ScrollPane.ScrollBarPolicy.AS_NEEDED) { @Override public boolean isSettable(ZoomableScrollPane n) { return n.hbarPolicy == null || !n.hbarPolicy.isBound(); } @Override public StyleableProperty getStyleableProperty(ZoomableScrollPane n) { return (StyleableProperty) n.hbarPolicyProperty(); } }; @SuppressWarnings("unchecked") private static final CssMetaData VBAR_POLICY = new CssMetaData<>("-fx-vbar-policy", StyleConverter.getEnumConverter(ScrollPane.ScrollBarPolicy.class), ScrollPane.ScrollBarPolicy.AS_NEEDED) { @Override public boolean isSettable(ZoomableScrollPane n) { return n.vbarPolicy == null || !n.vbarPolicy.isBound(); } @Override public StyleableProperty getStyleableProperty(ZoomableScrollPane n) { return (StyleableProperty) n.vbarPolicyProperty(); } }; private static final CssMetaData PANNABLE = new CssMetaData<>("-fx-pannable", StyleConverter.getBooleanConverter(), Boolean.FALSE) { @Override public boolean isSettable(ZoomableScrollPane n) { return n.pannable == null || !n.pannable.isBound(); } @Override public StyleableProperty getStyleableProperty(ZoomableScrollPane n) { return n.pannableProperty(); } }; private static final List> STYLEABLES; static { final List> styleables = new ArrayList<>(Control.getClassCssMetaData()); styleables.add(HBAR_POLICY); styleables.add(VBAR_POLICY); styleables.add(PANNABLE); STYLEABLES = Collections.unmodifiableList(styleables); } } private void onlyShowHorizontalScrollBarIfNeeded(ScrollBar scrollBar, RowConstraints rowConstraints, ObjectProperty scrollBarPolicy) { BooleanBinding visibilityBinding; visibilityBinding = Bindings.createBooleanBinding(() -> { if (scrollBarPolicy.get() == ScrollPane.ScrollBarPolicy.NEVER) { return false; } if (scrollBarPolicy.get() == ScrollPane.ScrollBarPolicy.ALWAYS) { return true; } return contentWidthProperty().get() > getWidth() || contentHeightProperty().get() > getHeight() && contentWidthProperty().get() > getWidth() - verticalScrollBar.getWidth(); }, scrollBarPolicy, contentHeightProperty(), contentWidthProperty(), heightProperty(), widthProperty(), verticalScrollBar.prefWidthProperty() ); scrollBar.visibleProperty().bind(visibilityBinding); rowConstraints.prefHeightProperty().bind( CustomBinding.convert(visibilityBinding, b -> b ? ScrollBar.USE_COMPUTED_SIZE : 0)); } private void onlyShowVerticalScrollBarIfNeeded(ScrollBar scrollBar, ColumnConstraints colConstraints, ObjectProperty scrollBarPolicy) { BooleanBinding visibilityBinding; visibilityBinding = Bindings.createBooleanBinding(() -> { if (scrollBarPolicy.get() == ScrollPane.ScrollBarPolicy.NEVER) { return false; } if (scrollBarPolicy.get() == ScrollPane.ScrollBarPolicy.ALWAYS) { return true; } return contentHeightProperty().get() > getHeight() || contentWidthProperty().get() > getWidth() && contentHeightProperty().get() > getHeight() - horizontalScrollBar.getHeight(); }, scrollBarPolicy, contentHeightProperty(), contentWidthProperty(), heightProperty(), widthProperty(), verticalScrollBar.prefWidthProperty() ); scrollBar.visibleProperty().bind(visibilityBinding); colConstraints.prefWidthProperty().bind( CustomBinding.convert(visibilityBinding, b -> b ? ScrollBar.USE_COMPUTED_SIZE : 0)); } private void initLayout() { // - Create the sub-scene pane. content = new Pane(); content.setManaged(false); subScene.setRoot(content); Rectangle clipRect = new Rectangle(); clipRect.widthProperty().bind(viewportWidthProperty()); clipRect.heightProperty().bind(viewportHeightProperty()); viewportPane.setClip(clipRect); // - Make all panes transparent. viewportPane.setBackground(null); background.setBackground(null); foreground.setBackground(null); content.setBackground(null); // - The call to subScene.setRoot() changed the style class of the // content pane to "root". We do not want this, because then the // scene stylesheet will assign a background color to it! content.getStyleClass().clear(); } private void onScrollEvent(ScrollEvent event, ScrollBar scrollBar, double delta) { double min = scrollBar.getMin(), max = scrollBar.getMax(), value = scrollBar.getValue(), visible = scrollBar.getVisibleAmount(); // we only consume if we can scroll if (visible < max - min) { scrollBar.setValue(MathUtil.clamp(value - delta, min, max)); event.consume(); } } private static DoubleBinding createContentRectTranslateBinding(ScrollBar scrollBar) { return CustomBinding.computeDouble( () -> getScrollBarPosition(scrollBar), scrollBar.valueProperty(), scrollBar.minProperty(), scrollBar.maxProperty(), scrollBar.visibleAmountProperty()); } public static @Nullable URL getFxmlResource() { return ZoomableScrollPane.class.getResource("/org/jhotdraw8/draw/gui/ZoomableScrollPane.fxml"); } public final ReadOnlyDoubleProperty viewportWidthProperty() { return viewportPane.widthProperty(); } public final ReadOnlyDoubleProperty viewportHeightProperty() { return viewportPane.heightProperty(); } public final DoubleProperty zoomFactorProperty() { return zoomFactor; } public double getZoomFactor() { return zoomFactor.get(); } public final ReadOnlyDoubleProperty viewWidthProperty() { return horizontalScrollBar.maxProperty(); } public final ReadOnlyDoubleProperty viewHeightProperty() { return verticalScrollBar.maxProperty(); } public double getViewportWidth() { return viewportWidthProperty().getValue(); } public double getViewportHeight() { return viewportHeightProperty().getValue(); } public ObservableList getContentChildren() { return content.getChildren(); } public ObservableList getBackgroundChildren() { return background.getChildren(); } public ObservableList getForegroundChildren() { return foreground.getChildren(); } /** * Returns the rectangle of the content which is currently visible in the * viewport in content coordinates. * * @return visible content rectangle in content coordinates */ public Bounds getVisibleContentRect() { return visibleContentRect.get(); } public Bounds getViewRect() { return getContentToView().transform(getVisibleContentRect()); } /** * Gets the position of the scrollbar. * * @param sb a scrollbar * @return the position of the scrollbar */ private static double getScrollBarPosition(ScrollBar sb) { double value = sb.getValue(), min = sb.getMin(), max = sb.getMax(), visible = sb.getVisibleAmount(); if (visible > max) { return -Math.round((visible - max) * 0.5); } return MathUtil.clamp(Math.round((max - min - visible) * (value - min) / (max - min)) + min, min, max); } public Bounds getViewportRect() { return viewportPane.getBoundsInParent(); } public ReadOnlyObjectProperty visibleContentRectProperty() { return visibleContentRect; } @FXML // fx:id="viewportPane" private Pane viewportPane; // Value injected by FXMLLoader public void setZoomFactor(double newValue) { zoomFactor.set(newValue); } public final Bounds getViewportBounds() { return viewportPane.getLayoutBounds(); } public void scrollViewRectToVisible(Bounds b) { scrollContentRectToVisible(getViewToContent().transform(b)); } public void scrollViewRectToVisible(double x, double y, double w, double h) { scrollViewRectToVisible(new BoundingBox(x, y, w, h)); } public void scrollContentRectToVisible(double x, double y, double w, double h) { double sf = getZoomFactor(); final double hmin = horizontalScrollBar.getMin(), hmax = horizontalScrollBar.getMax(), hvisible = horizontalScrollBar.getVisibleAmount(), vmin = verticalScrollBar.getMin(), vmax = verticalScrollBar.getMax(), vvisible = verticalScrollBar.getVisibleAmount(); final double cx = x * sf + (w * sf - hvisible) * 0.5, cy = y * sf + (h * sf - vvisible) * 0.5, hvalue = cx * (hmax - hmin) / (hmax - hvisible) + hmin, vvalue = cy * (vmax - vmin) / (vmax - vvisible) + vmin; horizontalScrollBar.setValue(MathUtil.clamp(hvalue, horizontalScrollBar.getMin(), horizontalScrollBar.getMax())); verticalScrollBar.setValue(MathUtil.clamp(vvalue, verticalScrollBar.getMin(), verticalScrollBar.getMax())); } public void scrollContentRectToVisible(Bounds boundsInWorld) { scrollContentRectToVisible(boundsInWorld.getMinX(), boundsInWorld.getMinY(), boundsInWorld.getWidth(), boundsInWorld.getHeight()); } public Transform getContentToView() { return contentToViewProperty().getValue(); } public Transform getViewToContent() { try { return getContentToView().createInverse(); } catch (NonInvertibleTransformException e) { Logger.getLogger(getClass().getName()).log(Level.WARNING, "Unexpected Exception " + e.getMessage(), e); return FXTransforms.IDENTITY; } } private final Property contentToView = new SimpleObjectProperty<>(this, "contentToView"); public ReadOnlyProperty contentToViewProperty() { return contentToView; } public DoubleProperty contentWidthProperty() { return content.prefWidthProperty(); } public DoubleProperty contentHeightProperty() { return content.prefHeightProperty(); } public void setContentSize(double w, double h) { setContentWidth(w); setContentHeight(h); } public void setContentWidth(double w) { contentWidthProperty().set(w); } public void setContentHeight(double w) { contentHeightProperty().set(w); } public double getContentWidth() { return contentWidthProperty().get(); } public double getContentHeight() { return contentHeightProperty().get(); } public String getSubSceneUserAgentStylesheet() { return subScene.getUserAgentStylesheet(); } public void setSubSceneUserAgentStylesheet(String newValue) { subScene.setUserAgentStylesheet(newValue); } public ObjectProperty subSceneUserAgentStylesheetProperty() { return subScene.userAgentStylesheetProperty(); } public Node getNode() { return this; } public static ZoomableScrollPane create() { FXMLLoader loader = new FXMLLoader(); final ZoomableScrollPane controller = new ZoomableScrollPane(); loader.setLocation(ZoomableScrollPane.getFxmlResource()); loader.setResources(null); loader.setController(controller); loader.setRoot(controller); try { loader.load(); } catch (IOException e) { throw new UncheckedIOException(e); } return loader.getController(); } public ReadOnlyDoubleProperty viewRectWidthProperty() { return horizontalScrollBar.visibleAmountProperty(); } public ReadOnlyDoubleProperty viewRectHeightProperty() { return verticalScrollBar.visibleAmountProperty(); } private ObjectProperty hbarPolicy; public final void setHbarPolicy(ScrollPane.ScrollBarPolicy value) { hbarPolicyProperty().set(value); } public final ScrollPane.ScrollBarPolicy getHbarPolicy() { return hbarPolicy == null ? ScrollPane.ScrollBarPolicy.AS_NEEDED : hbarPolicy.get(); } public final ObjectProperty hbarPolicyProperty() { if (hbarPolicy == null) { hbarPolicy = new StyleableObjectProperty<>(ScrollPane.ScrollBarPolicy.AS_NEEDED) { @Override public CssMetaData getCssMetaData() { return ZoomableScrollPane.StyleableProperties.HBAR_POLICY; } @Override public Object getBean() { return ZoomableScrollPane.this; } @Override public String getName() { return "hbarPolicy"; } }; } return hbarPolicy; } /** * Specifies the policy for showing the vertical scroll bar. */ private ObjectProperty vbarPolicy; public final void setVbarPolicy(ScrollPane.ScrollBarPolicy value) { vbarPolicyProperty().set(value); } public final ScrollPane.ScrollBarPolicy getVbarPolicy() { return vbarPolicy == null ? ScrollPane.ScrollBarPolicy.AS_NEEDED : vbarPolicy.get(); } public final ObjectProperty vbarPolicyProperty() { if (vbarPolicy == null) { vbarPolicy = new StyleableObjectProperty<>(ScrollPane.ScrollBarPolicy.AS_NEEDED) { @Override public CssMetaData getCssMetaData() { return ZoomableScrollPane.StyleableProperties.VBAR_POLICY; } @Override public Object getBean() { return ZoomableScrollPane.this; } @Override public String getName() { return "vbarPolicy"; } }; } return vbarPolicy; } /** * Specifies whether the user should be able to pan the viewport by using * the mouse. If mouse events reach the ZoomableScrollPane (that is, if mouse * events are not blocked by the contained node or one of its children) * then {@link #pannableProperty pannable} is consulted to determine if the events should be * used for panning. */ private StyleableBooleanProperty pannable; public final void setPannable(boolean value) { pannableProperty().set(value); } public final boolean isPannable() { return pannable != null && pannable.get(); } public final StyleableBooleanProperty pannableProperty() { if (pannable == null) { pannable = new StyleableBooleanProperty(false) { @Override public void invalidated() { pseudoClassStateChanged(PANNABLE_PSEUDOCLASS_STATE, get()); } @Override public CssMetaData getCssMetaData() { return ZoomableScrollPane.StyleableProperties.PANNABLE; } @Override public Object getBean() { return ZoomableScrollPane.this; } @Override public String getName() { return "pannable"; } }; } return pannable; } private static final PseudoClass PANNABLE_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("pannable"); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy