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

com.jfoenix.controls.JFXSnackbar Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 com.jfoenix.controls;

import com.jfoenix.controls.JFXButton.ButtonType;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.css.PseudoClass;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @see  Snackbars & toasts
 * 

* The use of a javafx Popup or PopupContainer for notifications would seem intuitive but Popups are displayed in their * own dedicated windows and alligning the popup window and handling window on top layering is more trouble then it is * worth. */ public class JFXSnackbar extends Group { private static final String DEFAULT_STYLE_CLASS = "jfx-snackbar"; private Label toast; private JFXButton action; private Pane snackbarContainer; private ChangeListener sizeListener; private AtomicBoolean processingQueue = new AtomicBoolean(false); private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue<>(); private StackPane actionContainer; private Interpolator easeInterpolator = Interpolator.SPLINE(0.250, 0.100, 0.250, 1.000); private BorderPane content; private String activePseudoClass = null; public JFXSnackbar() { this(null); } public JFXSnackbar(Pane snackbarContainer) { toast = new Label(); toast.setMinWidth(Control.USE_PREF_SIZE); toast.getStyleClass().add("jfx-snackbar-toast"); toast.setWrapText(true); StackPane toastContainer = new StackPane(toast); toastContainer.setPadding(new Insets(20)); action = new JFXButton(); action.setMinWidth(Control.USE_PREF_SIZE); action.setButtonType(ButtonType.FLAT); action.getStyleClass().add("jfx-snackbar-action"); // actions will be added upon showing the snackbar if needed actionContainer = new StackPane(action); actionContainer.setPadding(new Insets(0, 10, 0, 0)); content = new BorderPane(); content.setLeft(toastContainer); content.setRight(actionContainer); toast.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> { if (content.getPrefWidth() == -1) { return content.getPrefWidth(); } double actionWidth = actionContainer.isVisible() ? actionContainer.getWidth() : 0.0; return content.prefWidthProperty().get() - actionWidth; }, content.prefWidthProperty(), actionContainer.widthProperty(), actionContainer.visibleProperty())); //bind the content's height and width from this snackbar allowing the content's dimensions to be set externally content.getStyleClass().add("jfx-snackbar-content"); //wrap the content in a group so that the content is managed inside its own container //but the group is not managed in the snackbarContainer so it does not affect any layout calculations getChildren().add(content); setManaged(false); setVisible(false); sizeListener = (o, oldVal, newVal) -> refreshPopup(); // register the container before resizing it registerSnackbarContainer(snackbarContainer); // resize the popup if its layout has been changed layoutBoundsProperty().addListener((o, oldVal, newVal) -> refreshPopup()); addEventHandler(SnackbarEvent.SNACKBAR, e -> enqueue(e)); } private void initialize() { this.getStyleClass().add(DEFAULT_STYLE_CLASS); } /*************************************************************************** * * Setters / Getters * * **************************************************************************/ public Pane getPopupContainer() { return snackbarContainer; } public void setPrefWidth(double width) { content.setPrefWidth(width); } public double getPrefWidth() { return content.getPrefWidth(); } /*************************************************************************** * * Public API * * **************************************************************************/ public void registerSnackbarContainer(Pane snackbarContainer) { if (snackbarContainer != null) { if (this.snackbarContainer != null) { //since listeners are added the container should be properly registered/unregistered throw new IllegalArgumentException("Snackbar Container already set"); } this.snackbarContainer = snackbarContainer; this.snackbarContainer.getChildren().add(this); this.snackbarContainer.heightProperty().addListener(sizeListener); this.snackbarContainer.widthProperty().addListener(sizeListener); } } public void unregisterSnackbarContainer(Pane snackbarContainer) { if (snackbarContainer != null) { if (this.snackbarContainer == null) { throw new IllegalArgumentException("Snackbar Container not set"); } this.snackbarContainer.getChildren().remove(this); this.snackbarContainer.heightProperty().removeListener(sizeListener); this.snackbarContainer.widthProperty().removeListener(sizeListener); this.snackbarContainer = null; } } public void show(String toastMessage, long timeout) { this.show(toastMessage, null, timeout, null); } public void show(String message, String actionText, EventHandler actionHandler) { this.show(message, actionText, -1, actionHandler); } public void show(String toastMessage, String pseudoClass, long timeout) { this.show(toastMessage, pseudoClass, null, timeout, null); } public void show(String message, String pseudoClass, String actionText, EventHandler actionHandler) { this.show(message, pseudoClass, actionText, -1, actionHandler); } public void show(String message, String actionText, long timeout, EventHandler actionHandler) { this.show(message, null, actionText, timeout, actionHandler); } public void show(String message, String pseudoClass, String actionText, long timeout, EventHandler actionHandler) { toast.setText(message); if (actionText != null && !actionText.isEmpty()) { action.setVisible(true); actionContainer.setVisible(true); actionContainer.setManaged(true); // to force updating the layout bounds action.setText(""); action.setText(actionText); action.setOnAction(actionHandler); } else { actionContainer.setVisible(false); actionContainer.setManaged(false); action.setVisible(false); } openAnimation = getTimeline(timeout); if (pseudoClass != null) { activePseudoClass = pseudoClass; content.pseudoClassStateChanged(PseudoClass.getPseudoClass(activePseudoClass), true); } openAnimation.play(); } private Timeline openAnimation = null; private Timeline getTimeline(long timeout) { Timeline animation; if (timeout <= 0) { animation = new Timeline( new KeyFrame( Duration.ZERO, e -> this.toBack(), new KeyValue(this.visibleProperty(), false, Interpolator.EASE_BOTH), new KeyValue(this.translateYProperty(), this.getLayoutBounds().getHeight(), easeInterpolator), new KeyValue(this.opacityProperty(), 0, easeInterpolator) ), new KeyFrame( Duration.millis(10), e -> this.toFront(), new KeyValue(this.visibleProperty(), true, Interpolator.EASE_BOTH) ), new KeyFrame(Duration.millis(300), new KeyValue(this.opacityProperty(), 1, easeInterpolator), new KeyValue(this.translateYProperty(), 0, easeInterpolator) ) ); animation.setCycleCount(1); } else { animation = new Timeline( new KeyFrame( Duration.ZERO, (e) -> this.toBack(), new KeyValue(this.visibleProperty(), false, Interpolator.EASE_BOTH), new KeyValue(this.translateYProperty(), this.getLayoutBounds().getHeight(), easeInterpolator), new KeyValue(this.opacityProperty(), 0, easeInterpolator) ), new KeyFrame( Duration.millis(10), (e) -> this.toFront(), new KeyValue(this.visibleProperty(), true, Interpolator.EASE_BOTH) ), new KeyFrame(Duration.millis(300), new KeyValue(this.opacityProperty(), 1, easeInterpolator), new KeyValue(this.translateYProperty(), 0, easeInterpolator) ), new KeyFrame(Duration.millis(timeout / 2)) ); animation.setAutoReverse(true); animation.setCycleCount(2); animation.setOnFinished((e) -> { resetPseudoClass(); processSnackbars(); }); } return animation; } public void close() { if(openAnimation!=null) openAnimation.stop(); if (this.isVisible()) { Timeline closeAnimation = new Timeline( new KeyFrame( Duration.ZERO, e -> this.toFront(), new KeyValue(this.opacityProperty(), 1, easeInterpolator), new KeyValue(this.translateYProperty(), 0, easeInterpolator) ), new KeyFrame( Duration.millis(290), new KeyValue(this.visibleProperty(), true, Interpolator.EASE_BOTH) ), new KeyFrame(Duration.millis(300), e -> this.toBack(), new KeyValue(this.visibleProperty(), false, Interpolator.EASE_BOTH), new KeyValue(this.translateYProperty(), this.getLayoutBounds().getHeight(), easeInterpolator), new KeyValue(this.opacityProperty(), 0, easeInterpolator) ) ); closeAnimation.setCycleCount(1); closeAnimation.setOnFinished(e -> { resetPseudoClass(); processSnackbars(); }); closeAnimation.play(); } } private void resetPseudoClass() { if (activePseudoClass != null) { content.pseudoClassStateChanged(PseudoClass.getPseudoClass(activePseudoClass), false); activePseudoClass = null; } } private void processSnackbars() { SnackbarEvent qevent = eventQueue.poll(); if (qevent != null) { if (qevent.isPersistent()) { show(qevent.getMessage(), qevent.getpseudoClass(), qevent.getActionText(), qevent.getActionHandler()); } else { show(qevent.getMessage(), qevent.getpseudoClass(), qevent.getActionText(), qevent.getTimeout(), qevent.getActionHandler()); } } else { //The enqueue method and this listener should be executed sequentially on the FX Thread so there //should not be a race condition processingQueue.getAndSet(false); } } public void refreshPopup() { Bounds contentBound = this.getLayoutBounds(); double offsetX = Math.ceil(snackbarContainer.getWidth() / 2) - Math.ceil(contentBound.getWidth() / 2); double offsetY = snackbarContainer.getHeight() - contentBound.getHeight(); this.setLayoutX(offsetX); this.setLayoutY(offsetY); } public void enqueue(SnackbarEvent event) { eventQueue.add(event); if (processingQueue.compareAndSet(false, true)) { Platform.runLater(() -> { SnackbarEvent qevent = eventQueue.poll(); if (qevent != null) { if (qevent.isPersistent()) { show(qevent.getMessage(), qevent.getpseudoClass(), qevent.getActionText(), qevent.getActionHandler()); } else { show(qevent.getMessage(), qevent.getpseudoClass(), qevent.getActionText(), qevent.getTimeout(), qevent.getActionHandler()); } } }); } } /*************************************************************************** * * Event API * * **************************************************************************/ public static class SnackbarEvent extends Event { public static final EventType SNACKBAR = new EventType<>(Event.ANY, "SNACKBAR"); private final String message; private final String actionText; private final String pseudoClass; private final long timeout; private final boolean persistent; private final EventHandler actionHandler; public SnackbarEvent(String message) { this(message, null, 3000, false, null); } public SnackbarEvent(String message, String pseudoClass) { this(message, pseudoClass, null, 3000, false, null); } public SnackbarEvent(String message, String actionText, long timeout, boolean persistent, EventHandler actionHandler) { this(message, null, actionText, timeout, persistent, actionHandler); } public SnackbarEvent(String message, String pseudoClass, String actionText, long timeout, boolean persistent, EventHandler actionHandler) { super(SNACKBAR); this.message = message; this.pseudoClass = pseudoClass; this.actionText = actionText; this.timeout = timeout < 1 ? 3000 : timeout; this.actionHandler = actionHandler; this.persistent = persistent; } public String getMessage() { return message; } public String getActionText() { return actionText; } public long getTimeout() { return timeout; } public EventHandler getActionHandler() { return actionHandler; } @Override public EventType getEventType() { return (EventType) super.getEventType(); } public boolean isPersistent() { return persistent; } public String getpseudoClass() { return pseudoClass; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy