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

io.github.palexdev.virtualizedfx.flow.base.OrientationHelper Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2021 Parisi Alessandro
 * 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.flow.base;

import io.github.palexdev.virtualizedfx.flow.simple.SimpleVirtualFlowContainer;
import io.github.palexdev.virtualizedfx.utils.NumberUtils;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.Region;
import org.glavo.materialfx.adapter.skin.SnapUtils;

/**
 * Helper class to avoid as much as possible if/else statements to check
 * the VirtualFlow orientation.
 */
public interface OrientationHelper {

    //================================================================================
    // Layout
    //================================================================================

    /**
     * @return the index of the first visible cell in the viewport
     */
    int firstVisible();

    /**
     * @return the index of the last visible cell in the viewport
     */
    int lastVisible();

    /**
     * @return the number of cells to show
     */
    int computeCellsNumber();

    /**
     * Computes the estimated height of the flow.
     */
    double computeEstimatedHeight(double cellHeight);

    /**
     * Computes the estimated width of the flow.
     */
    double computeEstimatedWidth(double cellWidth);

    /**
     * @return the height of the given node
     */
    double getHeight(Node node);

    /**
     * @return the width of the given node
     */
    double getWidth(Node node);

    /**
     * Resizes and relocates the given node with the given parameters.
     */
    void resizeRelocate(Node node, double pos, double w, double h);

    /**
     * Lays out the given node.
     */
    void layout(Node node, int index, double cellW, double cellH);

