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}