/* * Copyright 2015 Laurent Pellegrino * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.controlsfx.yfiton; import javafx.animation.Animation.Status; import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.Event; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.geometry.VPos; import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.ButtonBar; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.stage.Screen; import javafx.util.Duration; import org.controlsfx.control.NotificationPane; import org.controlsfx.control.action.Action; import org.controlsfx.control.action.ActionUtils; /** * Copied and adapted from controlsfx source code. */ @SuppressWarnings("deprecation") public abstract class NotificationBar extends Region { private static final double MIN_HEIGHT = 40; final Label label; private final GridPane pane; // --- animation timeline code private final Duration TRANSITION_DURATION = new Duration(350.0); public DoubleProperty transition = new SimpleDoubleProperty() { @Override protected void invalidated() { requestContainerLayout(); } }; Label title; ButtonBar actionsBar; Button closeBtn; EventHandler<ActionEvent> onCloseAction; private Timeline timeline; private double transitionStartValue; public NotificationBar() { getStyleClass().add("notification-bar"); //$NON-NLS-1$ setVisible(isShowing()); pane = new GridPane(); pane.getStyleClass().add("pane"); //$NON-NLS-1$ pane.setAlignment(Pos.BASELINE_LEFT); getChildren().setAll(pane); // initialise title area, if one is set String titleStr = getTitle(); if (titleStr != null && !titleStr.isEmpty()) { title = new Label(); title.getStyleClass().add("title"); //$NON-NLS-1$ title.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); GridPane.setHgrow(title, Priority.ALWAYS); title.setText(titleStr); title.opacityProperty().bind(transition); } // initialise label area label = new Label(); label.setWrapText(true); label.setMaxSize(0.4 * Screen.getPrimary().getVisualBounds().getWidth(), Double.MAX_VALUE); GridPane.setVgrow(label, Priority.ALWAYS); GridPane.setHgrow(label, Priority.ALWAYS); label.setText(getText()); label.setGraphic(getGraphic()); label.opacityProperty().bind(transition); // initialise actions area getActions().addListener(new InvalidationListener() { @Override public void invalidated(Observable arg0) { updatePane(); } }); // initialise close button area closeBtn = new Button(); closeBtn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent arg0) { hide(); if (onCloseAction != null) { onCloseAction.handle(arg0); } } }); closeBtn.getStyleClass().setAll("close-button"); //$NON-NLS-1$ StackPane graphic = new StackPane(); graphic.getStyleClass().setAll("graphic"); //$NON-NLS-1$ closeBtn.setGraphic(graphic); closeBtn.setMinSize(17, 17); closeBtn.setPrefSize(17, 17); closeBtn.setFocusTraversable(false); closeBtn.opacityProperty().bind(transition); GridPane.setMargin(closeBtn, new Insets(0, 0, 0, 8)); // position the close button in the best place, depending on the height double minHeight = minHeight(-1); GridPane.setValignment(closeBtn, minHeight == MIN_HEIGHT ? VPos.CENTER : VPos.TOP); // put it all together updatePane(); } public void requestContainerLayout() { layoutChildren(); } public String getTitle() { return ""; //$NON-NLS-1$ } public boolean isCloseButtonVisible() { return true; } public abstract String getText(); public abstract Node getGraphic(); public abstract ObservableList<Action> getActions(); public abstract void hide(); public abstract boolean isShowing(); public abstract boolean isShowFromTop(); public abstract double getContainerHeight(); public abstract void relocateInParent(double x, double y); void updatePane() { actionsBar = ActionUtils.createButtonBar(getActions()); actionsBar.opacityProperty().bind(transition); GridPane.setHgrow(actionsBar, Priority.SOMETIMES); pane.getChildren().clear(); int row = 0; if (title != null) { pane.add(title, 0, row++); } pane.add(label, 0, row); pane.add(actionsBar, 1, row); if (isCloseButtonVisible()) { pane.add(closeBtn, 2, 0, 1, row + 1); } } @Override protected void layoutChildren() { final double w = getWidth(); final double h = computePrefHeight(-1); final double notificationBarHeight = prefHeight(w); final double notificationMinHeight = minHeight(w); if (isShowFromTop()) { // place at top of area pane.resize(w, h); relocateInParent(0, (transition.get() - 1) * notificationMinHeight); } else { // place at bottom of area pane.resize(w, notificationBarHeight); relocateInParent(0, getContainerHeight() - notificationBarHeight); } } @Override protected double computeMinHeight(double width) { return Math.max(super.computePrefHeight(width), MIN_HEIGHT); } @Override protected double computePrefHeight(double width) { return Math.max(pane.prefHeight(width), minHeight(width)) * transition.get(); } public void doShow() { transitionStartValue = 0; doAnimationTransition(); } public void doHide() { transitionStartValue = 1; doAnimationTransition(); } public void setOnCloseAction(EventHandler<ActionEvent> onAction) { this.onCloseAction = onAction; } private void doAnimationTransition() { Duration duration; if (timeline != null && (timeline.getStatus() != Status.STOPPED)) { duration = timeline.getCurrentTime(); // fix for #70 - the notification pane freezes up as it has zero // duration to expand / contract duration = duration == Duration.ZERO ? TRANSITION_DURATION : duration; transitionStartValue = transition.get(); // --- end of fix timeline.stop(); } else { duration = TRANSITION_DURATION; } timeline = new Timeline(); timeline.setCycleCount(1); KeyFrame k1, k2; if (isShowing()) { k1 = new KeyFrame( Duration.ZERO, new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { // start expand setCache(true); setVisible(true); pane.fireEvent(new Event(NotificationPane.ON_SHOWING)); } }, new KeyValue(transition, transitionStartValue) ); k2 = new KeyFrame( duration, new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { // end expand pane.setCache(false); pane.fireEvent(new Event(NotificationPane.ON_SHOWN)); } }, new KeyValue(transition, 1, Interpolator.EASE_OUT) ); } else { k1 = new KeyFrame( Duration.ZERO, new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { // Start collapse pane.setCache(true); pane.fireEvent(new Event(NotificationPane.ON_HIDING)); } }, new KeyValue(transition, transitionStartValue) ); k2 = new KeyFrame( duration, new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { // end collapse setCache(false); setVisible(false); pane.fireEvent(new Event(NotificationPane.ON_HIDDEN)); } }, new KeyValue(transition, 0, Interpolator.EASE_IN) ); } timeline.getKeyFrames().setAll(k1, k2); timeline.play(); } }