Moved Growl into it's own project
This commit is contained in:
parent
719d47c293
commit
f382160189
|
@ -1,281 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014, ControlsFX
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of ControlsFX, any associated website, nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*
|
||||
* MODIFIED BY DORKBOX, LLC
|
||||
* Copyright 2015 dorkbox, llc
|
||||
*
|
||||
* 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 dorkbox.util.javafx;
|
||||
|
||||
import com.sun.javafx.application.PlatformImpl;
|
||||
import dorkbox.util.JavaFxUtil;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.stage.Window;
|
||||
import javafx.util.Duration;
|
||||
import org.controlsfx.tools.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An API to show popup notification messages to the user in the corner of their
|
||||
* screen, unlike the {@link org.controlsfx.control.NotificationPane} which shows notification messages
|
||||
* within your application itself.
|
||||
*
|
||||
* <h3>Screenshot</h3>
|
||||
* <p>
|
||||
* The following screenshot shows a sample notification rising from the
|
||||
* bottom-right corner of my screen:
|
||||
*
|
||||
* <br/>
|
||||
* <br/>
|
||||
* <img src="notifications.png"/>
|
||||
*
|
||||
* <h3>Code Example:</h3>
|
||||
* <p>
|
||||
* To create the notification shown in the screenshot, simply do the following:
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* Notifications.create()
|
||||
* .title("Title Text")
|
||||
* .text("Hello World 0!")
|
||||
* .showWarning();
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class Growl {
|
||||
|
||||
/***************************************************************************
|
||||
* * Static fields * *
|
||||
**************************************************************************/
|
||||
|
||||
private static final String STYLE_CLASS_DARK = "dark"; //$NON-NLS-1$
|
||||
|
||||
/***************************************************************************
|
||||
* * Private fields * *
|
||||
**************************************************************************/
|
||||
|
||||
String title;
|
||||
String text;
|
||||
Node graphic;
|
||||
|
||||
Pos position = Pos.BOTTOM_RIGHT;
|
||||
private Duration hideAfterDuration = Duration.seconds(5);
|
||||
boolean hideCloseButton;
|
||||
private EventHandler<ActionEvent> onAction;
|
||||
Window owner;
|
||||
|
||||
List<String> styleClass = new ArrayList<>();
|
||||
|
||||
/***************************************************************************
|
||||
* * Constructors * *
|
||||
**************************************************************************/
|
||||
|
||||
// we do not allow instantiation of the Notifications class directly - users
|
||||
// must go via the builder API (that is, calling create())
|
||||
private
|
||||
Growl() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* * Public API * *
|
||||
**************************************************************************/
|
||||
|
||||
/**
|
||||
* Call this to begin the process of building a notification to show.
|
||||
*/
|
||||
public static
|
||||
Growl create() {
|
||||
// make sure that javafx application thread is started
|
||||
// Note that calling PlatformImpl.startup more than once is OK
|
||||
PlatformImpl.startup(() -> {
|
||||
// No need to do anything here
|
||||
});
|
||||
|
||||
return new Growl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the text to show in the notification.
|
||||
*/
|
||||
public
|
||||
Growl text(String text) {
|
||||
this.text = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the title to show in the notification.
|
||||
*/
|
||||
public
|
||||
Growl title(String title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the graphic to show in the notification.
|
||||
*/
|
||||
public
|
||||
Growl graphic(Node graphic) {
|
||||
this.graphic = graphic;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the position of the notification on screen, by default it is
|
||||
* {@link Pos#BOTTOM_RIGHT bottom-right}.
|
||||
*/
|
||||
public
|
||||
Growl position(Pos position) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The dialog window owner - if specified the notifications will be inside
|
||||
* the owner, otherwise the notifications will be shown within the whole
|
||||
* screen.
|
||||
*/
|
||||
public
|
||||
Growl owner(Object owner) {
|
||||
this.owner = Utils.getWindow(owner);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the duration that the notification should show, after which it
|
||||
* will be hidden.
|
||||
*/
|
||||
public
|
||||
Growl hideAfter(Duration duration) {
|
||||
this.hideAfterDuration = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify what to do when the user clicks on the notification (in addition
|
||||
* to the notification hiding, which happens whenever the notification is
|
||||
* clicked on).
|
||||
*/
|
||||
public
|
||||
Growl onAction(EventHandler<ActionEvent> onAction) {
|
||||
this.onAction = onAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the notification should use the built-in dark styling,
|
||||
* rather than the default 'modena' notification style (which is a
|
||||
* light-gray).
|
||||
*/
|
||||
public
|
||||
Growl darkStyle() {
|
||||
styleClass.add(STYLE_CLASS_DARK);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the close button in the top-right corner of the notification
|
||||
* should not be shown.
|
||||
*/
|
||||
public
|
||||
Growl hideCloseButton() {
|
||||
this.hideCloseButton = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the notification to be shown, and that it should use the built-in 'warning' graphic.
|
||||
*/
|
||||
public
|
||||
void showWarning() {
|
||||
graphic(new ImageView(Growl.class.getResource("/org/controlsfx/dialog/dialog-warning.png")
|
||||
.toExternalForm())); //$NON-NLS-1$
|
||||
show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the notification to be shown, and that it should use the built-in 'information' graphic.
|
||||
*/
|
||||
public
|
||||
void showInformation() {
|
||||
graphic(new ImageView(Growl.class.getResource("/org/controlsfx/dialog/dialog-information.png")
|
||||
.toExternalForm())); //$NON-NLS-1$
|
||||
show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the notification to be shown, and that it should use the built-in 'error' graphic.
|
||||
*/
|
||||
public
|
||||
void showError() {
|
||||
graphic(new ImageView(Growl.class.getResource("/org/controlsfx/dialog/dialog-error.png")
|
||||
.toExternalForm())); //$NON-NLS-1$
|
||||
show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the notification to be shown, and that it should use the built-in 'confirm' graphic.
|
||||
*/
|
||||
public
|
||||
void showConfirm() {
|
||||
graphic(new ImageView(Growl.class.getResource("/org/controlsfx/dialog/dialog-confirm.png")
|
||||
.toExternalForm())); //$NON-NLS-1$
|
||||
show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the notification to be shown.
|
||||
*/
|
||||
public
|
||||
void show() {
|
||||
// we can't use regular popup, because IF WE HAVE NO OWNER, it won't work!
|
||||
// instead, we just create a JFRAME (and use our StageViaSwing class) to put javaFX inside it
|
||||
JavaFxUtil.invokeAndWait(() -> new GrowlPopup(this).show());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014, ControlsFX All rights reserved.
|
||||
* <p>
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are
|
||||
* met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. *
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution. * Neither the name of ControlsFX, any associated website, nor the
|
||||
* names of its contributors may be used to endorse or promote products derived from this software without specific prior written
|
||||
* permission.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CONTROLSFX
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
* <p>
|
||||
* MODIFIED BY DORKBOX, LLC Copyright 2015 dorkbox, llc
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 dorkbox.util.javafx;
|
||||
|
||||
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.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
public
|
||||
class GrowlNotification extends Region {
|
||||
|
||||
private static final double MIN_HEIGHT = 40;
|
||||
|
||||
private final String textText;
|
||||
private final Node graphicNode;
|
||||
|
||||
protected final GridPane pane;
|
||||
|
||||
public
|
||||
GrowlNotification(final Growl notification) {
|
||||
this.textText = notification.text;
|
||||
this.graphicNode = notification.graphic;
|
||||
|
||||
getStyleClass().add("notification-bar"); //$NON-NLS-1$
|
||||
|
||||
setVisible(true);
|
||||
|
||||
pane = new GridPane();
|
||||
pane.getStyleClass()
|
||||
.add("pane"); //$NON-NLS-1$
|
||||
pane.setAlignment(Pos.BASELINE_LEFT);
|
||||
getChildren().setAll(pane);
|
||||
|
||||
// put it all together
|
||||
pane.getChildren()
|
||||
.clear();
|
||||
|
||||
int row = 0;
|
||||
|
||||
// title
|
||||
if (notification.title != null && !notification.title.isEmpty()) {
|
||||
Label 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(notification.title);
|
||||
pane.add(title, 0, row++);
|
||||
}
|
||||
|
||||
Region spacer = new Region();
|
||||
spacer.setPrefHeight(10);
|
||||
|
||||
pane.add(spacer, 0, row++);
|
||||
|
||||
// graphic + text area
|
||||
Label label = new Label();
|
||||
label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
|
||||
GridPane.setVgrow(label, Priority.ALWAYS);
|
||||
GridPane.setHgrow(label, Priority.ALWAYS);
|
||||
|
||||
label.setText(textText);
|
||||
label.setGraphic(graphicNode);
|
||||
pane.add(label, 0, row);
|
||||
|
||||
|
||||
// close button
|
||||
if (!notification.hideCloseButton) {
|
||||
Button closeBtn = new Button();
|
||||
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);
|
||||
|
||||
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);
|
||||
closeBtn.setOnAction(arg0 -> hide());
|
||||
|
||||
pane.add(closeBtn, 2, 0, 1, row + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
void hide() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
void layoutChildren() {
|
||||
final double w = getWidth();
|
||||
double h = computePrefHeight(-1);
|
||||
|
||||
pane.resize(w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
double computeMinWidth(double height) {
|
||||
String text = textText;
|
||||
Node graphic = graphicNode;
|
||||
|
||||
if ((text == null || text.isEmpty()) && (graphic != null)) {
|
||||
return graphic.minWidth(height) + 20;
|
||||
}
|
||||
return 400;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
double computeMinHeight(double width) {
|
||||
String text = textText;
|
||||
Node graphic = graphicNode;
|
||||
|
||||
if ((text == null || text.isEmpty()) && (graphic != null)) {
|
||||
return graphic.minHeight(width) + 20;
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected
|
||||
double computePrefHeight(double width) {
|
||||
return Math.max(pane.prefHeight(width), minHeight(width));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,467 +0,0 @@
|
|||
package dorkbox.util.javafx;
|
||||
|
||||
import dorkbox.util.ScreenUtil;
|
||||
import dorkbox.util.SwingUtil;
|
||||
import javafx.animation.*;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.controlsfx.tools.Utils;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public
|
||||
class GrowlPopup {
|
||||
|
||||
private static final java.util.List<GrowlPopup> popups = new ArrayList<>();
|
||||
// for animating in the notifications
|
||||
private static final ParallelTransition parallelTransition = new ParallelTransition();
|
||||
|
||||
private static final double padding = 40;
|
||||
|
||||
private final GrowlPopupViaSwing frame;
|
||||
|
||||
final double startX;
|
||||
final double startY;
|
||||
final javafx.stage.Window window;
|
||||
final double screenWidth;
|
||||
final double screenHeight;
|
||||
|
||||
private final Pos position;
|
||||
|
||||
private final double anchorX;
|
||||
private final double anchorY;
|
||||
|
||||
final Timeline animationTimeline = new Timeline();
|
||||
private double newX;
|
||||
private double newY;
|
||||
|
||||
GrowlPopup(final Growl notification) {
|
||||
final Image icon;
|
||||
if (notification.graphic instanceof ImageView) {
|
||||
icon = SwingFXUtils.fromFXImage(((ImageView) notification.graphic).getImage(), null);
|
||||
} else {
|
||||
icon = SwingUtil.BLANK_ICON;
|
||||
}
|
||||
|
||||
// created on the swing EDT
|
||||
frame = GrowlPopupViaSwing.create(icon, notification.title);
|
||||
// don't actually show anything. This will be done by our own animator
|
||||
frame.setShowAnimation(() -> {
|
||||
frame.completeShowTransition();
|
||||
});
|
||||
|
||||
// set screen position
|
||||
final javafx.stage.Window owner = notification.owner;
|
||||
if (owner == null) {
|
||||
final Point mouseLocation = MouseInfo.getPointerInfo()
|
||||
.getLocation();
|
||||
|
||||
final GraphicsDevice deviceAtMouse = ScreenUtil.getGraphicsDeviceAt(mouseLocation);
|
||||
|
||||
final Rectangle screenBounds = deviceAtMouse.getDefaultConfiguration()
|
||||
.getBounds();
|
||||
|
||||
/*
|
||||
* If the owner is not set, we work with the whole screen.
|
||||
* EDIT: we use the screen that the mouse is currently on.
|
||||
*/
|
||||
startX = screenBounds.getX();
|
||||
startY = screenBounds.getY();
|
||||
screenWidth = screenBounds.getWidth();
|
||||
screenHeight = screenBounds.getHeight();
|
||||
|
||||
window = Utils.getWindow(null);
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* If the owner is set, we will make the notifications popup
|
||||
* inside its window.
|
||||
*/
|
||||
startX = owner.getX();
|
||||
startY = owner.getY();
|
||||
screenWidth = owner.getWidth();
|
||||
screenHeight = owner.getHeight();
|
||||
window = owner;
|
||||
}
|
||||
|
||||
|
||||
// need to install our CSS
|
||||
if (owner instanceof Stage) {
|
||||
Scene ownerScene = owner.getScene();
|
||||
ownerScene.getStylesheets()
|
||||
.add(org.controlsfx.control.Notifications.class.getResource("notificationpopup.css")
|
||||
.toExternalForm()); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
|
||||
this.position = notification.position;
|
||||
|
||||
|
||||
VBox region = new VBox();
|
||||
final ObservableList<String> styleClass1 = region.getStyleClass();
|
||||
styleClass1.add("notification-bar");
|
||||
styleClass1.addAll(notification.styleClass);
|
||||
|
||||
region.setVisible(true);
|
||||
region.setMinWidth(300);
|
||||
region.setMinHeight(40);
|
||||
|
||||
|
||||
GridPane pane = new GridPane();
|
||||
pane.getStyleClass()
|
||||
.add("pane");
|
||||
pane.setAlignment(Pos.BASELINE_LEFT);
|
||||
region.getChildren()
|
||||
.add(pane);
|
||||
|
||||
// pane.setStyle("-fx-background-color: #2046ff;");
|
||||
|
||||
// title
|
||||
if (notification.title != null && !notification.title.isEmpty()) {
|
||||
javafx.scene.control.Label titleLabel = new javafx.scene.control.Label();
|
||||
titleLabel.getStyleClass()
|
||||
.add("title");
|
||||
titleLabel.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
|
||||
GridPane.setHgrow(titleLabel, Priority.ALWAYS);
|
||||
|
||||
titleLabel.setText(notification.title);
|
||||
pane.add(titleLabel, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
// close button
|
||||
if (!notification.hideCloseButton) {
|
||||
javafx.scene.control.Button closeBtn = new javafx.scene.control.Button();
|
||||
closeBtn.getStyleClass()
|
||||
.setAll("close-button");
|
||||
|
||||
StackPane graphic = new StackPane();
|
||||
graphic.getStyleClass()
|
||||
.setAll("graphic");
|
||||
|
||||
closeBtn.setGraphic(graphic);
|
||||
closeBtn.setMinSize(17, 17);
|
||||
closeBtn.setPrefSize(17, 17);
|
||||
|
||||
GridPane.setMargin(closeBtn, new javafx.geometry.Insets(0, 0, 0, 8));
|
||||
|
||||
// position the close button in the best place, depending on the height
|
||||
double minHeight = pane.minHeight(-1);
|
||||
GridPane.setValignment(closeBtn, minHeight == 40 ? VPos.CENTER : VPos.TOP);
|
||||
|
||||
closeBtn.setOnAction(arg0 -> createHideTimeline(Duration.ZERO).play());
|
||||
|
||||
pane.add(closeBtn, 2, 0, 1, 1);
|
||||
}
|
||||
|
||||
|
||||
// graphic + text area
|
||||
javafx.scene.control.Label label = new javafx.scene.control.Label();
|
||||
label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
|
||||
GridPane.setVgrow(label, Priority.ALWAYS);
|
||||
GridPane.setHgrow(label, Priority.ALWAYS);
|
||||
|
||||
label.setText(notification.text);
|
||||
label.setGraphic(notification.graphic);
|
||||
label.setPadding(new javafx.geometry.Insets(10, 0, 10, 5));
|
||||
pane.add(label, 0, 2);
|
||||
|
||||
|
||||
region.setOnMouseClicked(e -> createHideTimeline(Duration.ZERO).play());
|
||||
|
||||
Scene scene = new Scene(region);
|
||||
scene.getStylesheets()
|
||||
.add(org.controlsfx.control.Notifications.class.getResource("notificationpopup.css")
|
||||
.toExternalForm()); //$NON-NLS-1$
|
||||
frame.setScene(scene);
|
||||
|
||||
frame.sizeToScene();
|
||||
|
||||
// determine location for the popup
|
||||
final Dimension size = frame.getSize();
|
||||
final double barWidth = size.getWidth();
|
||||
final double barHeight = size.getHeight();
|
||||
|
||||
// get anchorX
|
||||
switch (position) {
|
||||
case TOP_LEFT:
|
||||
case CENTER_LEFT:
|
||||
case BOTTOM_LEFT:
|
||||
anchorX = startX + padding;
|
||||
break;
|
||||
|
||||
case TOP_CENTER:
|
||||
case CENTER:
|
||||
case BOTTOM_CENTER:
|
||||
anchorX = startX + (screenWidth / 2.0) - barWidth / 2.0 - padding / 2.0;
|
||||
break;
|
||||
|
||||
default:
|
||||
case TOP_RIGHT:
|
||||
case CENTER_RIGHT:
|
||||
case BOTTOM_RIGHT:
|
||||
anchorX = startX + screenWidth - barWidth - padding;
|
||||
break;
|
||||
}
|
||||
|
||||
// get anchorY
|
||||
switch (position) {
|
||||
case TOP_LEFT:
|
||||
case TOP_CENTER:
|
||||
case TOP_RIGHT:
|
||||
anchorY = padding + startY;
|
||||
break;
|
||||
|
||||
case CENTER_LEFT:
|
||||
case CENTER:
|
||||
case CENTER_RIGHT:
|
||||
anchorY = startY + (screenHeight / 2.0) - barHeight / 2.0 - padding / 2.0;
|
||||
break;
|
||||
|
||||
default:
|
||||
case BOTTOM_LEFT:
|
||||
case BOTTOM_CENTER:
|
||||
case BOTTOM_RIGHT:
|
||||
anchorY = startY + screenHeight - barHeight - padding;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
void show() {
|
||||
this.newX = anchorX;
|
||||
this.newY = anchorY;
|
||||
frame.show(anchorX, anchorY);
|
||||
|
||||
addPopupToMap();
|
||||
|
||||
// begin a timeline to get rid of the popup (default is 5 seconds)
|
||||
// if (notification.hideAfterDuration != Duration.INDEFINITE) {
|
||||
// Timeline timeline = createHideTimeline(popup, growlNotification, p, notification.hideAfterDuration);
|
||||
// timeline.play();
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GrowlPopup that = (GrowlPopup) o;
|
||||
return frame.equals(that.frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return frame.hashCode();
|
||||
}
|
||||
|
||||
void close() {
|
||||
// set it off screen (which is what the close method also does)
|
||||
this.newX = Short.MIN_VALUE;
|
||||
this.newY = Short.MIN_VALUE;
|
||||
|
||||
frame.close();
|
||||
}
|
||||
|
||||
Dimension2D getSize() {
|
||||
return frame.getSize();
|
||||
}
|
||||
|
||||
void animateToTarget(final boolean shouldFadeIn, final double x, final double y) {
|
||||
|
||||
if (shouldFadeIn) {
|
||||
if (frame.getOpacityProperty().getValue() == 0F) {
|
||||
frame.setLocation((int)x, (int)y);
|
||||
Timeline timeline = new Timeline();
|
||||
timeline.getKeyFrames()
|
||||
.addAll(new KeyFrame(Duration.millis(500), new KeyValue(frame.getOpacityProperty(), 1F, Interpolator.LINEAR)));
|
||||
timeline.play();
|
||||
}
|
||||
} else {
|
||||
frame.setLocation((int)x, (int)y);
|
||||
|
||||
// final boolean xEqual = x == frame.getX();
|
||||
// final boolean yEqual = y == frame.getY();
|
||||
//
|
||||
// if (xEqual && yEqual) {
|
||||
// return;
|
||||
// }
|
||||
//Transition t = new Transition() {
|
||||
// {
|
||||
// setCycleDuration(Duration.millis(500));
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected
|
||||
// void interpolate(final double frac) {
|
||||
// final double y1 = frame.getY();
|
||||
// final double distance = ((y-y1) * frac);
|
||||
//
|
||||
// frame.setLocation(x, y1 + distance);
|
||||
// }
|
||||
// };
|
||||
// parallelTransition.getChildren().add(t);
|
||||
}
|
||||
|
||||
// final ObservableList<KeyFrame> keyFrames = animationTimeline.getKeyFrames();
|
||||
// keyFrames.clear();
|
||||
//
|
||||
// if (!xEqual) {
|
||||
// keyFrames.addAll(new KeyFrame(Duration.millis(300), new KeyValue(xProperty, x, Interpolator.EASE_OUT)));
|
||||
// }
|
||||
// if (!yEqual) {
|
||||
// keyFrames.addAll(new KeyFrame(Duration.millis(300), new KeyValue(yProperty, y, Interpolator.EASE_OUT)));
|
||||
// }
|
||||
//
|
||||
// // x/y can change, keep running the animation until it's stable
|
||||
// animationTimeline.setOnFinished(event -> animateToTarget(GrowlPopup.this.newX, GrowlPopup.this.newY));
|
||||
// animationTimeline.playFromStart();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private
|
||||
Timeline createHideTimeline(final Duration startDelay) {
|
||||
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(500), new KeyValue(frame.getOpacityProperty(), 0.0F)));
|
||||
timeline.setDelay(startDelay);
|
||||
timeline.setOnFinished(e -> {
|
||||
close();
|
||||
removePopupFromMap();
|
||||
});
|
||||
|
||||
return timeline;
|
||||
}
|
||||
|
||||
// only called on the JavaFX app thread
|
||||
private
|
||||
void addPopupToMap() {
|
||||
popups.add(this);
|
||||
doAnimation(true);
|
||||
}
|
||||
|
||||
// only called on the JavaFX app thread
|
||||
private
|
||||
void removePopupFromMap() {
|
||||
popups.remove(this);
|
||||
|
||||
if (!popups.isEmpty()) {
|
||||
doAnimation(false);
|
||||
}
|
||||
}
|
||||
|
||||
// only called on the JavaFX app thread
|
||||
private static
|
||||
void doAnimation(boolean shouldFadeIn) {
|
||||
parallelTransition.stop();
|
||||
parallelTransition.getChildren()
|
||||
.clear();
|
||||
|
||||
|
||||
// the logic for this, is that the first popup in place, doesn't move. EVERY other popup after it will be moved
|
||||
// this behavior trickles down to the remaining popups, until all popups have been assigned new locations
|
||||
|
||||
final int length = popups.size();
|
||||
final GrowlPopup[] copies = popups.toArray(new GrowlPopup[length]);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
final GrowlPopup popup = copies[i];
|
||||
final boolean isShowFromTop = isShowFromTop(popup.position);
|
||||
|
||||
final Dimension2D size = popup.getSize();
|
||||
final double x = popup.newX;
|
||||
final double y = popup.newY;
|
||||
final double width = size.getWidth();
|
||||
final double height = size.getHeight();
|
||||
|
||||
if (isShowFromTop) {
|
||||
for (int j = i+1; j < length; j++) {
|
||||
final GrowlPopup copy = copies[j];
|
||||
|
||||
final Dimension2D size1 = copy.getSize();
|
||||
final double x1 = copy.newX;
|
||||
final double y1 = copy.newY;
|
||||
final double width1 = size1.getWidth();
|
||||
final double height1 = size1.getHeight();
|
||||
|
||||
if (intersectRect(x, y, width, height, x1, y1, width1, height1)) {
|
||||
copy.newY = y + height + 10;
|
||||
}
|
||||
}
|
||||
|
||||
popup.animateToTarget(shouldFadeIn, popup.newX, popup.newY);
|
||||
}
|
||||
|
||||
//
|
||||
// // first one is always as base location with padding
|
||||
// if (i == 0) {
|
||||
// newY = 30 + _popup.startY;
|
||||
// }
|
||||
// else {
|
||||
// // we add a little bit of padding, so they are not on top of eachother
|
||||
// newY += popupHeight + 10;
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// if (i == size - 1) {
|
||||
//// newY = changedPopup.getTargetY() - popupHeight;
|
||||
// }
|
||||
// else {
|
||||
// newY -= popupHeight;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (newY < 0) {
|
||||
// System.err.println("closing");
|
||||
// _popup.close();
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// popup.animateToTarget(popup.anchorX, newY);
|
||||
}
|
||||
|
||||
if (!parallelTransition.getChildren().isEmpty()) {
|
||||
// parallelTransition.play();
|
||||
}
|
||||
}
|
||||
|
||||
static boolean intersectRect(double x1, double y1, double w1, double h1, double x2, double y2, double w2, double h2) {
|
||||
return intersectRange(x1, x1+w1, x2, x2+w2) && intersectRange(y1, y1+h1, y2, y2+h2);
|
||||
}
|
||||
static boolean intersectRange(double ax1, double ax2, double bx1, double bx2) {
|
||||
return Math.max(ax1, bx1) <= Math.min(ax2, bx2);
|
||||
}
|
||||
|
||||
private static
|
||||
boolean isShowFromTop(final Pos p) {
|
||||
switch (p) {
|
||||
case TOP_LEFT:
|
||||
case TOP_CENTER:
|
||||
case TOP_RIGHT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package dorkbox.util.javafx;
|
||||
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public
|
||||
class GrowlPopupViaSwing extends StageViaSwing {
|
||||
|
||||
private static
|
||||
AtomicInteger ID = new AtomicInteger(0);
|
||||
|
||||
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
|
||||
static
|
||||
GrowlPopupViaSwing create(final Image icon, final String title) {
|
||||
final GrowlPopupViaSwing[] returnVal = new GrowlPopupViaSwing[1];
|
||||
|
||||
// this MUST happen on the EDT!
|
||||
SwingUtil.invokeAndWait(() -> {
|
||||
synchronized (returnVal) {
|
||||
returnVal[0] = new GrowlPopupViaSwing(icon, title, ID.getAndIncrement());
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (returnVal) {
|
||||
return returnVal[0];
|
||||
}
|
||||
}
|
||||
|
||||
private final int id;
|
||||
|
||||
|
||||
|
||||
GrowlPopupViaSwing(final Image icon, final String title, final int ID) {
|
||||
super();
|
||||
|
||||
this.id = ID;
|
||||
|
||||
frame.setAlwaysOnTop(true);
|
||||
frame.setResizable(false);
|
||||
frame.setIconImage(icon);
|
||||
frame.setTitle(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GrowlPopupViaSwing that = (GrowlPopupViaSwing) o;
|
||||
|
||||
return id == that.id;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
int hashCode() {
|
||||
return id;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user