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

io.github.palexdev.virtualizedfx.controls.VFXScrollBar Maven / Gradle / Ivy

There is a newer version: 21.6.4
Show newest version
/*
 * Copyright (C) 2024 Parisi Alessandro - [email protected]
 * This file is part of VirtualizedFX (https://github.com/palexdev/VirtualizedFX)
 *
 * VirtualizedFX is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 3 of the License,
 * or (at your option) any later version.
 *
 * VirtualizedFX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with VirtualizedFX. If not, see .
 */

package io.github.palexdev.virtualizedfx.controls;

import java.util.List;
import java.util.function.Supplier;

import io.github.palexdev.mfxcore.base.properties.styleable.StyleableBooleanProperty;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableDoubleProperty;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableObjectProperty;
import io.github.palexdev.mfxcore.builders.bindings.ObjectBindingBuilder;
import io.github.palexdev.mfxcore.controls.Control;
import io.github.palexdev.mfxcore.controls.SkinBase;
import io.github.palexdev.mfxcore.utils.NumberUtils;
import io.github.palexdev.mfxcore.utils.fx.ScrollUtils.ScrollDirection;
import io.github.palexdev.mfxcore.utils.fx.StyleUtils;
import io.github.palexdev.virtualizedfx.VFXResources;
import io.github.palexdev.virtualizedfx.base.VFXContainer;
import io.github.palexdev.virtualizedfx.base.VFXStyleable;
import io.github.palexdev.virtualizedfx.controls.behaviors.VFXScrollBarBehavior;
import io.github.palexdev.virtualizedfx.controls.skins.VFXScrollBarSkin;
import io.github.palexdev.virtualizedfx.utils.ScrollBounds;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.Styleable;
import javafx.css.StyleablePropertyFactory;
import javafx.geometry.Bounds;
import javafx.geometry.Orientation;
import javafx.scene.Node;