    /**
     * Computes size of a Node between its pref, min and max sizes.
     * 

* The formula is: *

*

     * {@code
     *     double a = Math.max(pref, min);
     *     double b = Math.max(min, max);
     *     return Math.min(a, b);
     * }
     * 
*

*/ static double boundSize(double pref, double min, double max) { return NumberUtils.clamp(pref, min, max); } static double boundWidth(Node node) { return boundSize(node.prefWidth(-1), node.minWidth(-1), node.maxWidth(-1)); } static double boundHeight(Node node) { return boundSize(node.prefHeight(-1), node.minHeight(-1), node.maxHeight(-1)); } //================================================================================ // Scrolling //================================================================================ /** * Scrolls the flow by the given amount of pixels. */ void scrollBy(double pixels); /** * Scrolls the flow to the given cell index. */ void scrollTo(int index); /** * Scrolls the flow to the first cell. */ void scrollToFirst(); /** * Scrolls the flow to the last cell. */ void scrollToLast(); /** * Scrolls the flow to the given pixel value. */ void scrollToPixel(double pixel); //================================================================================ // Others //================================================================================ /** * Removes and clears any listener. *

* This must be called every time the OrientationHelper of the VirtualFlow * is changed (typically occurs when the orientation changes). */ void dispose(); //================================================================================ // Implementations //================================================================================ /** * Implementation of {@link OrientationHelper} for {@link Orientation#HORIZONTAL}. *

* This helper is also responsible for listening to changes to the VirtualFlow's width, * because in such cases it's needed to recompute the number of cells needed (to add or remove), * and changes to the {@link VirtualFlow#horizontalPositionProperty()} to update the container's layoutX * property and call {@link SimpleVirtualFlowContainer#update(double)} with the new position. *

* Note that these listeners are added late during VirtualFlow's initialization as adding them already in the constructor * would lead to an infinite loop and RAM eating for some reason. * * @see OrientationHelper */ class HorizontalHelper implements OrientationHelper { //================================================================================ // Properties //================================================================================ private final VirtualFlow virtualFlow; private final SimpleVirtualFlowContainer container; private ChangeListener widthChanged; private ChangeListener heightChanged; private ChangeListener vPosChanged; private ChangeListener hPosChanged; //================================================================================ // Constructors //================================================================================ public HorizontalHelper(VirtualFlow virtualFlow, SimpleVirtualFlowContainer container) { this.virtualFlow = virtualFlow; this.container = container; this.widthChanged = (observable, oldValue, newValue) -> container.reset(); this.heightChanged = (observable, oldValue, newValue) -> { container.recomputeCellsSize(); container.requestCellsLayout(); }; this.vPosChanged = (observable, oldValue, newValue) -> container.setLayoutY(-newValue.floatValue() % container.getCellHeight()); this.hPosChanged = (observable, oldValue, newValue) -> { container.setLayoutX(-newValue.doubleValue() % container.getCellWidth()); container.update(newValue.doubleValue()); }; } //================================================================================ // Initialization //================================================================================ // TODO investigate more /** * DO NOT CALL THIS METHOD, it's automatically handled by the VirtualFlow. */ public void initialize() { virtualFlow.getVirtualFlow().widthProperty().addListener(widthChanged); virtualFlow.getVirtualFlow().heightProperty().addListener(heightChanged); virtualFlow.verticalPositionProperty().addListener(vPosChanged); virtualFlow.horizontalPositionProperty().addListener(hPosChanged); } //================================================================================ // Implemented Methods //================================================================================ /** * Computes the first visible cell index in the viewport with this formula: *

* {@code (int) Math.floor(scrolled / cellWidth)} *

* The scrolled parameter is the amount of pixels scrolled from the left * and cellWidth is the fixed cells' width. *

* The result is clamped between 0 and the number of items -1. */ @Override public int firstVisible() { return NumberUtils.clamp( (int) Math.floor(container.getScrolled() / container.getCellWidth()), 0, virtualFlow.getItems().size() - 1 ); } /** * Computes the first visible cell index in the viewport with this formula: *

* {@code (int) Math.ceil((scrolled + virtualFlowWidth) / cellWidth - 1)} *

* The scrolled parameter is the amount of pixels scrolled from the left, * the virtualFlowWidth well... is self-explanatory, and cellWidth is the fixed cells' width. *

* The result is clamped between 0 and the number of items -1. */ @Override public int lastVisible() { return NumberUtils.clamp( (int) Math.ceil((container.getScrolled() + virtualFlow.getVirtualFlow().getWidth()) / container.getCellWidth() - 1), 0, virtualFlow.getItems().size() - 1 ); } /** * @return the number of cells to show based on the viewport's width and cells' width */ @Override public int computeCellsNumber() { return (int) Math.ceil(virtualFlow.getVirtualFlow().getWidth() / container.getCellWidth()) + 1; } /** * @return the VirtualFlow's height */ @Override public double computeEstimatedHeight(double cellHeight) { Region vf = virtualFlow.getVirtualFlow(); if (virtualFlow.isFitToHeight()) { return vf.getHeight(); } return Math.max(vf.getHeight(), container.getHeight()); } /** * @return the given cellWidth multiplied by the number of items */ @Override public double computeEstimatedWidth(double cellWidth) { return virtualFlow.getItems().size() * cellWidth; } /** * If the Node's maxHeight is set to {@link Double#MAX_VALUE} then returns * the VirtualFlow's height, otherwise calls {@link #boundSize(double, double, double)}. */ @Override public double getHeight(Node node) { Region vf = virtualFlow.getVirtualFlow(); double max = node.maxHeight(-1); double pref = node.prefHeight(-1); return virtualFlow.isFitToHeight() ? max == Double.MAX_VALUE ? vf.getHeight() : boundHeight(node) : Math.max(pref, container.getHeight()); } /** * @return the Node's pref width */ @Override public double getWidth(Node node) { return node.prefWidth(-1); } /** * Resizes and relocated the given node with the given parameters, * the y offset is always 0. * * @param node the Node to resize and relocate * @param pos the x position * @param w the width * @param h the height */ @Override public void resizeRelocate(Node node, double pos, double w, double h) { node.resizeRelocate(pos, 0, w, h); } /** * Computes the Node's x position and then calls {@link #resizeRelocate(Node, double, double, double)}. */ @Override public void layout(Node node, int index, double cellW, double cellH) { double pos = SnapUtils.snapPositionX(container, cellW * index); resizeRelocate(node, pos, cellW, cellH); } /** * {@inheritDoc} *

* Gets the current horizontal position and adds the given amount of * pixels to it. The result is clamped between 0 and the hBar's max property. */ @Override public void scrollBy(double pixels) { ScrollBar hBar = virtualFlow.getHBar(); double currVal = hBar.getValue(); double newVal = NumberUtils.clamp(currVal + pixels, 0, hBar.getMax()); virtualFlow.setHorizontalPosition(newVal); } /** * {@inheritDoc} *

* Computes the pixel value by multiplying the given index for the fixed cells' width, * then calls {@link #scrollToPixel(double)} with the result. */ @Override public void scrollTo(int index) { scrollToPixel(container.getCellWidth() * index); } /** * Calls {@link #scrollTo(int)} with 0 as argument. */ @Override public void scrollToFirst() { scrollTo(0); } /** * Calls {@link #scrollTo(int)} with items size - 1 as argument. */ @Override public void scrollToLast() { scrollTo(virtualFlow.getItems().size() - 1); } /** * {@inheritDoc} *

* The given pixel value is clamped between 0 and the hBar's max property before. */ @Override public void scrollToPixel(double pixel) { ScrollBar hBar = virtualFlow.getHBar(); double val = NumberUtils.clamp(pixel, 0, hBar.getMax()); virtualFlow.setHorizontalPosition(val); } /** * {@inheritDoc} */ @Override public void dispose() { virtualFlow.getVirtualFlow().widthProperty().removeListener(widthChanged); virtualFlow.getVirtualFlow().heightProperty().removeListener(heightChanged); virtualFlow.verticalPositionProperty().removeListener(vPosChanged); virtualFlow.horizontalPositionProperty().removeListener(hPosChanged); widthChanged = null; heightChanged = null; vPosChanged = null; hPosChanged = null; } } /** * Implementation of {@link OrientationHelper} for {@link Orientation#VERTICAL}. *

* This helper is also responsible for listening to changes to the VirtualFlow's height, * because in such cases it's needed to recompute the number of cells needed (to add or remove), * and changes to the {@link VirtualFlow#verticalPositionProperty()} to update the container's layoutY * property and call {@link SimpleVirtualFlowContainer#update(double)} with the new position. *

* Note that these listeners are added late during VirtualFlow's initialization as adding them already in the constructor * would lead to an infinite loop and RAM eating for some reason. * * @see OrientationHelper */ class VerticalHelper implements OrientationHelper { //================================================================================ // Properties //================================================================================ private final VirtualFlow virtualFlow; private final SimpleVirtualFlowContainer container; private ChangeListener widthChanged; private ChangeListener heightChanged; private ChangeListener vPosChanged; private ChangeListener hPosChanged; //================================================================================ // Constructors //================================================================================ public VerticalHelper(VirtualFlow virtualFlow, SimpleVirtualFlowContainer container) { this.virtualFlow = virtualFlow; this.container = container; this.widthChanged = (observable, oldValue, newValue) -> { container.recomputeCellsSize(); container.requestCellsLayout(); }; this.heightChanged = (observable, oldValue, newValue) -> container.reset(); this.vPosChanged = (observable, oldValue, newValue) -> { container.setLayoutY(-newValue.doubleValue() % container.getCellHeight()); container.update(newValue.doubleValue()); }; this.hPosChanged = ((observable, oldValue, newValue) -> container.setLayoutX(-newValue.doubleValue() % container.getCellWidth())); } //================================================================================ // Initialization //================================================================================ // TODO investigate more /** * DO NOT CALL THIS METHOD, it's automatically handled by the VirtualFlow. */ public void initialize() { virtualFlow.getVirtualFlow().widthProperty().addListener(widthChanged); virtualFlow.getVirtualFlow().heightProperty().addListener(heightChanged); virtualFlow.verticalPositionProperty().addListener(vPosChanged); virtualFlow.horizontalPositionProperty().addListener(hPosChanged); } //================================================================================ // Implemented Methods //================================================================================ /** * Computes the first visible cell index in the viewport with this formula: *

* {@code (int) Math.floor(scrolled / cellHeight)} *

* The scrolled parameter is the amount of pixels scrolled from the top * and cellHeight is the fixed cells' height. *

* The result is clamped between 0 and the number of items -1. */ @Override public int firstVisible() { return NumberUtils.clamp( (int) Math.floor(container.getScrolled() / container.getCellHeight()), 0, virtualFlow.getItems().size() - 1 ); } /** * Computes the first visible cell index in the viewport with this formula: *

* {@code (int) Math.ceil((scrolled + virtualFlowHeight) / cellHeight - 1)} *

* The scrolled parameter is the amount of pixels scrolled from the top, * the virtualFlowHeight well... is self-explanatory, and cellHeight is the fixed cells' height. *

* The result is clamped between 0 and the number of items -1. */ @Override public int lastVisible() { return NumberUtils.clamp( (int) Math.ceil((container.getScrolled() + virtualFlow.getVirtualFlow().getHeight()) / container.getCellHeight() - 1), 0, virtualFlow.getItems().size() - 1 ); } /** * @return the number of cells to show based on the viewport's height and cells' height */ @Override public int computeCellsNumber() { return (int) Math.ceil(virtualFlow.getVirtualFlow().getHeight() / container.getCellHeight()) + 1; } /** * @return the given cellHeight multiplied by the number of items */ @Override public double computeEstimatedHeight(double cellHeight) { return virtualFlow.getItems().size() * cellHeight; } /** * @return the VirtualFlow's width */ @Override public double computeEstimatedWidth(double cellWidth) { Region vf = virtualFlow.getVirtualFlow(); if (virtualFlow.isFitToWidth()) { return vf.getWidth(); } return Math.max(vf.getWidth(), container.getWidth()); } /** * @return the Node's pref height */ @Override public double getHeight(Node node) { return node.prefHeight(-1); } /** * If the Node's maxWidth is set to {@link Double#MAX_VALUE} then returns * the VirtualFlow's width, otherwise calls {@link #boundSize(double, double, double)}. */ @Override public double getWidth(Node node) { Region vf = virtualFlow.getVirtualFlow(); double max = node.maxWidth(-1); double pref = node.prefWidth(-1); return virtualFlow.isFitToWidth() ? max == Double.MAX_VALUE ? vf.getWidth() : boundWidth(node) : Math.max(pref, container.getWidth()); } /** * Resizes and relocated the given node with the given parameters, * the x offset is always 0. * * @param node the Node to resize and relocate * @param pos the y position * @param w the width * @param h the height */ @Override public void resizeRelocate(Node node, double pos, double w, double h) { node.resizeRelocate(0, pos, w, h); } /** * Computes the Node's y position and then calls {@link #resizeRelocate(Node, double, double, double)}. */ @Override public void layout(Node node, int index, double cellW, double cellH) { double pos = SnapUtils.snapPositionY(container, cellH * index); resizeRelocate(node, pos, cellW, cellH); } /** * {@inheritDoc} *

* Gets the current vertical position and adds the given amount of * pixels to it. The result is clamped between 0 and the vBar's max property. */ @Override public void scrollBy(double pixels) { ScrollBar vBar = virtualFlow.getVBar(); double currVal = vBar.getValue(); double newVal = NumberUtils.clamp(currVal + pixels, 0, vBar.getMax()); virtualFlow.setVerticalPosition(newVal); } /** * {@inheritDoc} *

* Computes the pixel value by multiplying the given index for the fixed cells' height, * then calls {@link #scrollToPixel(double)} with the result. */ @Override public void scrollTo(int index) { scrollToPixel(container.getCellHeight() * index); } /** * Calls {@link #scrollTo(int)} with 0 as argument. */ @Override public void scrollToFirst() { scrollTo(0); } /** * Calls {@link #scrollTo(int)} with items size - 1 as argument. */ @Override public void scrollToLast() { scrollTo(virtualFlow.getItems().size() - 1); } /** * {@inheritDoc} *

* The given pixel value is clamped between 0 and the vBar's max property before. */ @Override public void scrollToPixel(double pixel) { ScrollBar vBar = virtualFlow.getVBar(); double val = NumberUtils.clamp(pixel, 0, vBar.getMax()); virtualFlow.setVerticalPosition(val); } /** * {@inheritDoc} */ @Override public void dispose() { virtualFlow.getVirtualFlow().widthProperty().removeListener(widthChanged); virtualFlow.getVirtualFlow().heightProperty().removeListener(heightChanged); virtualFlow.verticalPositionProperty().removeListener(vPosChanged); virtualFlow.horizontalPositionProperty().removeListener(hPosChanged); widthChanged = null; heightChanged = null; vPosChanged = null; hPosChanged = null; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy