Package hs.javafx.control

Source Code of hs.javafx.control.AbstractCarouselSkin

package hs.javafx.control;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;

import javafx.animation.Transition;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.geometry.Point2D;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.transform.Translate;
import javafx.util.Callback;
import javafx.util.Duration;

import com.sun.javafx.scene.control.skin.SkinBase;

public abstract class AbstractCarouselSkin<T> extends SkinBase<Carousel<T>, CarouselBehavior<T>> {
  private final ArrayList<CarouselCell<T>> cells = new ArrayList<>();

  private double visibleCellsCount;

  protected double getVisibleCellsCount() {
    return visibleCellsCount;
  }


  private final Transition transition = new Transition() {
    {
      setCycleDuration(Duration.millis(500));
    }

    @Override
    protected void interpolate(double frac) {
      fractionalIndex = startFractionalIndex - startFractionalIndex * frac;

      sortChildren();
      doLayout();
    }
  };

  private void sortChildren() {

    /*
     * Update the cell indices.
     */

    int index = getSkinnable().getFocusModel().getFocusedIndex() - (int)Math.round(fractionalIndex);
    int visibleCellsCount = cells.size();
    int start = index - (visibleCellsCount - 1) / 2;
    int end = index + visibleCellsCount / 2;

    double opacity = ((fractionalIndex > 0 ? fractionalIndex : 1 + fractionalIndex % 1) + 0.5) % 1;

    for(int i = start; i <= end; i++) {
      CarouselCell<T> carouselCell = cells.get((i + visibleCellsCount) % visibleCellsCount);

      carouselCell.updateIndex(i);

      if(i == start) {
        carouselCell.setOpacity(opacity);
      }
      else if(i == end) {
        carouselCell.setOpacity(1.0 - opacity);
      }
      else {
        carouselCell.setOpacity(1.0);
      }
    }

    /*
     * Resort the children of the StackPane so the cells closest to center are on top.  A temporary list
     * is used to prevent events firing (and to avoid duplicate items in the Scene caused by the sorting steps).
     */
    // TODO perhaps this is possible to achieve with toFront() ?

    List<Node> temporaryList = new ArrayList<>(getChildren());
    Collections.sort(temporaryList, Z_ORDER_FRAC);
    getChildren().setAll(temporaryList);
  }

  private double startFractionalIndex;
  private double fractionalIndex;

  public AbstractCarouselSkin(final Carousel<T> carousel) {
    super(carousel, new CarouselBehavior<>(carousel));

    getStyleClass().setAll("scroll-area");

    InvalidationListener cellCountInvalidationListener = new InvalidationListener() {
      @Override
      public void invalidated(Observable observable) {
        allocateCells();
        sortChildren();
      }
    };

    carousel.widthProperty().addListener(cellCountInvalidationListener);
    carousel.densityProperty().addListener(cellCountInvalidationListener);

    allocateCells();

    carousel.getFocusModel().focusedIndexProperty().addListener(new ChangeListener<Number>() {
      @Override
      public void changed(ObservableValue<? extends Number> observableValue, Number old, Number current) {

        /*
         * Calculate at how many (fractional) items distance from the middle the carousel currently is and start the transistion that will
         * move the now focused cell to the middle.
         */

        startFractionalIndex = fractionalIndex - old.doubleValue() + current.doubleValue();
        transition.playFromStart();
      }
    });
  }

  // Goal: Spacings between Cells should remain similar when width changes
  private void allocateCells() {
    double widthFactor = getSkinnable().getDensity();

    visibleCellsCount = getSkinnable().getWidth() * widthFactor;
    visibleCellsCount = visibleCellsCount < 3 ? 3 : visibleCellsCount;

    int preferredCellCount = (int)visibleCellsCount;

    if(cells.size() > preferredCellCount) {
      List<CarouselCell<T>> cellsToBeDeleted = cells.subList(preferredCellCount, cells.size());

      for(CarouselCell<T> carouselCell : cellsToBeDeleted) {
        carouselCell.updateIndex(-1);
      }

      getChildren().removeAll(cellsToBeDeleted);
      cellsToBeDeleted.clear();
    }
    else if(cells.size() < preferredCellCount) {
      for(int i = cells.size(); i < preferredCellCount; i++) {
        CarouselCell<T> cell = createCell();

        cell.updateCarousel(getSkinnable());
        cell.updateIndex(i);

        cells.add(cell);
        getChildren().add(cell);
      }
    }
  }

  private final Comparator<Node> Z_ORDER_FRAC = new Comparator<Node>() {
    @Override
    public int compare(Node o1, Node o2) {
      CarouselCell<?> cell1 = (CarouselCell<?>)o1;
      CarouselCell<?> cell2 = (CarouselCell<?>)o2;

      int selectedIndex = getSkinnable().getFocusModel().getFocusedIndex();

      int dist1 = Math.abs(selectedIndex - cell1.getIndex() - (int)Math.round(fractionalIndex));
      int dist2 = Math.abs(selectedIndex - cell2.getIndex() - (int)Math.round(fractionalIndex));

      return Integer.compare(dist2, dist1);
    }
  };

  private CarouselCell<T> createCell() {
    Callback<Carousel<T>, CarouselCell<T>> cellFactory = getSkinnable().getCellFactory();

    if(cellFactory == null) {
      return new CarouselCell<T>() {
        @Override
        protected void updateItem(T item, boolean empty) {
          super.updateItem(item, empty);

          if(!empty) {
            setText(item.toString());
          }
        }
      };
    }

    return cellFactory.call(getSkinnable());
  }

  @Override
  protected double computeMinWidth(double height) {
    return 16;
  }

  @Override
  protected double computeMinHeight(double width) {
    return 16;
  }

  @Override
  protected double computePrefWidth(double height) {
    return 16;
  }

  @Override
  protected double computePrefHeight(double width) {
    return 16;
  }

  @Override
  protected void layoutChildren() {
    doLayout();
  }

  private void doLayout() {
    Shape cumulativeClip = null;
    int selectedIndex = getSkinnable().getFocusModel().getFocusedIndex();

    /*
     * Positions the Cells in front-to-back order.  This is done in order to clip the reflections
     * of cells positioned behind other cells using a cumulative clip.  Reflections would otherwise
     * blend with each other as they are partially transparent in nature.
     */

    ListIterator<Node> iterator = getChildren().listIterator(getChildren().size());

    while(iterator.hasPrevious()) {
      @SuppressWarnings("unchecked")
      CarouselCell<T> cell = (CarouselCell<T>)iterator.previous();

      cell.setVisible(!cell.isEmpty());

      if(!cell.isEmpty()) {
        Shape clip = layoutCell(cell, selectedIndex - cell.getIndex() - fractionalIndex);

        layoutInArea(cell, getWidth() / 2, getHeight() / 2, 0, 0, 0, HPos.CENTER, VPos.CENTER);

        if(cumulativeClip != null) {
          Shape cellClip = Shape.intersect(cumulativeClip, new Rectangle(0, 0, getWidth(), getHeight()))// TODO there must be a better way to just copy a Shape...
          Point2D localToParent = cell.localToParent(0, 0);

          cellClip.getTransforms().add(new Translate(-localToParent.getX(), -localToParent.getY()));

          cell.setClip(cellClip);
        }
        else {
          cell.setClip(null);
        }

        if(clip != null) {
          clip.getTransforms().add(cell.getLocalToParentTransform());

          if(cumulativeClip == null) {
            cumulativeClip = new Rectangle(0, 0, getWidth(), getHeight());
          }

          cumulativeClip = Shape.subtract(cumulativeClip, clip);
        }
      }
    }

    setClip(new Rectangle(0, 0, getWidth(), getHeight()));
  }

  // index = fractional index
  public abstract Shape layoutCell(CarouselCell<T> cell, double index);
}
TOP

Related Classes of hs.javafx.control.AbstractCarouselSkin

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.