001package conexp.fx.gui.notification;
002
003/*
004 * #%L
005 * Concept Explorer FX
006 * %%
007 * Copyright (C) 2010 - 2023 Francesco Kriegel
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import java.awt.Window.Type;
026import java.util.Timer;
027import java.util.TimerTask;
028
029import javax.swing.JDialog;
030
031import conexp.fx.gui.util.CoordinateUtil;
032import javafx.animation.FadeTransitionBuilder;
033import javafx.animation.Interpolator;
034import javafx.animation.ParallelTransitionBuilder;
035import javafx.animation.ScaleTransitionBuilder;
036import javafx.animation.Transition;
037import javafx.animation.TranslateTransitionBuilder;
038import javafx.embed.swing.JFXPanel;
039import javafx.event.ActionEvent;
040import javafx.event.EventHandler;
041import javafx.geometry.Pos;
042import javafx.scene.Scene;
043import javafx.scene.layout.Pane;
044import javafx.scene.layout.StackPaneBuilder;
045import javafx.scene.paint.Color;
046import javafx.stage.Screen;
047import javafx.util.Duration;
048
049public class Notification {
050
051  public enum TransitionType {
052    TRANSLATE,
053    FADE;
054  }
055
056  private static final Duration DEFAULT_DURATION = Duration.seconds(10);
057
058  protected final JDialog       dialog           = new JDialog();
059  protected final JFXPanel      panel            = new JFXPanel();
060  private final Timer           timer            = new Timer();
061  private boolean               isDisposed       = false;
062  private Transition            showTransition;
063  private Transition            hideTransition;
064
065  public Notification(
066      final Pane contentPane,
067      final Pos position,
068      final int xShift,
069      final int yShift,
070      final Notification.TransitionType type) {
071    this(contentPane, position, xShift, yShift, type, DEFAULT_DURATION);
072  }
073
074  public Notification(
075      final Pane contentPane,
076      final Pos position,
077      final int xShift,
078      final int yShift,
079      final Notification.TransitionType type,
080      final Duration duration) {
081    this();
082    setContentPane(contentPane, position, xShift, yShift, type, duration);
083  }
084
085  protected Notification() {
086    super();
087  }
088
089  public void show() {
090    dialog.setVisible(true);
091    showTransition.play();
092  }
093
094  public void hideAndDispose() {
095    if (!isDisposed) {
096      timer.cancel();
097      isDisposed = true;
098      hideTransition.play();
099    }
100  }
101
102  protected final void setContentPane(
103      final Pane contentPane,
104      final Pos position,
105      final int xShift,
106      final int yShift,
107      final Notification.TransitionType type) {
108    setContentPane(contentPane, position, xShift, yShift, type, DEFAULT_DURATION);
109  }
110
111  protected final void setContentPane(
112      final Pane contentPane,
113      final Pos position,
114      final int xShift,
115      final int yShift,
116      final Notification.TransitionType type,
117      final Duration duration) {
118    final int width = (int) contentPane.getMaxWidth();
119    final int height = (int) contentPane.getMaxHeight();
120    final Scene scene = new Scene(
121        StackPaneBuilder.create().alignment(CoordinateUtil.contraryPosition(position)).children(contentPane).build(),
122        Color.TRANSPARENT);
123    panel.setScene(scene);
124    panel.setSize(width + xShift, height + yShift);
125    dialog.add(panel);
126    dialog.setType(Type.POPUP);
127    dialog.setUndecorated(true);
128    dialog.setAlwaysOnTop(true);
129    dialog.setResizable(false);
130    dialog.setBackground(new java.awt.Color(0, 0, 0, 0));
131    dialog.setLocation(getDialogX(position, width, xShift), getDialogY(position, height, yShift));
132    dialog.setSize(width + xShift, height + yShift);
133    createTransitions(contentPane, position, xShift, yShift, type);
134    if (!duration.isIndefinite() && !duration.isUnknown()) {
135      timer.schedule(new TimerTask() {
136
137        @Override
138        public void run() {
139          hideAndDispose();
140        }
141      }, (long) duration.toMillis());
142    }
143  }
144
145  private final void createTransitions(
146      final Pane contentPane,
147      final Pos position,
148      final int xShift,
149      final int yShift,
150      final Notification.TransitionType type) {
151    switch (type) {
152    case FADE:
153      createFadeTransition(contentPane);
154      break;
155    case TRANSLATE:
156      createTranslateTransition(contentPane, position, xShift, yShift);
157      break;
158    }
159  }
160
161  private final void createFadeTransition(final Pane contentPane) {
162    contentPane.setOpacity(0);
163    showTransition = ParallelTransitionBuilder
164        .create()
165        .children(
166            FadeTransitionBuilder
167                .create()
168                .node(contentPane)
169                .fromValue(0)
170                .toValue(1)
171                .interpolator(Interpolator.EASE_OUT)
172                .duration(Duration.millis(700))
173                .build(),
174            ScaleTransitionBuilder
175                .create()
176                .node(contentPane)
177                .fromX(0)
178                .fromY(0)
179                .toX(1)
180                .toY(1)
181                .interpolator(Interpolator.EASE_OUT)
182                .duration(Duration.millis(700))
183                .build())
184        .build();
185    hideTransition = ParallelTransitionBuilder
186        .create()
187        .children(
188            FadeTransitionBuilder
189                .create()
190                .node(contentPane)
191                .fromValue(1)
192                .toValue(0)
193                .interpolator(Interpolator.EASE_IN)
194                .duration(Duration.millis(700))
195                .build(),
196            ScaleTransitionBuilder
197                .create()
198                .node(contentPane)
199                .fromX(1)
200                .fromY(1)
201                .toX(0)
202                .toY(0)
203                .interpolator(Interpolator.EASE_IN)
204                .duration(Duration.millis(700))
205                .build())
206        .onFinished(new EventHandler<ActionEvent>() {
207
208          @Override
209          public void handle(ActionEvent event) {
210            dialog.setVisible(false);
211            dialog.dispose();
212          }
213        })
214        .build();
215  }
216
217  private final void
218      createTranslateTransition(final Pane contentPane, final Pos position, final int xShift, final int yShift) {
219    int translateX = 0;
220    switch (position) {
221    case BOTTOM_RIGHT:
222    case CENTER_RIGHT:
223    case TOP_RIGHT:
224      translateX = (int) contentPane.getMaxWidth() + xShift;
225      break;
226    case BOTTOM_LEFT:
227    case CENTER_LEFT:
228    case TOP_LEFT:
229      translateX = (int) -(contentPane.getMaxWidth() + xShift);
230      break;
231    default:
232    }
233    int translateY = 0;
234    switch (position) {
235    case BOTTOM_RIGHT:
236    case BOTTOM_CENTER:
237    case BOTTOM_LEFT:
238      translateY = (int) contentPane.getMaxHeight() + yShift;
239      break;
240    case TOP_RIGHT:
241    case TOP_CENTER:
242    case TOP_LEFT:
243      translateY = (int) -(contentPane.getMaxHeight() + yShift);
244      break;
245    default:
246    }
247    contentPane.setTranslateX(translateX);
248    contentPane.setTranslateY(translateY);
249    showTransition = TranslateTransitionBuilder
250        .create()
251        .node(contentPane)
252        .byX(-translateX)
253        .byY(-translateY)
254        .interpolator(Interpolator.EASE_OUT)
255        .duration(Duration.millis(700))
256        .build();
257    hideTransition = TranslateTransitionBuilder
258        .create()
259        .node(contentPane)
260        .byX(translateX)
261        .byY(translateY)
262        .interpolator(Interpolator.EASE_IN)
263        .duration(Duration.millis(700))
264        .onFinished(new EventHandler<ActionEvent>() {
265
266          @Override
267          public void handle(ActionEvent event) {
268            dialog.setVisible(false);
269            dialog.dispose();
270          }
271        })
272        .build();
273  }
274
275  private final int getDialogX(final Pos position, final int width, final int shift) {
276    switch (position) {
277    case BOTTOM_RIGHT:
278    case CENTER_RIGHT:
279    case TOP_RIGHT:
280      return (int) Screen.getPrimary().getBounds().getWidth() - width - shift;
281    case BOTTOM_CENTER:
282    case CENTER:
283    case TOP_CENTER:
284      return (int) (Screen.getPrimary().getBounds().getWidth() - width) / 2;
285    case BOTTOM_LEFT:
286    case CENTER_LEFT:
287    case TOP_LEFT:
288    default:
289      return 0;
290    }
291  }
292
293  private final int getDialogY(final Pos position, final int height, final int shift) {
294    switch (position) {
295    case BOTTOM_LEFT:
296    case BOTTOM_CENTER:
297    case BOTTOM_RIGHT:
298      return (int) Screen.getPrimary().getBounds().getHeight() - height - shift;
299    case CENTER_LEFT:
300    case CENTER:
301    case CENTER_RIGHT:
302      return (int) (Screen.getPrimary().getBounds().getHeight() - height) / 2;
303    case TOP_LEFT:
304    case TOP_CENTER:
305    case TOP_RIGHT:
306    default:
307      return 0;
308    }
309  }
310
311}