/**
 * My personal custom implementation of a scroll bar from scratch, follows the MVC pattern as enforced by {@link Control}.
 * The default skin is {@link VFXScrollBarSkin}. The default behavior is {@link VFXScrollBarBehavior}. Also implements {@link VFXStyleable}.
 * 

* VirtualizedFX offers a default stylesheet for this control 'VFXScrollBar.css', but it's not added automatically. * You can load it by using {@link VFXResources#loadResource(String)}. *

* In addition to an appealing style, the component offers many new features compared to the boring standard * JavaFX' scroll bar, such as: *

- the possibility of showing/hiding the increase and decrease buttons, {@link #showButtonsProperty()} *

- the possibility of controlling the hap between the buttons and the thumb, {@link #buttonsGapProperty()} *

- inbuilt smooth scroll both for the thumb and the track, {@link #smoothScrollProperty()}, {@link #trackSmoothScrollProperty()} *

- the possibility of querying the scroll direction, {@link #scrollDirectionProperty()} *

* Three {@link PseudoClass} worth mentioning: *

- ":dragging": active when the thumb is being dragged *

- ":horizontal": active when the scroll bar's orientation is HORIZONTAL *

- ":vertical": active when the scroll bar's orientation is VERTICAL *

* How VFXScrollBar is designed *

* The following details are important to understand what to expect and how to correctly set up and use this component. *

* Just like the JavaFX's implementation, this component has three properties which determine the scroll value: *

- the {@link #minProperty()} can be used as the lower bound to limit the {@link #valueProperty()} *

- the {@link #maxProperty()} can be used as the upper bound to limit the {@link #valueProperty()} *

- the {@link #valueProperty()} specifies the scroll position *

* The main difference is that {@code VFXScrollBar} does not use percentage values but expects the actual number of pixels. * Also, for the same reason, there's another big difference here. This implementation expects you to specify both the * bounds of the content and the view area by setting the {@link #scrollBoundsProperty()}. *

* While it's true that this makes the component less 'modular', and it's debatable whether this is or it is not a * responsibility of the scroll bar; but it's also true that it makes the implementation much easier and straightforward. *

* Examples *

 * {@code
 * // Example with VFXContainer
 * VFXContainer container = ...;
 * VFXScrollBar vBar = new VFXScrollBar();
 * vBar.bindTo(container, true); // Super easy
 * // Other options
 * // Same applied for hBar
 *
 *
 * // Example with generic Nodes
 * Node content = ...;
 * VBox box = new VBox(content);
 * // Limit the box to not grow infinitely
 * VFXScrollBar vBar = new VFXScrollBar();
 * vBar.scrollBoundsProperty().bind(Bindings.createObjectBinding(
 *     () -> new ScrollBounds(
 *             content.getLayoutBounds().getWidth(),
 *             content.getLayoutBounds().getHeight(),
 *             box.getWidth(),
 *             box.getHeight())
 * ), content.layoutBoundsProperty(), box.layoutBoundsProperty())
 * // Translate Y the content
 * // Other options
 * // Same applied for hBar
 * }
 * 
*/ public class VFXScrollBar extends Control implements VFXStyleable { //================================================================================ // PseudoClasses //================================================================================ public static final PseudoClass DRAGGING_PSEUDO_CLASS = PseudoClass.getPseudoClass("dragging"); public static final PseudoClass VERTICAL_PSEUDO_CLASS = PseudoClass.getPseudoClass("vertical"); public static final PseudoClass HORIZONTAL_PSEUDO_CLASS = PseudoClass.getPseudoClass("horizontal"); //================================================================================ // Properties //================================================================================ private final DoubleProperty min = new SimpleDoubleProperty(0.0) { @Override protected void invalidated() { invalidateValue(); } }; private final DoubleProperty value = new SimpleDoubleProperty() { @Override public void set(double newValue) { super.set(NumberUtils.clamp( newValue, Math.max(0.0, getMin()), Math.min(getMaxScroll(), getMax()) )); } }; private final DoubleProperty max = new SimpleDoubleProperty(Double.MAX_VALUE) { @Override protected void invalidated() { invalidateValue(); } }; private final ObjectProperty scrollBounds = new SimpleObjectProperty<>(ScrollBounds.ZERO) { @Override public void set(ScrollBounds newValue) { if (newValue == null) newValue = ScrollBounds.ZERO; super.set(newValue); } @Override protected void invalidated() { VFXScrollBar.this.setValue(VFXScrollBar.this.getValue()); } }; private final ObjectProperty scrollDirection = new SimpleObjectProperty<>(); //================================================================================ // Constructors //================================================================================ public VFXScrollBar() { this(Orientation.VERTICAL); } public VFXScrollBar(Orientation orientation) { setOrientation(orientation); initialize(); } //================================================================================ // Methods //================================================================================ private void initialize() { getStyleClass().setAll(defaultStyleClasses()); setDefaultBehaviorProvider(); } /** * Binds the {@link #scrollBoundsProperty()} to the given node implementing {@link VFXContainer} by using: * {@link VFXContainer#virtualMaxXProperty()} and {@link VFXContainer#virtualMaxXProperty()} as the content bounds; * {@link Bounds#getWidth()} and {@link Bounds#getHeight()} as the view bounds. *

* If the {@code bindPos} parameter is true then it also bidirectionally binds the properties: * {@link #valueProperty()} and {@link VFXContainer#vPosProperty()} or {@link VFXContainer#hPosProperty()} depending * on the {@link #orientationProperty()}. * * @param any {@link Node} which implements {@link VFXContainer} */ public > VFXScrollBar bindTo(N container, boolean bindPos) { scrollBounds.bind(ObjectBindingBuilder.build() .setMapper(() -> new ScrollBounds( container.getVirtualMaxX(), container.getVirtualMaxY(), container.getLayoutBounds().getWidth(), container.getLayoutBounds().getHeight() )) .addSources(container.virtualMaxXProperty(), container.virtualMaxYProperty()) .addSources(container.layoutBoundsProperty()) .get() ); if (bindPos) { DoubleProperty prop = (getOrientation() == Orientation.VERTICAL) ? container.vPosProperty() : container.hPosProperty(); valueProperty().bindBidirectional(prop); } return this; } /** * @return whether the thumb is being dragged */ public boolean isDragging() { return getPseudoClassStates().contains(DRAGGING_PSEUDO_CLASS); } /** * Shortcut for {@code setValue(getValue())}. This will cause the {@link #valueProperty()} to be withing the bounds * specified by the {@link #minProperty()} and the {@link #maxProperty()}. */ protected void invalidateValue() { setValue(getValue()); } //================================================================================ // Delegate Methods //================================================================================ /** * Delegate for {@link ScrollBounds#visibleAmount(Orientation)} * * @see ScrollBounds */ public double getVisibleAmount() { return getScrollBounds().visibleAmount(getOrientation()); } /** * Delegate for {@link ScrollBounds#maxScroll(Orientation)} * * @see ScrollBounds */ public double getMaxScroll() { return getScrollBounds().maxScroll(getOrientation()); } //================================================================================ // Overridden Methods //================================================================================ @Override public Supplier defaultBehaviorProvider() { return () -> new VFXScrollBarBehavior(this); } @Override protected SkinBase buildSkin() { return new VFXScrollBarSkin(this); } @Override public List defaultStyleClasses() { return List.of("vfx-scroll-bar"); } //================================================================================ // Styleable Properties //================================================================================ private final StyleableBooleanProperty showButtons = new StyleableBooleanProperty( StyleableProperties.SHOW_BUTTONS, this, "showButtons", false ); private final StyleableDoubleProperty buttonsGap = new StyleableDoubleProperty( StyleableProperties.BUTTONS_GAP, this, "buttonsGap", 1.0 ); private final StyleableObjectProperty orientation = new StyleableObjectProperty<>( StyleableProperties.ORIENTATION, this, "orientation", Orientation.VERTICAL ); private final StyleableDoubleProperty trackIncrement = new StyleableDoubleProperty( StyleableProperties.TRACK_INCREMENT, this, "trackIncrement", 25.0 ); private final StyleableDoubleProperty unitIncrement = new StyleableDoubleProperty( StyleableProperties.UNIT_INCREMENT, this, "unitIncrement", 10.0 ); private final StyleableBooleanProperty smoothScroll = new StyleableBooleanProperty( StyleableProperties.SMOOTH_SCROLL, this, "smoothScroll", false ); private final StyleableBooleanProperty trackSmoothScroll = new StyleableBooleanProperty( StyleableProperties.TRACK_SMOOTH_SCROLL, this, "trackSmoothScroll", false ); public boolean isShowButtons() { return showButtons.get(); } /** * Specifies whether the increase/decrease buttons are visible. *

* This is also settable via CSS with the "-vfx-show-buttons" property. */ public StyleableBooleanProperty showButtonsProperty() { return showButtons; } public void setShowButtons(boolean showButtons) { this.showButtons.set(showButtons); } public double getButtonsGap() { return buttonsGap.get(); } /** * Specifies the gap between the increase/decrease buttons and the thumb. *

* This is also settable via CSS with the "-vfx-buttons-gap" property. */ public StyleableDoubleProperty buttonsGapProperty() { return buttonsGap; } public void setButtonsGap(double buttonsGap) { this.buttonsGap.set(buttonsGap); } public Orientation getOrientation() { return orientation.get(); } /** * Specifies the scroll bar's orientation. *

* This is also settable via CSS with the "-vfx-orientation" property. */ public StyleableObjectProperty orientationProperty() { return orientation; } public void setOrientation(Orientation orientation) { this.orientation.set(orientation); } public double getTrackIncrement() { return trackIncrement.get(); } /** * Specifies the amount added/subtracted to the {@link #valueProperty()} used by the * scroll bar's track. *

* This is also settable via CSS with the "-vfx-track-increment" property. */ public StyleableDoubleProperty trackIncrementProperty() { return trackIncrement; } public void setTrackIncrement(double trackIncrement) { this.trackIncrement.set(trackIncrement); } public double getUnitIncrement() { return unitIncrement.get(); } /** * Specifies the amount added/subtracted to the {@link #valueProperty()} used by the * increment/decrement buttons and by scrolling. *

* This is also settable via CSS with the "-vfx-unit-increment" property. */ public StyleableDoubleProperty unitIncrementProperty() { return unitIncrement; } public void setUnitIncrement(double unitIncrement) { this.unitIncrement.set(unitIncrement); } public boolean isSmoothScroll() { return smoothScroll.get(); } /** * Specifies whether the scrolling should be smooth, done by animations. *

* This is also settable via CSS with the "-vfx-smooth-scroll" property. */ public StyleableBooleanProperty smoothScrollProperty() { return smoothScroll; } public void setSmoothScroll(boolean smoothScroll) { this.smoothScroll.set(smoothScroll); } public boolean isTrackSmoothScroll() { return trackSmoothScroll.get(); } /** * Specifies if the scrolling made by the track should be smooth, done by animations. *

* The default behavior considers this feature an addition to the {@link #smoothScrollProperty()}, * meaning that for this to work the aforementioned feature must be enabled too. *

* This is also settable via CSS with the "-vfx-track-smooth-scroll" property. */ public StyleableBooleanProperty trackSmoothScrollProperty() { return trackSmoothScroll; } public void setTrackSmoothScroll(boolean trackSmoothScroll) { this.trackSmoothScroll.set(trackSmoothScroll); } //================================================================================ // CssMetaData //================================================================================ private static class StyleableProperties { private static final StyleablePropertyFactory FACTORY = new StyleablePropertyFactory<>(Control.getClassCssMetaData()); private static final List> cssMetaDataList; private static final CssMetaData SHOW_BUTTONS = FACTORY.createBooleanCssMetaData( "-vfx-show-buttons", VFXScrollBar::showButtonsProperty, false ); private static final CssMetaData BUTTONS_GAP = FACTORY.createSizeCssMetaData( "-mfx-buttons-gap", VFXScrollBar::buttonsGapProperty, 1.0 ); private static final CssMetaData ORIENTATION = FACTORY.createEnumCssMetaData( Orientation.class, "-mfx-orientation", VFXScrollBar::orientationProperty, Orientation.VERTICAL ); private static final CssMetaData TRACK_INCREMENT = FACTORY.createSizeCssMetaData( "-mfx-track-increment", VFXScrollBar::trackIncrementProperty, 25.0 ); private static final CssMetaData UNIT_INCREMENT = FACTORY.createSizeCssMetaData( "-mfx-unit-increment", VFXScrollBar::unitIncrementProperty, 10.0 ); private static final CssMetaData SMOOTH_SCROLL = FACTORY.createBooleanCssMetaData( "-mfx-smooth-scroll", VFXScrollBar::smoothScrollProperty, false ); private static final CssMetaData TRACK_SMOOTH_SCROLL = FACTORY.createBooleanCssMetaData( "-mfx-track-smooth-scroll", VFXScrollBar::trackSmoothScrollProperty, false ); static { cssMetaDataList = StyleUtils.cssMetaDataList( Control.getClassCssMetaData(), SHOW_BUTTONS, BUTTONS_GAP, ORIENTATION, TRACK_INCREMENT, UNIT_INCREMENT, SMOOTH_SCROLL, TRACK_SMOOTH_SCROLL ); } } public static List> getClassCssMetaData() { return StyleableProperties.cssMetaDataList; } @Override protected List> getControlCssMetaData() { return getClassCssMetaData(); } //================================================================================ // Getters/Setters //================================================================================ public double getMin() { return min.get(); } /** * Specifies the minimum possible value for {@link #valueProperty()}. */ public DoubleProperty minProperty() { return min; } public void setMin(double min) { this.min.set(min); } public double getValue() { return value.get(); } /** * Specifies the scroll value, clamped between {@link #minProperty()} and {@link #maxProperty()}. */ public DoubleProperty valueProperty() { return value; } public void setValue(double value) { this.value.set(value); } /** * Increments the {@link #valueProperty()} by the amount specified by the {@link #unitIncrementProperty()}. */ public void uIncrement() { setValue(getValue() + getUnitIncrement()); } /** * Decrements the {@link #valueProperty()} by the amount specified by the {@link #unitIncrementProperty()}. */ public void uDecrement() { setValue(getValue() - getUnitIncrement()); } /** * Increments the {@link #valueProperty()} by the amount specified by the {@link #trackIncrementProperty()}. */ public void tIncrement() { setValue(getValue() + getTrackIncrement()); } /** * Decrements the {@link #valueProperty()} by the amount specified by the {@link #trackIncrementProperty()}. */ public void tDecrement() { setValue(getValue() - getTrackIncrement()); } public double getMax() { return max.get(); } /** * Specifies the maximum possible value for {@link #valueProperty()}. */ public DoubleProperty maxProperty() { return max; } public void setMax(double max) { this.max.set(max); } public ScrollBounds getScrollBounds() { return scrollBounds.get(); } /** * Specifies both the content's bounds and the view area's bounds. */ public ObjectProperty scrollBoundsProperty() { return scrollBounds; } public void setScrollBounds(ScrollBounds scrollBounds) { this.scrollBounds.set(scrollBounds); } public ScrollDirection getScrollDirection() { return scrollDirection.get(); } /** * Specifies the current scroll direction. The default behavior implementation manages this. * * @see VFXScrollBarBehavior */ public ObjectProperty scrollDirectionProperty() { return scrollDirection; } public void setScrollDirection(ScrollDirection scrollDirection) { this.scrollDirection.setValue(scrollDirection); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy