package bluej.utility.javafx;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;


| Like TitledPane, but does not have a title section, and instead | just has an arrow to expand/collapse above the content. | | It turned out that styling TitledPane to act like this was nigh-on impossible, | so I borrowed some of its workings to make our own simpler version. | public class UntitledCollapsiblePane extends Pane{ public static enum ArrowLocation {
| Arrow at top, content expands downwards from top | TOP, | Arrow at left, content expands rightwards from left | LEFT; } private final ArrowLocation arrowLocation; private final TriangleArrow arrow; private final BorderPane arrowWrapper; protected final double arrowPadding = 1; private final DoubleProperty transitionProperty = new SimpleDoubleProperty(1.0) { @Override protected void invalidated() { requestLayout(); } }; private final Node content; private final Rectangle clipRect; private final BooleanProperty expanded = new SimpleBooleanProperty(); private FXPlatformRunnable cancelHover; private Animation animation; public UntitledCollapsiblePane(Node content, ArrowLocation arrowLocation, boolean startCollapsed) { this.content = content; this.arrowLocation = arrowLocation; this.arrow = new TriangleArrow(isVertical() ? Orientation.VERTICAL : Orientation.HORIZONTAL); this.arrowWrapper = new BorderPane(arrow); arrowWrapper.setPadding(new Insets(arrowPadding)); this.clipRect = new Rectangle(); getChildren().addAll(arrowWrapper, content); content.setClip(clipRect); if (isVertical()) clipRect.widthProperty().bind(widthProperty()); else{ clipRect.heightProperty().bind(heightProperty()); } JavaFXUtil.addStyleClass(this, "untitled-pane"); expanded.set(!startCollapsed); arrowWrapper.setOnMouseClicked(e -> { expanded.set(!expanded.get()); }); arrowWrapper.setOnMouseEntered(e -> { if (cancelHover != null) cancelHover.run(); cancelHover = JavaFXUtil.runAfter(Duration.millis(200), () -> JavaFXUtil.setPseudoclass("bj-hover-long", true, arrowWrapper)); }); arrowWrapper.setOnMouseExited(e -> { if (cancelHover != null) { cancelHover.run(); cancelHover = null; } JavaFXUtil.setPseudoclass("bj-hover-long", false, arrowWrapper); }); if (startCollapsed) { transitionProperty.set(0.0); arrow.scaleProperty().set(1.0); } else { transitionProperty.set(1.0); arrow.scaleProperty().set(-1.0); } JavaFXUtil.addChangeListener(expanded, this::runAnimation); }
| A constructor which takes the main parameters needed for the UntitledCollapsiblePane | besides a consumer function to be executed when the arrow is clicked. | | @param content A node has all contents to be added to the Pane | @param arrowLocation Where to place the arrow (e.g. Top, Left). | @param startCollapsed The initial folding state (true it is collapsed, false expanded) | @param listener A consumer function to be executed when the arrow is clicked | public UntitledCollapsiblePane(Node content, ArrowLocation arrowLocation, boolean startCollapsed, FXPlatformConsumer<? super Boolean> listener) { this(content, arrowLocation, startCollapsed); arrowWrapper.setOnMouseClicked(e -> { expanded.set(!expanded.get()); listener.accept(expanded.get()); }); } private boolean isVertical() { return arrowLocation == ArrowLocation.TOP; } private void runAnimation(boolean toExpanded) { if (animation != null) { animation.stop(); } double dest = toExpanded ? 1 : 0; double destScale = toExpanded ? -1 : 1; animation = new Timeline(new KeyFrame(Duration.millis(300), new KeyValue(transitionProperty, dest), new KeyValue(arrow.scaleProperty(), destScale))); animation.setOnFinished(ev -> { animation = null; }); animation.playFromStart(); } public BooleanProperty expandedProperty() { return expanded; } @Override protected void layoutChildren() { this.layoutChildren(0, 0, getWidth(), getHeight()); } private void layoutChildren(double x, double y, final double w, final double h) { final double arrowSize; if (isVertical()) { arrowSize = snapSizeY(arrow.TRIANGLE_DEPTH + 2 * arrowPadding); arrowWrapper.resize(w, arrowSize); } else { arrowSize = snapSizeX(arrow.TRIANGLE_DEPTH + 2 * arrowPadding); arrowWrapper.resize(arrowSize, h); } positionInArea(arrowWrapper, x, y, isVertical() ? w : arrowSize, isVertical() ? arrowSize : h, 0, HPos.CENTER, VPos.CENTER); final double contentSize; if (isVertical()) { contentSize = snapSizeY(h - arrowSize); } else { contentSize = snapSizeX(w - arrowSize); } if (isVertical()) { y += arrowSize; content.resize(w, contentSize); clipRect.setHeight(contentSize); } else { x += arrowSize; content.resize(contentSize, h); clipRect.setWidth(contentSize); } positionInArea(content, x, y, isVertical() ? w : contentSize, isVertical() ? contentSize : h, /*baseline ignored , HPos.CENTER, VPos.CENTER); } @Override protected double computePrefWidth(double height) { return isVertical() ? content.prefWidth(height) : content.prefWidth(height) * getTransition() + arrow.TRIANGLE_DEPTH + 2 * arrowPadding; } @Override protected double computePrefHeight(double width) { return isVertical() ? content.prefHeight(width) * getTransition() + arrow.TRIANGLE_DEPTH + 2 * arrowPadding : content.prefHeight(width); } @Override protected double computeMinWidth(double height) { return isVertical() ? content.minWidth(height) : content.minWidth(height) * getTransition() + arrow.TRIANGLE_DEPTH + 2 * arrowPadding; } @Override protected double computeMinHeight(double width) { return isVertical() ? content.minHeight(width) * getTransition() + arrow.TRIANGLE_DEPTH + 2 * arrowPadding : content.minHeight(width); } private double getTransition() { return transitionProperty.get(); } public void addArrowWrapperStyleClass(String styleClass) { arrowWrapper.getStyleClass().add(styleClass); } }
top, use, map, class UntitledCollapsiblePane

.   invalidated
.   UntitledCollapsiblePane
.   UntitledCollapsiblePane
.   isVertical
.   runAnimation
.   expandedProperty
.   layoutChildren
.   layoutChildren
.   computePrefWidth
.   computePrefHeight
.   computeMinWidth
.   computeMinHeight
.   getTransition
.   addArrowWrapperStyleClass




277 neLoCode + 13 LoComm