Initial import to kotlin
This commit is contained in:
parent
7ea70989a8
commit
a218bfb8db
|
@ -13,9 +13,8 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
public
|
|
||||||
interface ActionHandler<T> {
|
interface ActionHandler<T> {
|
||||||
void handle(T value);
|
fun handle(value: T)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,115 +13,86 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
import java.awt.Component;
|
import dorkbox.util.SwingUtil
|
||||||
import java.awt.Frame;
|
import java.awt.Frame
|
||||||
import java.awt.event.ComponentEvent;
|
import java.awt.event.ComponentEvent
|
||||||
import java.awt.event.ComponentListener;
|
import java.awt.event.ComponentListener
|
||||||
import java.awt.event.WindowEvent;
|
import java.awt.event.WindowStateListener
|
||||||
import java.awt.event.WindowStateListener;
|
import javax.swing.ImageIcon
|
||||||
|
import javax.swing.JFrame
|
||||||
|
import javax.swing.JPanel
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
// this is a child to a Jframe/window (instead of globally to the screen).
|
||||||
import javax.swing.JFrame;
|
class AsApplication internal constructor(private val notification: Notify, image: ImageIcon?, private val appWindow: JFrame, theme: Theme) : INotify {
|
||||||
import javax.swing.JPanel;
|
companion object {
|
||||||
|
private const val glassPanePrefix = "dorkbox.notify"
|
||||||
|
}
|
||||||
|
|
||||||
import dorkbox.util.SwingUtil;
|
private val look: LookAndFeel
|
||||||
|
private val notifyCanvas: NotifyCanvas
|
||||||
|
private val parentListener: ComponentListener
|
||||||
|
private val windowStateListener: WindowStateListener
|
||||||
|
private var glassPane: JPanel? = null
|
||||||
|
|
||||||
// this is a child to a Jframe/window (instead of globally to the screen)
|
// NOTE: this is on the swing EDT
|
||||||
@SuppressWarnings({"Duplicates", "FieldCanBeLocal", "WeakerAccess", "DanglingJavadoc"})
|
init {
|
||||||
public
|
notifyCanvas = NotifyCanvas(this, notification, image, theme)
|
||||||
class AsApplication implements INotify {
|
look = LookAndFeel(this, appWindow, notifyCanvas, notification, appWindow.bounds, false)
|
||||||
private final LookAndFeel look;
|
|
||||||
private final Notify notification;
|
|
||||||
private final NotifyCanvas notifyCanvas;
|
|
||||||
private final JFrame appWindow;
|
|
||||||
|
|
||||||
private final ComponentListener parentListener;
|
|
||||||
private final WindowStateListener windowStateListener;
|
|
||||||
|
|
||||||
private static final String glassPanePrefix = "dorkbox.notify";
|
|
||||||
|
|
||||||
private JPanel glassPane;
|
|
||||||
|
|
||||||
// this is on the swing EDT
|
|
||||||
@SuppressWarnings("NumericCastThatLosesPrecision")
|
|
||||||
AsApplication(final Notify notification, final ImageIcon image, final JFrame appWindow, final Theme theme) {
|
|
||||||
this.notification = notification;
|
|
||||||
this.notifyCanvas = new NotifyCanvas(this, notification, image, theme);
|
|
||||||
this.appWindow = appWindow;
|
|
||||||
|
|
||||||
look = new LookAndFeel(this, appWindow, notifyCanvas, notification, appWindow.getBounds(), false);
|
|
||||||
|
|
||||||
// this makes sure that our notify canvas stay anchored to the parent window (if it's hidden/shown/moved/etc)
|
// this makes sure that our notify canvas stay anchored to the parent window (if it's hidden/shown/moved/etc)
|
||||||
parentListener = new ComponentListener() {
|
parentListener = object : ComponentListener {
|
||||||
@Override
|
override fun componentShown(e: ComponentEvent) {
|
||||||
public
|
look.reLayout(appWindow.bounds)
|
||||||
void componentShown(final ComponentEvent e) {
|
|
||||||
look.reLayout(appWindow.getBounds());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun componentHidden(e: ComponentEvent) {}
|
||||||
public
|
|
||||||
void componentHidden(final ComponentEvent e) {
|
override fun componentResized(e: ComponentEvent) {
|
||||||
|
look.reLayout(appWindow.bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun componentMoved(e: ComponentEvent) {}
|
||||||
public
|
|
||||||
void componentResized(final ComponentEvent e) {
|
|
||||||
look.reLayout(appWindow.getBounds());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
windowStateListener = WindowStateListener { e ->
|
||||||
public
|
val state = e.newState
|
||||||
void componentMoved(final ComponentEvent e) {
|
if (state and Frame.ICONIFIED == 0) {
|
||||||
}
|
look.reLayout(appWindow.bounds)
|
||||||
};
|
|
||||||
|
|
||||||
windowStateListener = new WindowStateListener() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void windowStateChanged(WindowEvent e) {
|
|
||||||
int state = e.getNewState();
|
|
||||||
if ((state & Frame.ICONIFIED) == 0) {
|
|
||||||
look.reLayout(appWindow.getBounds());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
appWindow.addWindowStateListener(windowStateListener)
|
||||||
|
appWindow.addComponentListener(parentListener)
|
||||||
|
|
||||||
|
|
||||||
appWindow.addWindowStateListener(windowStateListener);
|
val glassPane_ = appWindow.glassPane
|
||||||
appWindow.addComponentListener(parentListener);
|
if (glassPane_ is JPanel) {
|
||||||
|
glassPane = glassPane_
|
||||||
Component glassPane_ = appWindow.getGlassPane();
|
val name = glassPane_.name
|
||||||
if (glassPane_ instanceof JPanel) {
|
if (name != glassPanePrefix) {
|
||||||
glassPane = (JPanel) glassPane_;
|
|
||||||
String name = glassPane.getName();
|
|
||||||
|
|
||||||
if (!name.equals(glassPanePrefix)) {
|
|
||||||
// We just tweak the already existing glassPane, instead of replacing it with our own
|
// We just tweak the already existing glassPane, instead of replacing it with our own
|
||||||
// glassPane = new JPanel();
|
// glassPane = new JPanel();
|
||||||
glassPane.setLayout(null);
|
glassPane_.layout = null
|
||||||
glassPane.setName(glassPanePrefix);
|
glassPane_.name = glassPanePrefix
|
||||||
// glassPane.setSize(appWindow.getSize());
|
// glassPane.setSize(appWindow.getSize());
|
||||||
// glassPane.setOpaque(false);
|
// glassPane.setOpaque(false);
|
||||||
// appWindow.setGlassPane(glassPane);
|
// appWindow.setGlassPane(glassPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
glassPane.add(notifyCanvas);
|
glassPane_.add(notifyCanvas)
|
||||||
|
|
||||||
if (!glassPane.isVisible()) {
|
if (!glassPane_.isVisible) {
|
||||||
glassPane.setVisible(true);
|
glassPane_.isVisible = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Not able to add notification to custom glassPane");
|
System.err.println("Not able to add notification to custom glassPane")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun onClick(x: Int, y: Int) {
|
||||||
public
|
look.onClick(x, y)
|
||||||
void onClick(final int x, final int y) {
|
|
||||||
look.onClick(x, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,51 +101,39 @@ class AsApplication implements INotify {
|
||||||
* @param durationInMillis now long it will shake
|
* @param durationInMillis now long it will shake
|
||||||
* @param amplitude a measure of how much it needs to shake. 4 is a small amount of shaking, 10 is a lot.
|
* @param amplitude a measure of how much it needs to shake. 4 is a small amount of shaking, 10 is a lot.
|
||||||
*/
|
*/
|
||||||
@Override
|
override fun shake(durationInMillis: Int, amplitude: Int) {
|
||||||
public
|
look.shake(durationInMillis, amplitude)
|
||||||
void shake(final int durationInMillis, final int amplitude) {
|
|
||||||
look.shake(durationInMillis, amplitude);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun setVisible(visible: Boolean) {
|
||||||
public
|
|
||||||
void setVisible(final boolean visible) {
|
|
||||||
// this is because the order of operations are different based upon visibility.
|
// this is because the order of operations are different based upon visibility.
|
||||||
look.updatePositionsPre(visible);
|
look.updatePositionsPre(visible)
|
||||||
look.updatePositionsPost(visible);
|
look.updatePositionsPost(visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun close() {
|
||||||
public
|
|
||||||
void close() {
|
|
||||||
// this must happen in the Swing EDT. This is usually called by the active renderer
|
// this must happen in the Swing EDT. This is usually called by the active renderer
|
||||||
SwingUtil.invokeLater(new Runnable() {
|
SwingUtil.invokeLater {
|
||||||
@Override
|
look.close()
|
||||||
public
|
glassPane!!.remove(notifyCanvas)
|
||||||
void run() {
|
appWindow.removeWindowStateListener(windowStateListener)
|
||||||
look.close();
|
appWindow.removeComponentListener(parentListener)
|
||||||
|
|
||||||
glassPane.remove(notifyCanvas);
|
var found = false
|
||||||
|
val components = glassPane!!.components
|
||||||
appWindow.removeWindowStateListener(windowStateListener);
|
for (component in components) {
|
||||||
appWindow.removeComponentListener(parentListener);
|
if (component is NotifyCanvas) {
|
||||||
|
found = true
|
||||||
boolean found = false;
|
break
|
||||||
Component[] components = glassPane.getComponents();
|
|
||||||
for (Component component : components) {
|
|
||||||
if (component instanceof NotifyCanvas) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
// hide the glass pane if there are no more notifications on it.
|
// hide the glass pane if there are no more notifications on it.
|
||||||
glassPane.setVisible(false);
|
glassPane!!.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.onClose();
|
notification.onClose()
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,86 +13,66 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
import java.awt.Dimension;
|
import dorkbox.util.ScreenUtil
|
||||||
import java.awt.GraphicsDevice;
|
import dorkbox.util.SwingUtil
|
||||||
import java.awt.GraphicsEnvironment;
|
import java.awt.Dimension
|
||||||
import java.awt.MouseInfo;
|
import java.awt.GraphicsEnvironment
|
||||||
import java.awt.Point;
|
import java.awt.MouseInfo
|
||||||
import java.awt.Rectangle;
|
import javax.swing.ImageIcon
|
||||||
|
import javax.swing.JWindow
|
||||||
import javax.swing.ImageIcon;
|
|
||||||
import javax.swing.JWindow;
|
|
||||||
|
|
||||||
import dorkbox.util.ScreenUtil;
|
|
||||||
import dorkbox.util.SwingUtil;
|
|
||||||
|
|
||||||
// we can't use regular popup, because if we have no owner, it won't work!
|
// we can't use regular popup, because if we have no owner, it won't work!
|
||||||
// instead, we just create a JWindow and use it to hold our content
|
// instead, we just create a JWindow and use it to hold our content
|
||||||
@SuppressWarnings({"Duplicates", "FieldCanBeLocal", "WeakerAccess", "DanglingJavadoc"})
|
class AsDesktop internal constructor(private val notification: Notify, image: ImageIcon?, theme: Theme) : JWindow(), INotify {
|
||||||
public
|
companion object {
|
||||||
class AsDesktop extends JWindow implements INotify {
|
private const val serialVersionUID = 1L
|
||||||
private static final long serialVersionUID = 1L;
|
}
|
||||||
|
|
||||||
private final LookAndFeel look;
|
|
||||||
private final Notify notification;
|
|
||||||
|
|
||||||
|
private val look: LookAndFeel
|
||||||
|
|
||||||
// this is on the swing EDT
|
// this is on the swing EDT
|
||||||
@SuppressWarnings("NumericCastThatLosesPrecision")
|
init {
|
||||||
AsDesktop(final Notify notification, final ImageIcon image, final Theme theme) {
|
isAlwaysOnTop = true
|
||||||
this.notification = notification;
|
|
||||||
|
|
||||||
setAlwaysOnTop(true);
|
preferredSize = Dimension(WIDTH, HEIGHT)
|
||||||
|
maximumSize = preferredSize
|
||||||
|
minimumSize = preferredSize
|
||||||
|
|
||||||
final Dimension preferredSize = new Dimension(WIDTH, HEIGHT);
|
setSize(NotifyCanvas.WIDTH, NotifyCanvas.HEIGHT)
|
||||||
setPreferredSize(preferredSize);
|
setLocation(Short.MIN_VALUE.toInt(), Short.MIN_VALUE.toInt())
|
||||||
setMaximumSize(preferredSize);
|
|
||||||
setMinimumSize(preferredSize);
|
|
||||||
setSize(NotifyCanvas.WIDTH, NotifyCanvas.HEIGHT);
|
|
||||||
setLocation(Short.MIN_VALUE, Short.MIN_VALUE);
|
|
||||||
|
|
||||||
Rectangle bounds;
|
val device = if (notification.screenNumber == Short.MIN_VALUE.toInt()) {
|
||||||
GraphicsDevice device;
|
|
||||||
|
|
||||||
if (notification.screenNumber == Short.MIN_VALUE) {
|
|
||||||
// set screen position based on mouse
|
// set screen position based on mouse
|
||||||
Point mouseLocation = MouseInfo.getPointerInfo()
|
val mouseLocation = MouseInfo.getPointerInfo().location
|
||||||
.getLocation();
|
ScreenUtil.getMonitorAtLocation(mouseLocation)
|
||||||
|
} else {
|
||||||
device = ScreenUtil.getMonitorAtLocation(mouseLocation);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// set screen position based on specified screen
|
// set screen position based on specified screen
|
||||||
int screenNumber = notification.screenNumber;
|
var screenNumber = notification.screenNumber
|
||||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
||||||
GraphicsDevice screenDevices[] = ge.getScreenDevices();
|
val screenDevices = ge.screenDevices
|
||||||
|
|
||||||
if (screenNumber < 0) {
|
if (screenNumber < 0) {
|
||||||
screenNumber = 0;
|
screenNumber = 0
|
||||||
}
|
} else if (screenNumber > screenDevices.size - 1) {
|
||||||
else if (screenNumber > screenDevices.length - 1) {
|
screenNumber = screenDevices.size - 1
|
||||||
screenNumber = screenDevices.length - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
device = screenDevices[screenNumber];
|
screenDevices[screenNumber]
|
||||||
}
|
}
|
||||||
|
|
||||||
bounds = device.getDefaultConfiguration()
|
val bounds = device.defaultConfiguration.bounds
|
||||||
.getBounds();
|
|
||||||
|
|
||||||
|
|
||||||
NotifyCanvas notifyCanvas = new NotifyCanvas(this, notification, image, theme);
|
val notifyCanvas = NotifyCanvas(this, notification, image, theme)
|
||||||
getContentPane().add(notifyCanvas);
|
contentPane.add(notifyCanvas)
|
||||||
|
|
||||||
look = new LookAndFeel(this, this, notifyCanvas, notification, bounds, true);
|
look = LookAndFeel(this, this, notifyCanvas, notification, bounds, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun onClick(x: Int, y: Int) {
|
||||||
public
|
look.onClick(x, y)
|
||||||
void onClick(final int x, final int y) {
|
|
||||||
look.onClick(x, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,55 +81,41 @@ class AsDesktop extends JWindow implements INotify {
|
||||||
* @param durationInMillis now long it will shake
|
* @param durationInMillis now long it will shake
|
||||||
* @param amplitude a measure of how much it needs to shake. 4 is a small amount of shaking, 10 is a lot.
|
* @param amplitude a measure of how much it needs to shake. 4 is a small amount of shaking, 10 is a lot.
|
||||||
*/
|
*/
|
||||||
@Override
|
override fun shake(durationInMillis: Int, amplitude: Int) {
|
||||||
public
|
look.shake(durationInMillis, amplitude)
|
||||||
void shake(final int durationInMillis, final int amplitude) {
|
|
||||||
look.shake(durationInMillis, amplitude);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun setVisible(visible: Boolean) {
|
||||||
public
|
|
||||||
void setVisible(final boolean visible) {
|
|
||||||
// was it already visible?
|
// was it already visible?
|
||||||
if (visible == isVisible()) {
|
if (visible == isVisible) {
|
||||||
// prevent "double setting" visible state
|
// prevent "double setting" visible state
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is because the order of operations are different based upon visibility.
|
// this is because the order of operations are different based upon visibility.
|
||||||
look.updatePositionsPre(visible);
|
look.updatePositionsPre(visible)
|
||||||
|
super.setVisible(visible)
|
||||||
super.setVisible(visible);
|
|
||||||
|
|
||||||
// this is because the order of operations are different based upon visibility.
|
// this is because the order of operations are different based upon visibility.
|
||||||
look.updatePositionsPost(visible);
|
look.updatePositionsPost(visible)
|
||||||
|
|
||||||
if (visible) {
|
if (visible) {
|
||||||
this.toFront();
|
toFront()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setVisible(false) with any extra logic
|
// setVisible(false) with any extra logic
|
||||||
void doHide() {
|
fun doHide() {
|
||||||
super.setVisible(false);
|
super.setVisible(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun close() {
|
||||||
public
|
|
||||||
void close() {
|
|
||||||
// this must happen in the Swing EDT. This is usually called by the active renderer
|
// this must happen in the Swing EDT. This is usually called by the active renderer
|
||||||
SwingUtil.invokeLater(new Runnable() {
|
SwingUtil.invokeLater {
|
||||||
@Override
|
doHide()
|
||||||
public
|
look.close()
|
||||||
void run() {
|
removeAll()
|
||||||
doHide();
|
dispose()
|
||||||
look.close();
|
notification.onClose()
|
||||||
|
}
|
||||||
removeAll();
|
|
||||||
dispose();
|
|
||||||
|
|
||||||
notification.onClose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,20 +13,14 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent
|
||||||
|
|
||||||
class ClickAdapter extends MouseAdapter {
|
internal class ClickAdapter : MouseAdapter() {
|
||||||
|
override fun mouseReleased(e: MouseEvent) {
|
||||||
ClickAdapter() {
|
val parent = (e.source as NotifyCanvas).parent
|
||||||
}
|
parent.onClick(e.x, e.y)
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void mouseReleased(final MouseEvent e) {
|
|
||||||
INotify parent = ((NotifyCanvas) e.getSource()).parent;
|
|
||||||
parent.onClick(e.getX(), e.getY());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,11 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
public
|
|
||||||
interface INotify {
|
interface INotify {
|
||||||
void close();
|
fun close()
|
||||||
|
fun shake(durationInMillis: Int, amplitude: Int)
|
||||||
void shake(int durationInMillis, int amplitude);
|
fun setVisible(visible: Boolean)
|
||||||
|
fun onClick(x: Int, y: Int)
|
||||||
void setVisible(boolean b);
|
|
||||||
|
|
||||||
void onClick(int x, int y);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,500 +13,387 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
import java.awt.Point;
|
import dorkbox.swingActiveRender.ActionHandlerLong
|
||||||
import java.awt.Rectangle;
|
import dorkbox.swingActiveRender.SwingActiveRender
|
||||||
import java.awt.Window;
|
import dorkbox.tweenEngine.Tween
|
||||||
import java.awt.event.MouseAdapter;
|
import dorkbox.tweenEngine.TweenCallback.Events.COMPLETE
|
||||||
import java.util.HashMap;
|
import dorkbox.tweenEngine.TweenEngine.Companion.create
|
||||||
import java.util.Iterator;
|
import dorkbox.tweenEngine.TweenEquations
|
||||||
import java.util.Map;
|
import dorkbox.util.ScreenUtil
|
||||||
import java.util.Random;
|
import java.awt.Point
|
||||||
|
import java.awt.Rectangle
|
||||||
|
import java.awt.Window
|
||||||
|
import java.awt.event.MouseAdapter
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
import dorkbox.swingActiveRender.ActionHandlerLong;
|
internal class LookAndFeel(
|
||||||
import dorkbox.swingActiveRender.SwingActiveRender;
|
private val notify: INotify,
|
||||||
import dorkbox.tweenEngine.BaseTween;
|
private val parent: Window,
|
||||||
import dorkbox.tweenEngine.Tween;
|
private val notifyCanvas: NotifyCanvas,
|
||||||
import dorkbox.tweenEngine.TweenCallback;
|
private val notification: Notify,
|
||||||
import dorkbox.tweenEngine.TweenEngine;
|
parentBounds: Rectangle,
|
||||||
import dorkbox.tweenEngine.TweenEquations;
|
private val isDesktopNotification: Boolean
|
||||||
import dorkbox.util.ScreenUtil;
|
) {
|
||||||
|
companion object {
|
||||||
|
private val popups: MutableMap<String?, PopupList> = HashMap()
|
||||||
|
|
||||||
@SuppressWarnings({"FieldCanBeLocal"})
|
// access is only from a single thread ever, so unsafe is preferred.
|
||||||
class LookAndFeel {
|
val animation = create().unsafe().build()
|
||||||
private static final Map<String, PopupList> popups = new HashMap<String, PopupList>();
|
|
||||||
|
|
||||||
static final TweenEngine animation = TweenEngine.Companion.create()
|
val accessor = NotifyAccessor()
|
||||||
.unsafe() // access is only from a single thread ever, so unsafe is preferred.
|
|
||||||
.build();
|
|
||||||
|
|
||||||
static final NotifyAccessor accessor = new NotifyAccessor();
|
|
||||||
private static final ActionHandlerLong frameStartHandler;
|
|
||||||
|
|
||||||
|
|
||||||
static {
|
|
||||||
// this is for updating the tween engine during active-rendering
|
// this is for updating the tween engine during active-rendering
|
||||||
frameStartHandler = new ActionHandlerLong() {
|
private val frameStartHandler = ActionHandlerLong { deltaInNanos -> animation.update(deltaInNanos) }
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void handle(final long deltaInNanos) {
|
|
||||||
LookAndFeel.animation.update(deltaInNanos);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static final int SPACER = 10;
|
const val SPACER = 10
|
||||||
static final int MARGIN = 20;
|
const val MARGIN = 20
|
||||||
|
|
||||||
private static final java.awt.event.WindowAdapter windowListener = new WindowAdapter();
|
private val windowListener: java.awt.event.WindowAdapter = WindowAdapter()
|
||||||
private static final MouseAdapter mouseListener = new ClickAdapter();
|
private val mouseListener: MouseAdapter = ClickAdapter()
|
||||||
|
private val RANDOM = Random()
|
||||||
|
private val MOVE_DURATION = Notify.MOVE_DURATION
|
||||||
|
|
||||||
private static final Random RANDOM = new Random();
|
private fun getAnchorX(position: Pos, bounds: Rectangle, isDesktop: Boolean): Int {
|
||||||
|
|
||||||
private static final float MOVE_DURATION = Notify.MOVE_DURATION;
|
|
||||||
private final boolean isDesktopNotification;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private volatile int anchorX;
|
|
||||||
private volatile int anchorY;
|
|
||||||
|
|
||||||
|
|
||||||
private final INotify notify;
|
|
||||||
private final Window parent;
|
|
||||||
private final NotifyCanvas notifyCanvas;
|
|
||||||
|
|
||||||
private final float hideAfterDurationInSeconds;
|
|
||||||
private final Pos position;
|
|
||||||
|
|
||||||
// this is used in combination with position, so that we can track which screen and what position a popup is in
|
|
||||||
private final String idAndPosition;
|
|
||||||
private int popupIndex;
|
|
||||||
|
|
||||||
private volatile Tween tween = null;
|
|
||||||
private volatile Tween hideTween = null;
|
|
||||||
|
|
||||||
private final ActionHandler<Notify> onGeneralAreaClickAction;
|
|
||||||
|
|
||||||
LookAndFeel(final INotify notify, final Window parent,
|
|
||||||
final NotifyCanvas notifyCanvas,
|
|
||||||
final Notify notification,
|
|
||||||
final Rectangle parentBounds,
|
|
||||||
final boolean isDesktopNotification) {
|
|
||||||
|
|
||||||
this.notify = notify;
|
|
||||||
this.parent = parent;
|
|
||||||
this.notifyCanvas = notifyCanvas;
|
|
||||||
this.isDesktopNotification = isDesktopNotification;
|
|
||||||
|
|
||||||
|
|
||||||
if (isDesktopNotification) {
|
|
||||||
parent.addWindowListener(windowListener);
|
|
||||||
}
|
|
||||||
notifyCanvas.addMouseListener(mouseListener);
|
|
||||||
|
|
||||||
hideAfterDurationInSeconds = notification.hideAfterDurationInMillis / 1000.0F;
|
|
||||||
position = notification.position;
|
|
||||||
|
|
||||||
if (notification.onGeneralAreaClickAction != null) {
|
|
||||||
onGeneralAreaClickAction = new ActionHandler<Notify>() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void handle(final Notify value) {
|
|
||||||
notification.onGeneralAreaClickAction.handle(notification);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
onGeneralAreaClickAction = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDesktopNotification) {
|
|
||||||
Point point = new Point((int) parentBounds.getX(), ((int) parentBounds.getY()));
|
|
||||||
idAndPosition = ScreenUtil.getMonitorNumberAtLocation(point) + ":" + position;
|
|
||||||
} else {
|
|
||||||
idAndPosition = parent.getName() + ":" + position;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
anchorX = getAnchorX(position, parentBounds, isDesktopNotification);
|
|
||||||
anchorY = getAnchorY(position, parentBounds, isDesktopNotification);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onClick(final int x, final int y) {
|
|
||||||
// Check - we were over the 'X' (and thus no notify), or was it in the general area?
|
|
||||||
|
|
||||||
// reasonable position for detecting mouse over
|
|
||||||
if (!notifyCanvas.isCloseButton(x, y)) {
|
|
||||||
// only call the general click handler IF we click in the general area!
|
|
||||||
if (onGeneralAreaClickAction != null) {
|
|
||||||
onGeneralAreaClickAction.handle(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we always close the notification popup
|
|
||||||
notify.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// only called from an application
|
|
||||||
void reLayout(final Rectangle bounds) {
|
|
||||||
// when the parent window moves, we stop all animation and snap the popup into place. This simplifies logic greatly
|
|
||||||
anchorX = getAnchorX(position, bounds, isDesktopNotification);
|
|
||||||
anchorY = getAnchorY(position, bounds, isDesktopNotification);
|
|
||||||
|
|
||||||
boolean growDown = growDown(this);
|
|
||||||
|
|
||||||
if (tween != null) {
|
|
||||||
tween.cancel(); // cancel does its thing on the next tick of animation cycle
|
|
||||||
tween = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int changedY;
|
|
||||||
if (popupIndex == 0) {
|
|
||||||
changedY = anchorY;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
synchronized (popups) {
|
|
||||||
String id = idAndPosition;
|
|
||||||
|
|
||||||
PopupList looks = popups.get(id);
|
|
||||||
if (looks != null) {
|
|
||||||
if (growDown) {
|
|
||||||
changedY = anchorY + (popupIndex * (NotifyCanvas.HEIGHT + SPACER));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
changedY = anchorY - (popupIndex * (NotifyCanvas.HEIGHT + SPACER));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
changedY = anchorY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setLocation(anchorX, changedY);
|
|
||||||
}
|
|
||||||
|
|
||||||
void close() {
|
|
||||||
if (hideTween != null) {
|
|
||||||
hideTween.cancel();
|
|
||||||
hideTween = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tween != null) {
|
|
||||||
tween.cancel();
|
|
||||||
tween = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDesktopNotification) {
|
|
||||||
parent.removeWindowListener(windowListener);
|
|
||||||
}
|
|
||||||
parent.removeMouseListener(mouseListener);
|
|
||||||
|
|
||||||
updatePositionsPre(false);
|
|
||||||
updatePositionsPost(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void shake(final int durationInMillis, final int amplitude) {
|
|
||||||
int i1 = RANDOM.nextInt((amplitude << 2) + 1) - amplitude;
|
|
||||||
int i2 = RANDOM.nextInt((amplitude << 2) + 1) - amplitude;
|
|
||||||
|
|
||||||
i1 = i1 >> 2;
|
|
||||||
i2 = i2 >> 2;
|
|
||||||
|
|
||||||
// make sure it always moves by some amount
|
|
||||||
if (i1 < 0) {
|
|
||||||
i1 -= amplitude >> 2;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
i1 += amplitude >> 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i2 < 0) {
|
|
||||||
i2 -= amplitude >> 2;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
i2 += amplitude >> 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int count = durationInMillis / 50;
|
|
||||||
// make sure we always end the animation where we start
|
|
||||||
if ((count & 1) == 0) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
animation.to(this, NotifyAccessor.X_Y_POS, accessor, 0.05F)
|
|
||||||
.targetRelative(i1, i2)
|
|
||||||
.repeatAutoReverse(count, 0)
|
|
||||||
.ease(TweenEquations.Linear)
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setY(final int y) {
|
|
||||||
if (isDesktopNotification) {
|
|
||||||
parent.setLocation(parent.getX(), y);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
notifyCanvas.setLocation(notifyCanvas.getX(), y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int getY() {
|
|
||||||
if (isDesktopNotification) {
|
|
||||||
return parent.getY();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return notifyCanvas.getY();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int getX() {
|
|
||||||
if (isDesktopNotification) {
|
|
||||||
return parent.getX();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return notifyCanvas.getX();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setLocation(final int x, final int y) {
|
|
||||||
if (isDesktopNotification) {
|
|
||||||
parent.setLocation(x, y);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
notifyCanvas.setLocation(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static
|
|
||||||
int getAnchorX(final Pos position, final Rectangle bounds, boolean isDesktop) {
|
|
||||||
// we use the screen that the mouse is currently on.
|
// we use the screen that the mouse is currently on.
|
||||||
final int startX;
|
val startX = if (isDesktop) {
|
||||||
if (isDesktop) {
|
bounds.getX().toInt()
|
||||||
startX = (int) bounds.getX();
|
|
||||||
} else {
|
} else {
|
||||||
startX = 0;
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
final int screenWidth = (int) bounds.getWidth();
|
val screenWidth = bounds.getWidth().toInt()
|
||||||
|
return when (position) {
|
||||||
// determine location for the popup
|
Pos.TOP_LEFT, Pos.BOTTOM_LEFT -> MARGIN + startX
|
||||||
// get anchorX
|
Pos.CENTER -> startX + screenWidth / 2 - NotifyCanvas.WIDTH / 2 - MARGIN / 2
|
||||||
switch (position) {
|
Pos.TOP_RIGHT, Pos.BOTTOM_RIGHT -> startX + screenWidth - NotifyCanvas.WIDTH - MARGIN
|
||||||
case TOP_LEFT:
|
|
||||||
case BOTTOM_LEFT:
|
|
||||||
return MARGIN + startX;
|
|
||||||
|
|
||||||
case CENTER:
|
|
||||||
return startX + (screenWidth / 2) - NotifyCanvas.WIDTH / 2 - MARGIN / 2;
|
|
||||||
|
|
||||||
case TOP_RIGHT:
|
|
||||||
case BOTTOM_RIGHT:
|
|
||||||
return startX + screenWidth - NotifyCanvas.WIDTH - MARGIN;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("Unknown position. '" + position + "'");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static
|
private fun getAnchorY(position: Pos, bounds: Rectangle, isDesktop: Boolean): Int {
|
||||||
int getAnchorY(final Pos position, final Rectangle bounds, final boolean isDesktop) {
|
val startY = if (isDesktop) {
|
||||||
final int startY;
|
bounds.getY().toInt()
|
||||||
if (isDesktop) {
|
|
||||||
startY = (int) bounds.getY();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
startY = 0;
|
|
||||||
}
|
|
||||||
final int screenHeight = (int) bounds.getHeight();
|
|
||||||
|
|
||||||
// get anchorY
|
|
||||||
switch (position) {
|
|
||||||
case TOP_LEFT:
|
|
||||||
case TOP_RIGHT:
|
|
||||||
return startY + MARGIN;
|
|
||||||
|
|
||||||
case CENTER:
|
|
||||||
return startY + (screenHeight / 2) - NotifyCanvas.HEIGHT / 2 - MARGIN / 2 - SPACER;
|
|
||||||
|
|
||||||
case BOTTOM_LEFT:
|
|
||||||
case BOTTOM_RIGHT:
|
|
||||||
if (isDesktop) {
|
|
||||||
return startY + screenHeight - NotifyCanvas.HEIGHT - MARGIN;
|
|
||||||
} else {
|
} else {
|
||||||
return startY + screenHeight - NotifyCanvas.HEIGHT - MARGIN - SPACER * 2;
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
val screenHeight = bounds.getHeight().toInt()
|
||||||
throw new RuntimeException("Unknown position. '" + position + "'");
|
return when (position) {
|
||||||
|
Pos.TOP_LEFT, Pos.TOP_RIGHT -> startY + MARGIN
|
||||||
|
Pos.CENTER -> startY + screenHeight / 2 - NotifyCanvas.HEIGHT / 2 - MARGIN / 2 - SPACER
|
||||||
|
Pos.BOTTOM_LEFT, Pos.BOTTOM_RIGHT -> if (isDesktop) {
|
||||||
|
startY + screenHeight - NotifyCanvas.HEIGHT - MARGIN
|
||||||
|
} else {
|
||||||
|
screenHeight - NotifyCanvas.HEIGHT - MARGIN - SPACER * 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// only called on the swing EDT thread
|
// only called on the swing EDT thread
|
||||||
private static
|
private fun addPopupToMap(sourceLook: LookAndFeel) {
|
||||||
void addPopupToMap(final LookAndFeel sourceLook) {
|
|
||||||
synchronized(popups) {
|
synchronized(popups) {
|
||||||
String id = sourceLook.idAndPosition;
|
val id = sourceLook.idAndPosition
|
||||||
|
var looks = popups[id]
|
||||||
PopupList looks = popups.get(id);
|
|
||||||
if (looks == null) {
|
if (looks == null) {
|
||||||
looks = new PopupList();
|
looks = PopupList()
|
||||||
popups.put(id, looks);
|
popups[id] = looks
|
||||||
}
|
}
|
||||||
final int index = looks.size();
|
|
||||||
sourceLook.popupIndex = index;
|
val index = looks.size()
|
||||||
|
sourceLook.popupIndex = index
|
||||||
|
|
||||||
// the popups are ALL the same size!
|
// the popups are ALL the same size!
|
||||||
// popups at TOP grow down, popups at BOTTOM grow up
|
// popups at TOP grow down, popups at BOTTOM grow up
|
||||||
int targetY;
|
val anchorX = sourceLook.anchorX
|
||||||
int anchorX = sourceLook.anchorX;
|
val anchorY = sourceLook.anchorY
|
||||||
int anchorY = sourceLook.anchorY;
|
|
||||||
|
|
||||||
if (index == 0) {
|
val targetY = if (index == 0) {
|
||||||
targetY = anchorY;
|
anchorY
|
||||||
} else {
|
} else {
|
||||||
boolean growDown = growDown(sourceLook);
|
val growDown = growDown(sourceLook)
|
||||||
|
|
||||||
if (sourceLook.isDesktopNotification && index == 1) {
|
if (sourceLook.isDesktopNotification && index == 1) {
|
||||||
// have to adjust for offsets when the window-manager has a toolbar that consumes space and prevents overlap.
|
// have to adjust for offsets when the window-manager has a toolbar that consumes space and prevents overlap.
|
||||||
// this is only done when the 2nd popup is added to the list
|
// this is only done when the 2nd popup is added to the list
|
||||||
looks.calculateOffset(growDown, anchorX, anchorY);
|
looks.calculateOffset(growDown, anchorX, anchorY)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (growDown) {
|
if (growDown) {
|
||||||
targetY = anchorY + (index * (NotifyCanvas.HEIGHT + SPACER)) + looks.getOffsetY();
|
anchorY + index * (NotifyCanvas.HEIGHT + SPACER) + looks.offsetY
|
||||||
|
} else {
|
||||||
|
anchorY - index * (NotifyCanvas.HEIGHT + SPACER) + looks.offsetY
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
targetY = anchorY - (index * (NotifyCanvas.HEIGHT + SPACER)) + looks.getOffsetY();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
looks.add(sourceLook)
|
||||||
|
sourceLook.setLocation(anchorX, targetY)
|
||||||
looks.add(sourceLook);
|
|
||||||
sourceLook.setLocation(anchorX, targetY);
|
|
||||||
|
|
||||||
if (sourceLook.hideAfterDurationInSeconds > 0 && sourceLook.hideTween == null) {
|
if (sourceLook.hideAfterDurationInSeconds > 0 && sourceLook.hideTween == null) {
|
||||||
// begin a timeline to get rid of the popup (default is 5 seconds)
|
// begin a timeline to get rid of the popup (default is 5 seconds)
|
||||||
animation.to(sourceLook, NotifyAccessor.PROGRESS, accessor, sourceLook.hideAfterDurationInSeconds)
|
animation.to(sourceLook, NotifyAccessor.PROGRESS, accessor, sourceLook.hideAfterDurationInSeconds)
|
||||||
.target(NotifyCanvas.WIDTH)
|
.target(NotifyCanvas.WIDTH.toFloat())
|
||||||
.ease(TweenEquations.Linear)
|
.ease(TweenEquations.Linear)
|
||||||
.addCallback(new TweenCallback() {
|
.addCallback(COMPLETE) { sourceLook.notify.close() }
|
||||||
@Override
|
.start()
|
||||||
public void onEvent(int type, BaseTween source) {
|
|
||||||
if (type == Events.COMPLETE) {
|
|
||||||
sourceLook.notify.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// only called on the swing app or SwingActiveRender thread
|
// only called on the swing app or SwingActiveRender thread
|
||||||
private static
|
private fun removePopupFromMap(sourceLook: LookAndFeel): Boolean {
|
||||||
boolean removePopupFromMap(final LookAndFeel sourceLook) {
|
val growDown = growDown(sourceLook)
|
||||||
boolean growDown = growDown(sourceLook);
|
var popupsAreEmpty: Boolean
|
||||||
boolean popupsAreEmpty;
|
|
||||||
|
|
||||||
synchronized(popups) {
|
synchronized(popups) {
|
||||||
popupsAreEmpty = popups.isEmpty();
|
popupsAreEmpty = popups.isEmpty()
|
||||||
final PopupList allLooks = popups.get(sourceLook.idAndPosition);
|
val allLooks = popups[sourceLook.idAndPosition]
|
||||||
|
|
||||||
// there are two loops because it is necessary to cancel + remove all tweens BEFORE adding new ones.
|
// there are two loops because it is necessary to cancel + remove all tweens BEFORE adding new ones.
|
||||||
boolean adjustPopupPosition = false;
|
var adjustPopupPosition = false
|
||||||
for (Iterator<LookAndFeel> iterator = allLooks.iterator(); iterator.hasNext(); ) {
|
val iterator = allLooks!!.iterator()
|
||||||
final LookAndFeel look = iterator.next();
|
while (iterator.hasNext()) {
|
||||||
|
val look = iterator.next()
|
||||||
if (look.tween != null) {
|
if (look.tween != null) {
|
||||||
look.tween.cancel(); // cancel does its thing on the next tick of animation cycle
|
look.tween!!.cancel() // cancel does its thing on the next tick of animation cycle
|
||||||
look.tween = null;
|
look.tween = null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (look == sourceLook) {
|
if (look === sourceLook) {
|
||||||
if (look.hideTween != null) {
|
if (look.hideTween != null) {
|
||||||
look.hideTween.cancel();
|
look.hideTween!!.cancel()
|
||||||
look.hideTween = null;
|
look.hideTween = null
|
||||||
}
|
}
|
||||||
|
adjustPopupPosition = true
|
||||||
adjustPopupPosition = true;
|
iterator.remove()
|
||||||
iterator.remove();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adjustPopupPosition) {
|
if (adjustPopupPosition) {
|
||||||
look.popupIndex--;
|
look.popupIndex--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// have to adjust for offsets when the window-manager has a toolbar that consumes space and prevents overlap.
|
// have to adjust for offsets when the window-manager has a toolbar that consumes space and prevents overlap.
|
||||||
int offsetY = allLooks.getOffsetY();
|
val offsetY = allLooks.offsetY
|
||||||
|
for (index in 0 until allLooks.size()) {
|
||||||
|
val look = allLooks[index]
|
||||||
|
|
||||||
for (int index = 0; index < allLooks.size(); index++) {
|
|
||||||
final LookAndFeel look = allLooks.get(index);
|
|
||||||
// the popups are ALL the same size!
|
// the popups are ALL the same size!
|
||||||
// popups at TOP grow down, popups at BOTTOM grow up
|
// popups at TOP grow down, popups at BOTTOM grow up
|
||||||
int changedY;
|
val changedY = if (growDown) {
|
||||||
|
look.anchorY + (look.popupIndex * (NotifyCanvas.HEIGHT + SPACER) + offsetY)
|
||||||
if (growDown) {
|
} else {
|
||||||
changedY = look.anchorY + (look.popupIndex * (NotifyCanvas.HEIGHT + SPACER) + offsetY);
|
look.anchorY - (look.popupIndex * (NotifyCanvas.HEIGHT + SPACER) + offsetY)
|
||||||
}
|
|
||||||
else {
|
|
||||||
changedY = look.anchorY - (look.popupIndex * (NotifyCanvas.HEIGHT + SPACER) + offsetY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// now animate that popup to its new location
|
// now animate that popup to its new location
|
||||||
look.tween = animation.to(look, NotifyAccessor.Y_POS, accessor, MOVE_DURATION)
|
look.tween = animation
|
||||||
.target((float) changedY)
|
.to(look, NotifyAccessor.Y_POS, accessor, MOVE_DURATION)
|
||||||
|
.target(changedY.toFloat())
|
||||||
.ease(TweenEquations.Linear)
|
.ease(TweenEquations.Linear)
|
||||||
.addCallback(new TweenCallback() {
|
.addCallback(COMPLETE) {
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void onEvent(final int type, final BaseTween source) {
|
|
||||||
if (type == Events.COMPLETE) {
|
|
||||||
// make sure to remove the tween once it's done, otherwise .kill can do weird things.
|
// make sure to remove the tween once it's done, otherwise .kill can do weird things.
|
||||||
look.tween = null;
|
look.tween = null
|
||||||
|
}
|
||||||
|
.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
return popupsAreEmpty
|
||||||
.start();
|
}
|
||||||
|
|
||||||
|
private fun growDown(look: LookAndFeel): Boolean {
|
||||||
|
return when (look.position) {
|
||||||
|
Pos.TOP_LEFT, Pos.TOP_RIGHT, Pos.CENTER -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return popupsAreEmpty;
|
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var anchorX: Int
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var anchorY: Int
|
||||||
|
private val hideAfterDurationInSeconds: Float
|
||||||
|
private val position: Pos
|
||||||
|
|
||||||
|
// this is used in combination with position, so that we can track which screen and what position a popup is in
|
||||||
|
private var idAndPosition: String? = null
|
||||||
|
private var popupIndex = 0
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var tween: Tween<*>? = null
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var hideTween: Tween<*>? = null
|
||||||
|
private val onGeneralAreaClickAction = notification.onGeneralAreaClickAction // explicitly make a copy
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (isDesktopNotification) {
|
||||||
|
parent.addWindowListener(windowListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static
|
notifyCanvas.addMouseListener(mouseListener)
|
||||||
boolean growDown(final LookAndFeel look) {
|
hideAfterDurationInSeconds = notification.hideAfterDurationInMillis / 1000.0f
|
||||||
switch (look.position) {
|
position = notification.position
|
||||||
case TOP_LEFT:
|
|
||||||
case TOP_RIGHT:
|
idAndPosition = if (isDesktopNotification) {
|
||||||
case CENTER: // center grows down
|
val point = Point(parentBounds.getX().toInt(), parentBounds.getY().toInt())
|
||||||
return true;
|
ScreenUtil.getMonitorNumberAtLocation(point).toString() + ":" + position
|
||||||
default:
|
} else {
|
||||||
return false;
|
parent.name + ":" + position
|
||||||
|
}
|
||||||
|
|
||||||
|
anchorX = getAnchorX(position, parentBounds, isDesktopNotification)
|
||||||
|
anchorY = getAnchorY(position, parentBounds, isDesktopNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onClick(x: Int, y: Int) {
|
||||||
|
// Check - we were over the 'X' (and thus no notify), or was it in the general area?
|
||||||
|
|
||||||
|
// reasonable position for detecting mouse over
|
||||||
|
if (!notifyCanvas.isCloseButton(x, y)) {
|
||||||
|
// only call the general click handler IF we click in the general area!
|
||||||
|
onGeneralAreaClickAction.invoke(notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we always close the notification popup
|
||||||
|
notify.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// only called from an application
|
||||||
|
fun reLayout(bounds: Rectangle) {
|
||||||
|
// when the parent window moves, we stop all animation and snap the popup into place. This simplifies logic greatly
|
||||||
|
anchorX = getAnchorX(position, bounds, isDesktopNotification)
|
||||||
|
anchorY = getAnchorY(position, bounds, isDesktopNotification)
|
||||||
|
|
||||||
|
val growDown = growDown(this)
|
||||||
|
|
||||||
|
if (tween != null) {
|
||||||
|
tween!!.cancel() // cancel does its thing on the next tick of animation cycle
|
||||||
|
tween = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var changedY: Int
|
||||||
|
if (popupIndex == 0) {
|
||||||
|
changedY = anchorY
|
||||||
|
} else {
|
||||||
|
synchronized(popups) {
|
||||||
|
val id = idAndPosition
|
||||||
|
val looks = popups[id]
|
||||||
|
changedY = if (looks != null) {
|
||||||
|
if (growDown) {
|
||||||
|
anchorY + popupIndex * (NotifyCanvas.HEIGHT + SPACER)
|
||||||
|
} else {
|
||||||
|
anchorY - popupIndex * (NotifyCanvas.HEIGHT + SPACER)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
anchorY
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setProgress(final int progress) {
|
setLocation(anchorX, changedY)
|
||||||
notifyCanvas.setProgress(progress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int getProgress() {
|
fun close() {
|
||||||
return notifyCanvas.getProgress();
|
if (hideTween != null) {
|
||||||
|
hideTween!!.cancel()
|
||||||
|
hideTween = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tween != null) {
|
||||||
|
tween!!.cancel()
|
||||||
|
tween = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDesktopNotification) {
|
||||||
|
parent.removeWindowListener(windowListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.removeMouseListener(mouseListener)
|
||||||
|
updatePositionsPre(false)
|
||||||
|
updatePositionsPost(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shake(durationInMillis: Int, amplitude: Int) {
|
||||||
|
var i1 = RANDOM.nextInt((amplitude shl 2) + 1) - amplitude
|
||||||
|
var i2 = RANDOM.nextInt((amplitude shl 2) + 1) - amplitude
|
||||||
|
i1 = i1 shr 2
|
||||||
|
i2 = i2 shr 2
|
||||||
|
|
||||||
|
// make sure it always moves by some amount
|
||||||
|
if (i1 < 0) {
|
||||||
|
i1 -= amplitude shr 2
|
||||||
|
} else {
|
||||||
|
i1 += amplitude shr 2
|
||||||
|
}
|
||||||
|
if (i2 < 0) {
|
||||||
|
i2 -= amplitude shr 2
|
||||||
|
} else {
|
||||||
|
i2 += amplitude shr 2
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = durationInMillis / 50
|
||||||
|
// make sure we always end the animation where we start
|
||||||
|
if (count and 1 == 0) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
animation
|
||||||
|
.to(this, NotifyAccessor.X_Y_POS, accessor, 0.05f)
|
||||||
|
.targetRelative(i1.toFloat(), i2.toFloat())
|
||||||
|
.repeatAutoReverse(count, 0f)
|
||||||
|
.ease(TweenEquations.Linear)
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
var y: Int
|
||||||
|
get() = if (isDesktopNotification) {
|
||||||
|
parent.y
|
||||||
|
} else {
|
||||||
|
notifyCanvas.y
|
||||||
|
}
|
||||||
|
set(y) {
|
||||||
|
if (isDesktopNotification) {
|
||||||
|
parent.setLocation(parent.x, y)
|
||||||
|
} else {
|
||||||
|
notifyCanvas.setLocation(notifyCanvas.x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val x: Int
|
||||||
|
get() = if (isDesktopNotification) {
|
||||||
|
parent.x
|
||||||
|
} else {
|
||||||
|
notifyCanvas.x
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setLocation(x: Int, y: Int) {
|
||||||
|
if (isDesktopNotification) {
|
||||||
|
parent.setLocation(x, y)
|
||||||
|
} else {
|
||||||
|
notifyCanvas.setLocation(x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var progress: Int
|
||||||
|
get() = notifyCanvas.progress
|
||||||
|
set(progress) {
|
||||||
|
notifyCanvas.progress = progress
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* we have to remove the active renderer BEFORE we set the visibility status.
|
* we have to remove the active renderer BEFORE we set the visibility status.
|
||||||
*/
|
*/
|
||||||
void updatePositionsPre(final boolean visible) {
|
fun updatePositionsPre(visible: Boolean) {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
boolean popupsAreEmpty = LookAndFeel.removePopupFromMap(this);
|
val popupsAreEmpty = removePopupFromMap(this)
|
||||||
SwingActiveRender.removeActiveRender(notifyCanvas);
|
SwingActiveRender.removeActiveRender(notifyCanvas)
|
||||||
|
|
||||||
if (popupsAreEmpty) {
|
if (popupsAreEmpty) {
|
||||||
// if there's nothing left, stop the timer.
|
// if there's nothing left, stop the timer.
|
||||||
SwingActiveRender.removeActiveRenderFrameStart(frameStartHandler);
|
SwingActiveRender.removeActiveRenderFrameStart(frameStartHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -514,17 +401,16 @@ class LookAndFeel {
|
||||||
/**
|
/**
|
||||||
* when using active rendering, we have to add it AFTER we have set the visibility status
|
* when using active rendering, we have to add it AFTER we have set the visibility status
|
||||||
*/
|
*/
|
||||||
void updatePositionsPost(final boolean visible) {
|
fun updatePositionsPost(visible: Boolean) {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
SwingActiveRender.addActiveRender(notifyCanvas);
|
SwingActiveRender.addActiveRender(notifyCanvas)
|
||||||
|
|
||||||
// start if we have previously stopped the timer
|
// start if we have previously stopped the timer
|
||||||
if (!SwingActiveRender.containsActiveRenderFrameStart(frameStartHandler)) {
|
if (!SwingActiveRender.containsActiveRenderFrameStart(frameStartHandler)) {
|
||||||
LookAndFeel.animation.resetUpdateTime();
|
animation.resetUpdateTime()
|
||||||
SwingActiveRender.addActiveRenderFrameStart(frameStartHandler);
|
SwingActiveRender.addActiveRenderFrameStart(frameStartHandler)
|
||||||
}
|
}
|
||||||
|
addPopupToMap(this)
|
||||||
LookAndFeel.addPopupToMap(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,385 +13,330 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
import java.awt.Image;
|
import dorkbox.propertyLoader.Property
|
||||||
import java.awt.image.BufferedImage;
|
import dorkbox.util.ImageUtil
|
||||||
import java.io.IOException;
|
import dorkbox.util.LocationResolver
|
||||||
import java.io.InputStream;
|
import dorkbox.util.SwingUtil
|
||||||
import java.lang.ref.SoftReference;
|
import java.awt.Image
|
||||||
import java.util.HashMap;
|
import java.awt.image.BufferedImage
|
||||||
import java.util.Map;
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
import javax.imageio.ImageIO;
|
import java.lang.ref.SoftReference
|
||||||
import javax.swing.ImageIcon;
|
import javax.imageio.ImageIO
|
||||||
import javax.swing.JFrame;
|
import javax.swing.ImageIcon
|
||||||
|
import javax.swing.JFrame
|
||||||
import dorkbox.propertyLoader.Property;
|
|
||||||
import dorkbox.util.ImageUtil;
|
|
||||||
import dorkbox.util.LocationResolver;
|
|
||||||
import dorkbox.util.SwingUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Popup notification messages, similar to the popular "Growl" notification system on macosx, that display in the corner of the monitor.
|
* Popup notification messages, similar to the popular "Growl" notification system on macosx, that display in the corner of the monitor.
|
||||||
* </p>
|
*
|
||||||
* They can follow the mouse (if the screen is unspecified), and have a variety of features, such as "shaking" to draw attention,
|
* They can follow the mouse (if the screen is unspecified), and have a variety of features, such as "shaking" to draw attention,
|
||||||
* animating upon movement (for collating w/ multiple in a single location), and automatically hiding after a set duration.
|
* animating upon movement (for collating w/ multiple in a single location), and automatically hiding after a set duration.
|
||||||
* </p>
|
*
|
||||||
* These notifications are for a single screen only, and cannot be anchored to an application.
|
* These notifications are for a single screen only, and cannot be anchored to an application.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* {@code
|
* `Notify.create()
|
||||||
* Notify.create()
|
|
||||||
* .title("Title Text")
|
* .title("Title Text")
|
||||||
* .text("Hello World!")
|
* .text("Hello World!")
|
||||||
* .useDarkStyle()
|
* .useDarkStyle()
|
||||||
* .showWarning();
|
* .showWarning();
|
||||||
* }
|
` *
|
||||||
* </pre>
|
</pre> *
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"WeakerAccess", "unused", "UnusedReturnValue"})
|
@Suppress("unused")
|
||||||
public final
|
class Notify private constructor() {
|
||||||
class Notify {
|
companion object {
|
||||||
|
const val DIALOG_CONFIRM = "dialog-confirm.png"
|
||||||
public static final String DIALOG_CONFIRM = "dialog-confirm.png";
|
const val DIALOG_INFORMATION = "dialog-information.png"
|
||||||
|
const val DIALOG_WARNING = "dialog-warning.png"
|
||||||
public static final String DIALOG_INFORMATION = "dialog-information.png";
|
const val DIALOG_ERROR = "dialog-error.png"
|
||||||
public static final String DIALOG_WARNING = "dialog-warning.png";
|
|
||||||
public static final String DIALOG_ERROR = "dialog-error.png";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the title font used by a notification.
|
* This is the title font used by a notification.
|
||||||
*/
|
*/
|
||||||
@Property
|
@Property
|
||||||
public static String TITLE_TEXT_FONT = "Source Code Pro BOLD 16";
|
var TITLE_TEXT_FONT = "Source Code Pro BOLD 16"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the main text font used by a notification.
|
* This is the main text font used by a notification.
|
||||||
*/
|
*/
|
||||||
@Property
|
@Property
|
||||||
public static String MAIN_TEXT_FONT = "Source Code Pro BOLD 12";
|
var MAIN_TEXT_FONT = "Source Code Pro BOLD 12"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How long we want it to take for the popups to relocate when one is closed
|
* How long we want it to take for the popups to relocate when one is closed
|
||||||
*/
|
*/
|
||||||
@Property
|
@Property
|
||||||
public static float MOVE_DURATION = 1.0F;
|
var MOVE_DURATION = 1.0f
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Location of the dialog image resources. By default they must be in the 'resources' directory relative to the application
|
* Location of the dialog image resources. By default they must be in the 'resources' directory relative to the application
|
||||||
*/
|
*/
|
||||||
@Property
|
@Property
|
||||||
public static String IMAGE_PATH = "resources";
|
var IMAGE_PATH = "resources"
|
||||||
|
private val imageCache = mutableMapOf<String, SoftReference<ImageIcon>>()
|
||||||
private static Map<String, SoftReference<ImageIcon>> imageCache = new HashMap<String, SoftReference<ImageIcon>>(4);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the version number.
|
* Gets the version number.
|
||||||
*/
|
*/
|
||||||
public static
|
const val version = "3.7"
|
||||||
String getVersion() {
|
|
||||||
return "3.7";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder pattern to create the notification.
|
* Builder pattern to create the notification.
|
||||||
*/
|
*/
|
||||||
public static
|
fun create(): Notify {
|
||||||
Notify create() {
|
return Notify()
|
||||||
return new Notify();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the size of the image to be used in the notification, which is a 48x48 pixel image.
|
* Gets the size of the image to be used in the notification, which is a 48x48 pixel image.
|
||||||
*/
|
*/
|
||||||
public static
|
val imageSize: Int
|
||||||
int getImageSize() {
|
get() = 48
|
||||||
return 48;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Permits one to override the default images for the dialogs. This is NOT thread safe, and must be performed BEFORE showing a
|
* Permits one to override the default images for the dialogs. This is NOT thread safe, and must be performed BEFORE showing a
|
||||||
* notification.
|
* notification.
|
||||||
* <p>
|
*
|
||||||
|
*
|
||||||
* The image names are as follows:
|
* The image names are as follows:
|
||||||
* <p>
|
*
|
||||||
|
*
|
||||||
* 'Notify.DIALOG_CONFIRM' 'Notify.DIALOG_INFORMATION' 'Notify.DIALOG_WARNING' 'Notify.DIALOG_ERROR'
|
* 'Notify.DIALOG_CONFIRM' 'Notify.DIALOG_INFORMATION' 'Notify.DIALOG_WARNING' 'Notify.DIALOG_ERROR'
|
||||||
*
|
*
|
||||||
* @param imageName the name of the image, either your own if you want want it cached, or one of the above.
|
* @param imageName the name of the image, either your own if you want it cached, or one of the above.
|
||||||
* @param image the BufferedImage that you want to cache.
|
* @param image the BufferedImage that you want to cache.
|
||||||
*/
|
*/
|
||||||
public static
|
fun overrideDefaultImage(imageName: String, image: BufferedImage) {
|
||||||
void overrideDefaultImage(String imageName, BufferedImage image) {
|
|
||||||
if (imageCache.containsKey(imageName)) {
|
if (imageCache.containsKey(imageName)) {
|
||||||
throw new RuntimeException("Unable to set an image that already has been set. This action must be done as soon as possible.");
|
throw RuntimeException("Unable to set an image that already has been set. This action must be done as soon as possible.")
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageUtil.waitForImageLoad(image);
|
ImageUtil.waitForImageLoad(image)
|
||||||
|
|
||||||
// we only use 48x48 pixel images. Resize as necessary
|
// we only use 48x48 pixel images. Resize as necessary
|
||||||
int width = image.getWidth(null);
|
val width = image.getWidth(null)
|
||||||
int height = image.getHeight(null);
|
val height = image.getHeight(null)
|
||||||
|
|
||||||
BufferedImage bufferedImage;
|
|
||||||
|
|
||||||
// resize the image, keep aspect ratio
|
// resize the image, keep aspect ratio
|
||||||
if (width > height) {
|
val bufferedImage = if (width > height) {
|
||||||
bufferedImage = ImageUtil.resizeImage(image, getImageSize(), -1);
|
ImageUtil.resizeImage(image, imageSize, -1)
|
||||||
}
|
} else {
|
||||||
else {
|
ImageUtil.resizeImage(image, -1, imageSize)
|
||||||
bufferedImage = ImageUtil.resizeImage(image, -1, getImageSize());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
imageCache.put(imageName, new SoftReference<ImageIcon>(new ImageIcon(bufferedImage)));
|
imageCache[imageName] = SoftReference(ImageIcon(bufferedImage))
|
||||||
}
|
}
|
||||||
|
|
||||||
private static
|
private fun getImage(imageName: String): ImageIcon? {
|
||||||
ImageIcon getImage(String imageName) {
|
var resourceAsStream: InputStream? = null
|
||||||
ImageIcon image = null;
|
|
||||||
InputStream resourceAsStream = null;
|
var image = imageCache[imageName]?.get()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SoftReference<ImageIcon> reference = imageCache.get(imageName);
|
|
||||||
|
|
||||||
if (reference != null) {
|
|
||||||
image = reference.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
// String name = IMAGE_PATH + File.separatorChar + imageName;
|
// String name = IMAGE_PATH + File.separatorChar + imageName;
|
||||||
String name = imageName;
|
resourceAsStream = LocationResolver.getResourceAsStream(imageName)
|
||||||
|
image = ImageIcon(ImageIO.read(resourceAsStream))
|
||||||
resourceAsStream = LocationResolver.getResourceAsStream(name);
|
imageCache[imageName] = SoftReference(image)
|
||||||
|
|
||||||
image = new ImageIcon(ImageIO.read(resourceAsStream));
|
|
||||||
imageCache.put(imageName, new SoftReference<ImageIcon>(image));
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (e: IOException) {
|
||||||
e.printStackTrace();
|
e.printStackTrace()
|
||||||
} finally {
|
} finally {
|
||||||
if (resourceAsStream != null) {
|
resourceAsStream?.close()
|
||||||
try {
|
|
||||||
resourceAsStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return image
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
String title;
|
internal var title = "Notification"
|
||||||
String text;
|
internal var text = "Lorem ipsum"
|
||||||
|
private var theme: Theme? = null
|
||||||
|
internal var position = Pos.BOTTOM_RIGHT
|
||||||
|
internal var hideAfterDurationInMillis = 0
|
||||||
|
internal var hideCloseButton = false
|
||||||
|
private var isDark = false
|
||||||
|
internal var screenNumber = Short.MIN_VALUE.toInt()
|
||||||
|
|
||||||
Theme theme;
|
private var icon: ImageIcon? = null
|
||||||
|
internal var onGeneralAreaClickAction: Notify.()->Unit = {}
|
||||||
|
|
||||||
Pos position = Pos.BOTTOM_RIGHT;
|
private var notifyPopup: INotify? = null
|
||||||
int hideAfterDurationInMillis = 0;
|
private var name: String? = null
|
||||||
|
private var shakeDurationInMillis = 0
|
||||||
boolean hideCloseButton;
|
private var shakeAmplitude = 0
|
||||||
boolean isDark = false;
|
private var appWindow: JFrame? = null
|
||||||
int screenNumber = Short.MIN_VALUE;
|
|
||||||
private ImageIcon icon;
|
|
||||||
|
|
||||||
ActionHandler<Notify> onGeneralAreaClickAction;
|
|
||||||
private INotify notifyPopup;
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
private int shakeDurationInMillis = 0;
|
|
||||||
private int shakeAmplitude = 0;
|
|
||||||
private JFrame appWindow;
|
|
||||||
|
|
||||||
private
|
|
||||||
Notify() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the main text
|
* Specifies the main text
|
||||||
*/
|
*/
|
||||||
public
|
fun text(text: String): Notify {
|
||||||
Notify text(String text) {
|
this.text = text
|
||||||
this.text = text;
|
return this
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the title
|
* Specifies the title
|
||||||
*/
|
*/
|
||||||
public
|
fun title(title: String): Notify {
|
||||||
Notify title(String title) {
|
this.title = title
|
||||||
this.title = title;
|
return this
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the image
|
* Specifies the image
|
||||||
*/
|
*/
|
||||||
public
|
fun image(image: Image): Notify {
|
||||||
Notify image(Image image) {
|
|
||||||
// we only use 48x48 pixel images. Resize as necessary
|
// we only use 48x48 pixel images. Resize as necessary
|
||||||
int width = image.getWidth(null);
|
val width = image.getWidth(null)
|
||||||
int height = image.getHeight(null);
|
val height = image.getHeight(null)
|
||||||
|
var bufferedImage = ImageUtil.getBufferedImage(image)
|
||||||
BufferedImage bufferedImage = ImageUtil.getBufferedImage(image);
|
|
||||||
|
|
||||||
// resize the image, keep aspect ratio
|
// resize the image, keep aspect ratio
|
||||||
if (width > height) {
|
bufferedImage = if (width > height) {
|
||||||
bufferedImage = ImageUtil.resizeImage(bufferedImage, 48, -1);
|
ImageUtil.resizeImage(bufferedImage, 48, -1)
|
||||||
} else {
|
} else {
|
||||||
bufferedImage = ImageUtil.resizeImage(bufferedImage, -1, 48);
|
ImageUtil.resizeImage(bufferedImage, -1, 48)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have to now clamp to a max dimension of 48
|
// we have to now clamp to a max dimension of 48
|
||||||
bufferedImage = ImageUtil.clampMaxImageSize(bufferedImage, 48);
|
bufferedImage = ImageUtil.clampMaxImageSize(bufferedImage, 48)
|
||||||
|
|
||||||
// now we want to center the image
|
// now we want to center the image
|
||||||
bufferedImage = ImageUtil.getSquareBufferedImage(bufferedImage);
|
bufferedImage = ImageUtil.getSquareBufferedImage(bufferedImage)
|
||||||
|
icon = ImageIcon(bufferedImage)
|
||||||
this.icon = new ImageIcon(bufferedImage);
|
return this
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the position of the notification on screen, by default it is {@link Pos#BOTTOM_RIGHT bottom-right}.
|
* Specifies the position of the notification on screen, by default it is [bottom-right][Pos.BOTTOM_RIGHT].
|
||||||
*/
|
*/
|
||||||
public
|
fun position(position: Pos): Notify {
|
||||||
Notify position(Pos position) {
|
this.position = position
|
||||||
this.position = position;
|
return this
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the duration that the notification should show, after which it will be hidden. 0 means to show forever. By default it
|
* Specifies the duration that the notification should show, after which it will be hidden. 0 means to show forever. By default it
|
||||||
* will show forever
|
* will show forever
|
||||||
*/
|
*/
|
||||||
public
|
fun hideAfter(durationInMillis: Int): Notify {
|
||||||
Notify hideAfter(int durationInMillis) {
|
hideAfterDurationInMillis = if (durationInMillis < 0) {
|
||||||
if (durationInMillis < 0) {
|
0
|
||||||
durationInMillis = 0;
|
} else {
|
||||||
|
durationInMillis
|
||||||
}
|
}
|
||||||
this.hideAfterDurationInMillis = durationInMillis;
|
|
||||||
return this;
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies what to do when the user clicks on the notification (in addition o the notification hiding, which happens whenever the
|
* Specifies what to do when the user clicks on the notification (in addition o the notification hiding, which happens whenever the
|
||||||
* notification is clicked on). This does not apply when clicking on the "close" button
|
* notification is clicked on). This does not apply when clicking on the "close" button
|
||||||
*/
|
*/
|
||||||
public
|
fun onAction(onAction: Notify.()->Unit): Notify {
|
||||||
Notify onAction(ActionHandler<Notify> onAction) {
|
onGeneralAreaClickAction = onAction
|
||||||
this.onGeneralAreaClickAction = onAction;
|
return this
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies that the notification should use the built-in dark styling, rather than the default, light-gray notification style.
|
* Specifies that the notification should use the built-in dark styling, rather than the default, light-gray notification style.
|
||||||
*/
|
*/
|
||||||
public
|
fun darkStyle(): Notify {
|
||||||
Notify darkStyle() {
|
isDark = true
|
||||||
isDark = true;
|
return this
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies what the theme should be, if other than the default. This will always take precedence over the defaults.
|
* Specifies what the theme should be, if other than the default. This will always take precedence over the defaults.
|
||||||
*/
|
*/
|
||||||
public
|
fun text(theme: Theme?): Notify {
|
||||||
Notify text(Theme theme) {
|
this.theme = theme
|
||||||
this.theme = theme;
|
return this
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify that the close button in the top-right corner of the notification should not be shown.
|
* Specify that the close button in the top-right corner of the notification should not be shown.
|
||||||
*/
|
*/
|
||||||
public
|
fun hideCloseButton(): Notify {
|
||||||
Notify hideCloseButton() {
|
hideCloseButton = true
|
||||||
this.hideCloseButton = true;
|
return this
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the notification with the built-in 'warning' image.
|
* Shows the notification with the built-in 'warning' image.
|
||||||
*/
|
*/
|
||||||
public
|
fun showWarning() {
|
||||||
void showWarning() {
|
name = DIALOG_WARNING
|
||||||
name = DIALOG_WARNING;
|
icon = getImage(DIALOG_WARNING)
|
||||||
icon = getImage(name);
|
show()
|
||||||
show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the notification with the built-in 'information' image.
|
* Shows the notification with the built-in 'information' image.
|
||||||
*/
|
*/
|
||||||
public
|
fun showInformation() {
|
||||||
void showInformation() {
|
name = DIALOG_INFORMATION
|
||||||
name = DIALOG_INFORMATION;
|
icon = getImage(DIALOG_INFORMATION)
|
||||||
icon = getImage(name);
|
show()
|
||||||
show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the notification with the built-in 'error' image.
|
* Shows the notification with the built-in 'error' image.
|
||||||
*/
|
*/
|
||||||
public
|
fun showError() {
|
||||||
void showError() {
|
name = DIALOG_ERROR
|
||||||
name = DIALOG_ERROR;
|
icon = getImage(DIALOG_ERROR)
|
||||||
icon = getImage(name);
|
show()
|
||||||
show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the notification with the built-in 'confirm' image.
|
* Shows the notification with the built-in 'confirm' image.
|
||||||
*/
|
*/
|
||||||
public
|
fun showConfirm() {
|
||||||
void showConfirm() {
|
name = DIALOG_CONFIRM
|
||||||
name = DIALOG_CONFIRM;
|
icon = getImage(DIALOG_CONFIRM)
|
||||||
icon = getImage(name);
|
show()
|
||||||
show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the notification. If the Notification is assigned to a screen, but shown inside a Swing/etc parent, the screen number will be
|
* Shows the notification. If the Notification is assigned to a screen, but shown inside a Swing/etc parent, the screen number will be
|
||||||
* ignored.
|
* ignored.
|
||||||
*/
|
*/
|
||||||
public
|
fun show() {
|
||||||
void show() {
|
|
||||||
// must be done in the swing EDT
|
// must be done in the swing EDT
|
||||||
//noinspection Convert2Lambda
|
SwingUtil.invokeAndWaitQuietly {
|
||||||
SwingUtil.invokeAndWaitQuietly(new Runnable() {
|
val notify = this@Notify
|
||||||
@Override
|
val image = notify.icon
|
||||||
public
|
val theme = if (notify.theme != null) {
|
||||||
void run() {
|
// use custom provided theme
|
||||||
final Notify notify = Notify.this;
|
notify.theme!!
|
||||||
final ImageIcon image = notify.icon;
|
|
||||||
|
|
||||||
Theme theme;
|
|
||||||
if (notify.theme != null) {
|
|
||||||
// use custom theme.
|
|
||||||
theme = notify.theme;
|
|
||||||
} else {
|
} else {
|
||||||
theme = new Theme(Notify.TITLE_TEXT_FONT, Notify.MAIN_TEXT_FONT, notify.isDark);
|
Theme(TITLE_TEXT_FONT, MAIN_TEXT_FONT, notify.isDark)
|
||||||
|
}
|
||||||
|
val window = appWindow
|
||||||
|
|
||||||
|
val notifyPopup = if (window == null) {
|
||||||
|
AsDesktop(notify, image, theme)
|
||||||
|
} else {
|
||||||
|
AsApplication(notify, image, window, theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appWindow == null) {
|
notifyPopup.setVisible(true)
|
||||||
notifyPopup = new AsDesktop(notify, image, theme);
|
|
||||||
} else {
|
|
||||||
notifyPopup = new AsApplication(notify, image, appWindow, theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyPopup.setVisible(true);
|
|
||||||
|
|
||||||
if (shakeDurationInMillis > 0) {
|
if (shakeDurationInMillis > 0) {
|
||||||
notifyPopup.shake(notify.shakeDurationInMillis, notify.shakeAmplitude);
|
notifyPopup.shake(notify.shakeDurationInMillis, notify.shakeAmplitude)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notify.notifyPopup = notifyPopup
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// don't need to hang onto these.
|
// don't need to hang onto these.
|
||||||
icon = null;
|
icon = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -400,68 +345,48 @@ class Notify {
|
||||||
* @param durationInMillis now long it will shake
|
* @param durationInMillis now long it will shake
|
||||||
* @param amplitude a measure of how much it needs to shake. 4 is a small amount of shaking, 10 is a lot.
|
* @param amplitude a measure of how much it needs to shake. 4 is a small amount of shaking, 10 is a lot.
|
||||||
*/
|
*/
|
||||||
public
|
fun shake(durationInMillis: Int, amplitude: Int): Notify {
|
||||||
Notify shake(final int durationInMillis, final int amplitude) {
|
shakeDurationInMillis = durationInMillis
|
||||||
this.shakeDurationInMillis = durationInMillis;
|
shakeAmplitude = amplitude
|
||||||
this.shakeAmplitude = amplitude;
|
|
||||||
|
|
||||||
if (notifyPopup != null) {
|
if (notifyPopup != null) {
|
||||||
// must be done in the swing EDT
|
// must be done in the swing EDT
|
||||||
//noinspection Convert2Lambda
|
SwingUtil.invokeLater { notifyPopup!!.shake(durationInMillis, amplitude) }
|
||||||
SwingUtil.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
notifyPopup.shake(durationInMillis, amplitude);
|
|
||||||
}
|
}
|
||||||
});
|
return this
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the notification. Particularly useful if it's an "infinite" duration notification.
|
* Closes the notification. Particularly useful if it's an "infinite" duration notification.
|
||||||
*/
|
*/
|
||||||
public
|
fun close() {
|
||||||
void close() {
|
|
||||||
if (notifyPopup == null) {
|
if (notifyPopup == null) {
|
||||||
throw new NullPointerException("NotifyPopup");
|
throw NullPointerException("NotifyPopup")
|
||||||
}
|
}
|
||||||
|
|
||||||
// must be done in the swing EDT
|
// must be done in the swing EDT
|
||||||
//noinspection Convert2Lambda
|
SwingUtil.invokeLater { notifyPopup!!.close() }
|
||||||
SwingUtil.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
notifyPopup.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies which screen to display on. If <0, it will show on screen 0. If > max-screens, it will show on the last screen.
|
* Specifies which screen to display on. If <0, it will show on screen 0. If > max-screens, it will show on the last screen.
|
||||||
*/
|
*/
|
||||||
public
|
fun setScreen(screenNumber: Int): Notify {
|
||||||
Notify setScreen(final int screenNumber) {
|
this.screenNumber = screenNumber
|
||||||
this.screenNumber = screenNumber;
|
return this
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches this notification to a specific JFrame, instead of having a global notification
|
* Attaches this notification to a specific JFrame, instead of having a global notification
|
||||||
*/
|
*/
|
||||||
public
|
fun attach(frame: JFrame?): Notify {
|
||||||
Notify attach(final JFrame frame) {
|
appWindow = frame
|
||||||
this.appWindow = frame;
|
return this
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// called when this notification is closed.
|
// called when this notification is closed.
|
||||||
void onClose() {
|
fun onClose() {
|
||||||
notifyPopup = null;
|
notifyPopup = null
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -13,52 +13,54 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
import dorkbox.tweenEngine.TweenAccessor;
|
import dorkbox.tweenEngine.TweenAccessor
|
||||||
|
|
||||||
class NotifyAccessor implements TweenAccessor<LookAndFeel> {
|
internal class NotifyAccessor : TweenAccessor<LookAndFeel> {
|
||||||
|
companion object {
|
||||||
static final int Y_POS = 1;
|
const val Y_POS = 1
|
||||||
static final int X_Y_POS = 2;
|
const val X_Y_POS = 2
|
||||||
static final int PROGRESS = 3;
|
const val PROGRESS = 3
|
||||||
|
|
||||||
|
|
||||||
NotifyAccessor() {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun getValues(target: LookAndFeel, tweenType: Int, returnValues: FloatArray): Int {
|
||||||
public
|
when (tweenType) {
|
||||||
int getValues(final LookAndFeel target, final int tweenType, final float[] returnValues) {
|
Y_POS -> {
|
||||||
switch (tweenType) {
|
returnValues[0] = target.y.toFloat()
|
||||||
case Y_POS:
|
return 1
|
||||||
returnValues[0] = (float) target.getY();
|
|
||||||
return 1;
|
|
||||||
case X_Y_POS:
|
|
||||||
returnValues[0] = (float) target.getX();
|
|
||||||
returnValues[1] = (float) target.getY();
|
|
||||||
return 2;
|
|
||||||
case PROGRESS:
|
|
||||||
returnValues[0] = (float) target.getProgress();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"NumericCastThatLosesPrecision", "UnnecessaryReturnStatement"})
|
X_Y_POS -> {
|
||||||
@Override
|
returnValues[0] = target.x.toFloat()
|
||||||
public
|
returnValues[1] = target.y.toFloat()
|
||||||
void setValues(final LookAndFeel target, final int tweenType, final float[] newValues) {
|
return 2
|
||||||
switch (tweenType) {
|
}
|
||||||
case Y_POS:
|
|
||||||
target.setY((int) newValues[0]);
|
PROGRESS -> {
|
||||||
return;
|
returnValues[0] = target.progress.toFloat()
|
||||||
case X_Y_POS:
|
return 1
|
||||||
target.setLocation((int) newValues[0], (int) newValues[1]);
|
}
|
||||||
return;
|
}
|
||||||
case PROGRESS:
|
return 1
|
||||||
target.setProgress((int) newValues[0]);
|
}
|
||||||
return;
|
|
||||||
|
override fun setValues(target: LookAndFeel, tweenType: Int, newValues: FloatArray) {
|
||||||
|
when (tweenType) {
|
||||||
|
Y_POS -> {
|
||||||
|
target.y = newValues[0].toInt()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
X_Y_POS -> {
|
||||||
|
target.setLocation(newValues[0].toInt(), newValues[1].toInt())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
PROGRESS -> {
|
||||||
|
target.progress = newValues[0].toInt()
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,88 +13,56 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
import java.awt.BasicStroke;
|
import java.awt.BasicStroke
|
||||||
import java.awt.Canvas;
|
import java.awt.Canvas
|
||||||
import java.awt.Color;
|
import java.awt.Color
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D
|
||||||
import java.awt.Point;
|
import java.awt.RenderingHints
|
||||||
import java.awt.RenderingHints;
|
import java.awt.Stroke
|
||||||
import java.awt.Stroke;
|
import java.awt.image.BufferedImage
|
||||||
import java.awt.image.BufferedImage;
|
import javax.swing.ImageIcon
|
||||||
|
import javax.swing.JLabel
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
internal class NotifyCanvas(
|
||||||
import javax.swing.JLabel;
|
val parent: INotify,
|
||||||
|
private val notification: Notify,
|
||||||
|
private val imageIcon: ImageIcon?,
|
||||||
|
private val theme: Theme
|
||||||
|
) : Canvas() {
|
||||||
|
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
private val showCloseButton: Boolean
|
||||||
class NotifyCanvas extends Canvas {
|
private var cachedImage: BufferedImage
|
||||||
private static final Stroke stroke = new BasicStroke(2);
|
|
||||||
private static final int closeX = 282;
|
|
||||||
private static final int closeY = 2;
|
|
||||||
|
|
||||||
private static final int Y_1 = closeY + 5;
|
|
||||||
private static final int X_1 = closeX + 5;
|
|
||||||
private static final int Y_2 = closeY + 11;
|
|
||||||
private static final int X_2 = closeX + 11;
|
|
||||||
|
|
||||||
static final int WIDTH = 300;
|
|
||||||
static final int HEIGHT = 87;
|
|
||||||
private static final int PROGRESS_HEIGHT = HEIGHT - 2;
|
|
||||||
|
|
||||||
private final boolean showCloseButton;
|
|
||||||
private BufferedImage cachedImage;
|
|
||||||
private final Notify notification;
|
|
||||||
private final ImageIcon imageIcon;
|
|
||||||
|
|
||||||
// for the progress bar. we directly draw this onscreen
|
// for the progress bar. we directly draw this onscreen
|
||||||
// non-volatile because it's always accessed in the active render thread
|
// non-volatile because it's always accessed in the active render thread
|
||||||
private int progress = 0;
|
var progress = 0
|
||||||
|
|
||||||
private final Theme theme;
|
init {
|
||||||
final INotify parent;
|
val preferredSize = Dimension(WIDTH, HEIGHT)
|
||||||
|
setPreferredSize(preferredSize)
|
||||||
|
maximumSize = preferredSize
|
||||||
|
minimumSize = preferredSize
|
||||||
|
setSize(WIDTH, HEIGHT)
|
||||||
|
|
||||||
|
isFocusable = false
|
||||||
NotifyCanvas(final INotify parent, final Notify notification, final ImageIcon imageIcon, final Theme theme) {
|
background = theme.panel_BG
|
||||||
this.parent = parent;
|
showCloseButton = !notification.hideCloseButton
|
||||||
this.notification = notification;
|
|
||||||
this.imageIcon = imageIcon;
|
|
||||||
this.theme = theme;
|
|
||||||
|
|
||||||
final Dimension preferredSize = new Dimension(WIDTH, HEIGHT);
|
|
||||||
setPreferredSize(preferredSize);
|
|
||||||
setMaximumSize(preferredSize);
|
|
||||||
setMinimumSize(preferredSize);
|
|
||||||
setSize(WIDTH, HEIGHT);
|
|
||||||
|
|
||||||
setFocusable(false);
|
|
||||||
|
|
||||||
setBackground(this.theme.panel_BG);
|
|
||||||
showCloseButton = !notification.hideCloseButton;
|
|
||||||
|
|
||||||
// now we setup the rendering of the image
|
// now we setup the rendering of the image
|
||||||
cachedImage = renderBackgroundInfo(notification.title, notification.text, this.theme, this.imageIcon);
|
cachedImage = renderBackgroundInfo(notification.title, notification.text, theme, imageIcon)
|
||||||
}
|
}
|
||||||
|
|
||||||
void setProgress(final int progress) {
|
override fun paint(g: Graphics) {
|
||||||
this.progress = progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getProgress() {
|
|
||||||
return progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void paint(final Graphics g) {
|
|
||||||
// we cache the text + image (to another image), and then always render the close + progressbar
|
// we cache the text + image (to another image), and then always render the close + progressbar
|
||||||
|
|
||||||
// use our cached image, so we don't have to re-render text/background/etc
|
// use our cached image, so we don't have to re-render text/background/etc
|
||||||
try {
|
try {
|
||||||
g.drawImage(cachedImage, 0, 0, null);
|
g.drawImage(cachedImage, 0, 0, null)
|
||||||
} catch (Exception ignored) {
|
} catch (ignored: Exception) {
|
||||||
// have also seen (happened after screen/PC was "woken up", in Xubuntu 16.04):
|
// have also seen (happened after screen/PC was "woken up", in Xubuntu 16.04):
|
||||||
// java.lang.ClassCastException:sun.awt.image.BufImgSurfaceData cannot be cast to sun.java2d.xr.XRSurfaceData at sun.java2d.xr.XRPMBlitLoops.cacheToTmpSurface(XRPMBlitLoops.java:148)
|
// java.lang.ClassCastException:sun.awt.image.BufImgSurfaceData cannot be cast to sun.java2d.xr.XRSurfaceData at sun.java2d.xr.XRPMBlitLoops.cacheToTmpSurface(XRPMBlitLoops.java:148)
|
||||||
// at sun.java2d.xr.XrSwToPMBlit.Blit(XRPMBlitLoops.java:356)
|
// at sun.java2d.xr.XrSwToPMBlit.Blit(XRPMBlitLoops.java:356)
|
||||||
|
@ -110,130 +78,138 @@ class NotifyCanvas extends Canvas {
|
||||||
// at dorkbox.notify.NotifyCanvas.paint(NotifyCanvas.java:92)
|
// at dorkbox.notify.NotifyCanvas.paint(NotifyCanvas.java:92)
|
||||||
|
|
||||||
// redo the image
|
// redo the image
|
||||||
cachedImage = renderBackgroundInfo(notification.title, notification.text, this.theme, imageIcon);
|
cachedImage = renderBackgroundInfo(notification.title, notification.text, theme, imageIcon)
|
||||||
|
|
||||||
// try to draw again
|
// try to draw again
|
||||||
try {
|
try {
|
||||||
g.drawImage(cachedImage, 0, 0, null);
|
g.drawImage(cachedImage, 0, 0, null)
|
||||||
} catch (Exception ignored2) {
|
} catch (ignored2: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the progress bar and close button are the only things that can change, so we always draw them every time
|
// the progress bar and close button are the only things that can change, so we always draw them every time
|
||||||
Graphics2D g2 = (Graphics2D) g.create();
|
val g2 = g.create() as Graphics2D
|
||||||
try {
|
try {
|
||||||
if (showCloseButton) {
|
if (showCloseButton) {
|
||||||
// manually draw the close button
|
// manually draw the close button
|
||||||
Graphics2D g3 = (Graphics2D) g.create();
|
val g3 = g.create() as Graphics2D
|
||||||
|
g3.color = theme.panel_BG
|
||||||
|
g3.stroke = stroke
|
||||||
|
|
||||||
g3.setColor(theme.panel_BG);
|
val p = mousePosition
|
||||||
g3.setStroke(stroke);
|
|
||||||
|
|
||||||
final Point p = getMousePosition();
|
|
||||||
// reasonable position for detecting mouse over
|
// reasonable position for detecting mouse over
|
||||||
if (p != null && p.getX() >= 280 && p.getY() <= 20) {
|
if (p != null && p.getX() >= 280 && p.getY() <= 20) {
|
||||||
g3.setColor(Color.RED);
|
g3.color = Color.RED
|
||||||
}
|
} else {
|
||||||
else {
|
g3.color = theme.closeX_FG
|
||||||
g3.setColor(theme.closeX_FG);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw the X
|
// draw the X
|
||||||
g3.drawLine(X_1, Y_1, X_2, Y_2);
|
g3.drawLine(X_1, Y_1, X_2, Y_2)
|
||||||
g3.drawLine(X_2, Y_1, X_1, Y_2);
|
g3.drawLine(X_2, Y_1, X_1, Y_2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw the progress bar along the bottom
|
// draw the progress bar along the bottom
|
||||||
g2.setColor(theme.progress_FG);
|
g2.color = theme.progress_FG
|
||||||
g2.fillRect(0, PROGRESS_HEIGHT, progress, 2);
|
g2.fillRect(0, PROGRESS_HEIGHT, progress, 2)
|
||||||
} finally {
|
} finally {
|
||||||
g2.dispose();
|
g2.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return TRUE if we were over the 'X' or FALSE if the click was in the general area (and not over the 'X').
|
* @return TRUE if we were over the 'X' or FALSE if the click was in the general area (and not over the 'X').
|
||||||
*/
|
*/
|
||||||
boolean isCloseButton(final int x, final int y) {
|
fun isCloseButton(x: Int, y: Int): Boolean {
|
||||||
return showCloseButton && x >= 280 && y <= 20;
|
return showCloseButton && x >= 280 && y <= 20
|
||||||
}
|
}
|
||||||
|
|
||||||
private static
|
companion object {
|
||||||
BufferedImage renderBackgroundInfo(final String title,
|
private val stroke: Stroke = BasicStroke(2f)
|
||||||
final String notificationText,
|
|
||||||
final Theme theme,
|
|
||||||
final ImageIcon imageIcon) {
|
|
||||||
|
|
||||||
|
private const val closeX = 282
|
||||||
|
private const val closeY = 2
|
||||||
|
|
||||||
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
|
private const val Y_1 = closeY + 5
|
||||||
Graphics2D g2 = image.createGraphics();
|
private const val X_1 = closeX + 5
|
||||||
g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
private const val Y_2 = closeY + 11
|
||||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
private const val X_2 = closeX + 11
|
||||||
g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
|
|
||||||
g2.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
|
const val WIDTH = 300
|
||||||
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
const val HEIGHT = 87
|
||||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
private const val PROGRESS_HEIGHT = HEIGHT - 2
|
||||||
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
private fun renderBackgroundInfo(title: String, notificationText: String, theme: Theme, imageIcon: ImageIcon?): BufferedImage {
|
||||||
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
|
|
||||||
|
val image = BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB)
|
||||||
|
val g2 = image.createGraphics()
|
||||||
|
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY)
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY)
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE)
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON)
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
g2.setColor(theme.panel_BG);
|
g2.color = theme.panel_BG
|
||||||
g2.fillRect(0, 0, WIDTH, HEIGHT);
|
g2.fillRect(0, 0, WIDTH, HEIGHT)
|
||||||
|
|
||||||
// Draw the title text
|
// Draw the title text
|
||||||
g2.setColor(theme.titleText_FG);
|
g2.color = theme.titleText_FG
|
||||||
g2.setFont(theme.titleTextFont);
|
g2.font = theme.titleTextFont
|
||||||
g2.drawString(title, 5, 20);
|
g2.drawString(title, 5, 20)
|
||||||
|
|
||||||
|
|
||||||
int posX = 10;
|
var posX = 10
|
||||||
int posY = -8;
|
val posY = -8
|
||||||
int textLengthLimit = 108;
|
var textLengthLimit = 108
|
||||||
|
|
||||||
// ICON
|
// ICON
|
||||||
if (imageIcon != null) {
|
if (imageIcon != null) {
|
||||||
textLengthLimit = 88;
|
textLengthLimit = 88
|
||||||
posX = 60;
|
posX = 60
|
||||||
// Draw the image
|
// Draw the image
|
||||||
imageIcon.paintIcon(null, g2, 5, 30);
|
imageIcon.paintIcon(null, g2, 5, 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the main text
|
// Draw the main text
|
||||||
int length = notificationText.length();
|
var length = notificationText.length
|
||||||
StringBuilder text = new StringBuilder(length);
|
val text = StringBuilder(length)
|
||||||
|
|
||||||
// are we "html" already? just check for the starting tag and strip off END html tag
|
// are we "html" already? just check for the starting tag and strip off END html tag
|
||||||
if (length >= 13 && notificationText.regionMatches(true, length - 7, "</html>", 0, 7)) {
|
if (length >= 13 && notificationText.regionMatches(length - 7, "</html>", 0, 7, ignoreCase = true)) {
|
||||||
text.append(notificationText);
|
text.append(notificationText)
|
||||||
text.delete(text.length() - 7, text.length());
|
text.delete(text.length - 7, text.length)
|
||||||
|
length -= 7
|
||||||
length -= 7;
|
} else {
|
||||||
}
|
text.append("<html>")
|
||||||
else {
|
text.append(notificationText)
|
||||||
text.append("<html>");
|
|
||||||
text.append(notificationText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the text is the correct length
|
// make sure the text is the correct length
|
||||||
if (length > textLengthLimit) {
|
if (length > textLengthLimit) {
|
||||||
text.delete(6 + textLengthLimit, text.length());
|
text.delete(6 + textLengthLimit, text.length)
|
||||||
text.append("...");
|
text.append("...")
|
||||||
}
|
}
|
||||||
text.append("</html>");
|
text.append("</html>")
|
||||||
|
|
||||||
JLabel mainTextLabel = new JLabel();
|
|
||||||
mainTextLabel.setForeground(theme.mainText_FG);
|
|
||||||
mainTextLabel.setFont(theme.mainTextFont);
|
|
||||||
mainTextLabel.setText(text.toString());
|
|
||||||
mainTextLabel.setBounds(0, 0, WIDTH - posX - 2, HEIGHT);
|
|
||||||
|
|
||||||
g2.translate(posX, posY);
|
val mainTextLabel = JLabel()
|
||||||
mainTextLabel.paint(g2);
|
mainTextLabel.foreground = theme.mainText_FG
|
||||||
g2.translate(-posX, -posY);
|
mainTextLabel.font = theme.mainTextFont
|
||||||
|
mainTextLabel.text = text.toString()
|
||||||
|
mainTextLabel.setBounds(0, 0, WIDTH - posX - 2, HEIGHT)
|
||||||
|
|
||||||
|
g2.translate(posX, posY)
|
||||||
|
mainTextLabel.paint(g2)
|
||||||
|
g2.translate(-posX, -posY)
|
||||||
} finally {
|
} finally {
|
||||||
g2.dispose();
|
g2.dispose()
|
||||||
|
}
|
||||||
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,68 +13,56 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
import java.awt.GraphicsConfiguration;
|
import dorkbox.util.ScreenUtil
|
||||||
import java.awt.Insets;
|
import java.awt.Point
|
||||||
import java.awt.Point;
|
import java.awt.Toolkit
|
||||||
import java.awt.Toolkit;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import dorkbox.util.ScreenUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains a list of notification popups + the Y offset (if any)
|
* Contains a list of notification popups + the Y offset (if any)
|
||||||
*/
|
*/
|
||||||
class PopupList {
|
internal class PopupList {
|
||||||
private int offsetY = 0;
|
var offsetY = 0
|
||||||
private ArrayList<LookAndFeel> popups = new ArrayList<LookAndFeel>(4);
|
private set
|
||||||
|
|
||||||
|
private val popups = ArrayList<LookAndFeel>(4)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* have to adjust for offsets when the window-manager has a toolbar that consumes space and prevents overlap.
|
* have to adjust for offsets when the window-manager has a toolbar that consumes space and prevents overlap.
|
||||||
*
|
*
|
||||||
* this is only done on the 2nd popup is added to the list
|
* this is only done on the 2nd popup is added to the list
|
||||||
*/
|
*/
|
||||||
void calculateOffset(final boolean showFromTop, final int anchorX, final int anchorY) {
|
fun calculateOffset(showFromTop: Boolean, anchorX: Int, anchorY: Int) {
|
||||||
if (offsetY == 0) {
|
if (offsetY == 0) {
|
||||||
Point point = new Point(anchorX, anchorY);
|
val point = Point(anchorX, anchorY)
|
||||||
GraphicsConfiguration gc = ScreenUtil.getMonitorAtLocation(point)
|
val gc = ScreenUtil.getMonitorAtLocation(point).defaultConfiguration
|
||||||
.getDefaultConfiguration();
|
val screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc)
|
||||||
|
|
||||||
Insets screenInsets = Toolkit.getDefaultToolkit()
|
|
||||||
.getScreenInsets(gc);
|
|
||||||
|
|
||||||
if (showFromTop) {
|
if (showFromTop) {
|
||||||
if (screenInsets.top > 0) {
|
if (screenInsets.top > 0) {
|
||||||
offsetY = screenInsets.top - LookAndFeel.MARGIN;
|
offsetY = screenInsets.top - LookAndFeel.MARGIN
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (screenInsets.bottom > 0) {
|
if (screenInsets.bottom > 0) {
|
||||||
offsetY = screenInsets.bottom + LookAndFeel.MARGIN;
|
offsetY = screenInsets.bottom + LookAndFeel.MARGIN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int getOffsetY() {
|
fun size(): Int {
|
||||||
return offsetY;
|
return popups.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun add(lookAndFeel: LookAndFeel) {
|
||||||
int size() {
|
popups.add(lookAndFeel)
|
||||||
return popups.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void add(final LookAndFeel lookAndFeel) {
|
operator fun iterator(): MutableIterator<LookAndFeel> {
|
||||||
popups.add(lookAndFeel);
|
return popups.iterator()
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<LookAndFeel> iterator() {
|
operator fun get(index: Int): LookAndFeel {
|
||||||
return popups.iterator();
|
return popups[index]
|
||||||
}
|
|
||||||
|
|
||||||
LookAndFeel get(final int index) {
|
|
||||||
return popups.get(index);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,9 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
public
|
enum class Pos {
|
||||||
enum Pos {
|
|
||||||
/**
|
/**
|
||||||
* top vertically, left horizontally
|
* top vertically, left horizontally
|
||||||
*/
|
*/
|
||||||
|
@ -40,5 +39,5 @@ enum Pos {
|
||||||
/**
|
/**
|
||||||
* bottom vertically, right horizontally
|
* bottom vertically, right horizontally
|
||||||
*/
|
*/
|
||||||
BOTTOM_RIGHT,
|
BOTTOM_RIGHT
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,59 +13,58 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
import java.awt.Color;
|
import dorkbox.util.FontUtil
|
||||||
import java.awt.Font;
|
import java.awt.Color
|
||||||
|
import java.awt.Font
|
||||||
import dorkbox.util.FontUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings available to change the theme
|
* Settings available to change the theme
|
||||||
*/
|
*/
|
||||||
public
|
|
||||||
class Theme {
|
class Theme {
|
||||||
final Color panel_BG;
|
val panel_BG: Color
|
||||||
final Color titleText_FG;
|
val titleText_FG: Color
|
||||||
final Color mainText_FG;
|
val mainText_FG: Color
|
||||||
final Color closeX_FG;
|
val closeX_FG: Color
|
||||||
final Color progress_FG;
|
val progress_FG: Color
|
||||||
|
val titleTextFont: Font
|
||||||
|
val mainTextFont: Font
|
||||||
|
|
||||||
final Font titleTextFont;
|
constructor(titleTextFont: String, mainTextFont: String, isDarkTheme: Boolean) {
|
||||||
final Font mainTextFont;
|
this.titleTextFont = FontUtil.parseFont(titleTextFont)
|
||||||
|
this.mainTextFont = FontUtil.parseFont(mainTextFont)
|
||||||
|
|
||||||
Theme(final String titleTextFont, final String mainTextFont, boolean isDarkTheme) {
|
|
||||||
this.titleTextFont = FontUtil.parseFont(titleTextFont);
|
|
||||||
this.mainTextFont = FontUtil.parseFont(mainTextFont);
|
|
||||||
|
|
||||||
if (isDarkTheme) {
|
if (isDarkTheme) {
|
||||||
panel_BG = Color.DARK_GRAY;
|
panel_BG = Color.DARK_GRAY
|
||||||
titleText_FG = Color.GRAY;
|
titleText_FG = Color.GRAY
|
||||||
mainText_FG = Color.LIGHT_GRAY;
|
mainText_FG = Color.LIGHT_GRAY
|
||||||
closeX_FG = Color.GRAY;
|
closeX_FG = Color.GRAY
|
||||||
progress_FG = Color.gray;
|
progress_FG = Color.gray
|
||||||
}
|
} else {
|
||||||
else {
|
panel_BG = Color.WHITE
|
||||||
panel_BG = Color.WHITE;
|
titleText_FG = Color.GRAY.darker()
|
||||||
titleText_FG = Color.GRAY.darker();
|
mainText_FG = Color.GRAY
|
||||||
mainText_FG = Color.GRAY;
|
closeX_FG = Color.LIGHT_GRAY
|
||||||
closeX_FG = Color.LIGHT_GRAY;
|
progress_FG = Color(0x42A5F5)
|
||||||
progress_FG = new Color(0x42A5F5);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public
|
constructor(
|
||||||
Theme(final String titleTextFont, final String mainTextFont,
|
titleTextFont: String,
|
||||||
final Color panel_BG, final Color titleText_FG, final Color mainText_FG,
|
mainTextFont: String,
|
||||||
final Color closeX_FG, final Color progress_FG) {
|
panel_BG: Color,
|
||||||
this.titleTextFont = FontUtil.parseFont(titleTextFont);
|
titleText_FG: Color,
|
||||||
this.mainTextFont = FontUtil.parseFont(mainTextFont);
|
mainText_FG: Color,
|
||||||
|
closeX_FG: Color,
|
||||||
this.panel_BG = panel_BG;
|
progress_FG: Color
|
||||||
this.titleText_FG = titleText_FG;
|
) {
|
||||||
this.mainText_FG = mainText_FG;
|
this.titleTextFont = FontUtil.parseFont(titleTextFont)
|
||||||
this.closeX_FG = closeX_FG;
|
this.mainTextFont = FontUtil.parseFont(mainTextFont)
|
||||||
this.progress_FG = progress_FG;
|
this.panel_BG = panel_BG
|
||||||
|
this.titleText_FG = titleText_FG
|
||||||
|
this.mainText_FG = mainText_FG
|
||||||
|
this.closeX_FG = closeX_FG
|
||||||
|
this.progress_FG = progress_FG
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,31 +13,28 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package dorkbox.notify;
|
package dorkbox.notify
|
||||||
|
|
||||||
import java.awt.event.WindowEvent;
|
import java.awt.event.WindowAdapter
|
||||||
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
class WindowAdapter extends java.awt.event.WindowAdapter {
|
internal class WindowAdapter : WindowAdapter() {
|
||||||
@Override
|
override fun windowClosing(e: WindowEvent) {
|
||||||
public
|
if (e.newState != WindowEvent.WINDOW_CLOSED) {
|
||||||
void windowClosing(WindowEvent e) {
|
val source = e.source as AsDesktop
|
||||||
if (e.getNewState() != WindowEvent.WINDOW_CLOSED) {
|
source.close()
|
||||||
AsDesktop source = (AsDesktop) e.getSource();
|
|
||||||
source.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun windowLostFocus(e: WindowEvent) {
|
||||||
public
|
if (e.newState != WindowEvent.WINDOW_CLOSED) {
|
||||||
void windowLostFocus(WindowEvent e) {
|
val source = e.source as AsDesktop
|
||||||
if (e.getNewState() != WindowEvent.WINDOW_CLOSED) {
|
|
||||||
AsDesktop source = (AsDesktop) e.getSource();
|
|
||||||
// these don't work
|
// these don't work
|
||||||
//toFront();
|
//toFront()
|
||||||
//requestFocus();
|
//requestFocus()
|
||||||
//requestFocusInWindow();
|
//requestFocusInWindow()
|
||||||
source.setAlwaysOnTop(false);
|
source.isAlwaysOnTop = false
|
||||||
source.setAlwaysOnTop(true);
|
source.isAlwaysOnTop = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
24
src9/dorkbox/notify/EmptyClass.java
Executable file
24
src9/dorkbox/notify/EmptyClass.java
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.notify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required for intellij to not complain regarding `module-info` for a multi-release jar.
|
||||||
|
* This file is completely ignored by the gradle build process
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class EmptyClass {}
|
11
src9/module-info.java
Normal file
11
src9/module-info.java
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
module dorkbox.notify {
|
||||||
|
exports dorkbox.notify;
|
||||||
|
|
||||||
|
// requires transitive dorkbox.updates;
|
||||||
|
//
|
||||||
|
// requires static com.esotericsoftware.kryo;
|
||||||
|
// requires static io.netty.common;
|
||||||
|
// requires static io.netty.buffer;
|
||||||
|
//
|
||||||
|
// requires transitive kotlin.stdlib;
|
||||||
|
}
|
BIN
src9/notify-dark.png
Executable file
BIN
src9/notify-dark.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
|
@ -1,227 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
import java.awt.FlowLayout;
|
|
||||||
import java.awt.Image;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JFrame;
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
|
|
||||||
import dorkbox.notify.Notify;
|
|
||||||
import dorkbox.notify.Pos;
|
|
||||||
import dorkbox.util.ImageUtil;
|
|
||||||
import dorkbox.util.LocationResolver;
|
|
||||||
import dorkbox.util.ScreenUtil;
|
|
||||||
|
|
||||||
public
|
|
||||||
class NotifyTest {
|
|
||||||
|
|
||||||
public static
|
|
||||||
void main(String[] args) {
|
|
||||||
Notify notify;
|
|
||||||
|
|
||||||
JFrame frame = new JFrame("Test");
|
|
||||||
|
|
||||||
JPanel panel = new JPanel();
|
|
||||||
panel.setLayout(new FlowLayout());
|
|
||||||
|
|
||||||
JLabel label = new JLabel("This is a label!");
|
|
||||||
|
|
||||||
JButton button = new JButton();
|
|
||||||
button.setText("Press me");
|
|
||||||
|
|
||||||
panel.add(label);
|
|
||||||
panel.add(button);
|
|
||||||
|
|
||||||
frame.add(panel);
|
|
||||||
frame.setSize(900, 600);
|
|
||||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
|
||||||
frame.setVisible(true);
|
|
||||||
|
|
||||||
ScreenUtil.showOnSameScreenAsMouse_Center(frame);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int count = 2;
|
|
||||||
|
|
||||||
// for (int i = 0; i < count; i++) {
|
|
||||||
// final int finalI = i;
|
|
||||||
// notify = Notify.create()
|
|
||||||
// .title("Notify title " + i)
|
|
||||||
// .text("This is a notification " + i + " popup message This is a notification popup message This is a " +
|
|
||||||
// "notification popup message")
|
|
||||||
// .hideAfter(13000)
|
|
||||||
// .position(Pos.BOTTOM_RIGHT)
|
|
||||||
// // .position(Pos.CENTER)
|
|
||||||
// // .setScreen(0)
|
|
||||||
// .darkStyle()
|
|
||||||
// // .shake(1300, 4)
|
|
||||||
// .shake(1300, 10)
|
|
||||||
// .attach(frame)
|
|
||||||
// .hideCloseButton()
|
|
||||||
// .onAction(new ActionHandler<Notify>() {
|
|
||||||
// @Override
|
|
||||||
// public
|
|
||||||
// void handle(final Notify arg0) {
|
|
||||||
// System.err.println("Notification " + finalI + " clicked on!");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// notify.showWarning();
|
|
||||||
//
|
|
||||||
// // try {
|
|
||||||
// // Thread.sleep(1000);
|
|
||||||
// // } catch (InterruptedException e) {
|
|
||||||
// // e.printStackTrace();
|
|
||||||
// // }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for (int i = 0; i < count; i++) {
|
|
||||||
// final int finalI = i;
|
|
||||||
// notify = Notify.create()
|
|
||||||
// .title("Notify title " + i)
|
|
||||||
// .text("This is a notification " + i + " popup message This is a notification popup message This is a " +
|
|
||||||
// "notification popup message")
|
|
||||||
// .hideAfter(13000)
|
|
||||||
// .position(Pos.TOP_LEFT)
|
|
||||||
// // .position(Pos.CENTER)
|
|
||||||
// // .setScreen(0)
|
|
||||||
// // .darkStyle()
|
|
||||||
// // .shake(1300, 4)
|
|
||||||
// // .shake(1300, 10)
|
|
||||||
// .attach(frame)
|
|
||||||
// // .hideCloseButton()
|
|
||||||
// .onAction(new ActionHandler<Notify>() {
|
|
||||||
// @Override
|
|
||||||
// public
|
|
||||||
// void handle(final Notify arg0) {
|
|
||||||
// System.err.println("Notification " + finalI + " clicked on!");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// notify.showWarning();
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// Thread.sleep(1000);
|
|
||||||
// } catch (InterruptedException e) {
|
|
||||||
// e.printStackTrace();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// for (int i = 0; i < count; i++) {
|
|
||||||
// final int finalI = i;
|
|
||||||
// notify = Notify.create()
|
|
||||||
// .title("Notify title " + i)
|
|
||||||
// .text("This is a notification " + i + " popup message This is a notification popup message This is a " +
|
|
||||||
// "notification popup message")
|
|
||||||
// .hideAfter(3000)
|
|
||||||
// .position(Pos.TOP_RIGHT)
|
|
||||||
// // .setScreen(0)
|
|
||||||
// .darkStyle()
|
|
||||||
// // .shake(1300, 4)
|
|
||||||
// .shake(1300, 10)
|
|
||||||
// .hideCloseButton()
|
|
||||||
// .onAction(new ActionHandler<Notify>() {
|
|
||||||
// @Override
|
|
||||||
// public
|
|
||||||
// void handle(final Notify arg0) {
|
|
||||||
// System.err.println("Notification " + finalI + " clicked on!");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// notify.show();
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// Thread.sleep(1000);
|
|
||||||
// } catch (InterruptedException e) {
|
|
||||||
// e.printStackTrace();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// The purpose of this, is to display this image AS A SQUARE!
|
|
||||||
InputStream resourceAsStream = LocationResolver.getResourceAsStream("notify-dark.png");
|
|
||||||
Image image = null;
|
|
||||||
try {
|
|
||||||
image = ImageIO.read(resourceAsStream);
|
|
||||||
ImageUtil.waitForImageLoad(image);
|
|
||||||
|
|
||||||
// image = image.getScaledInstance(144, 104, Image.SCALE_SMOOTH);
|
|
||||||
// image = image.getScaledInstance(104, 144, Image.SCALE_SMOOTH);
|
|
||||||
image = image.getScaledInstance(144, 144, Image.SCALE_SMOOTH);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
notify = Notify.create()
|
|
||||||
.title("Notify scaled")
|
|
||||||
.text("This is a notification popup message scaled This is a notification popup message This is a " +
|
|
||||||
"notification popup message scaled ")
|
|
||||||
// .hideAfter(13000)
|
|
||||||
.position(Pos.BOTTOM_LEFT)
|
|
||||||
// .setScreen(0)
|
|
||||||
// .darkStyle()
|
|
||||||
// .shake(1300, 4)
|
|
||||||
// .shake(1300, 10)
|
|
||||||
// .hideCloseButton()
|
|
||||||
.onAction(arg0->System.err.println("Notification scaled clicked on!"));
|
|
||||||
|
|
||||||
notify.image(image);
|
|
||||||
notify.show();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// for (int i = 0; i < count; i++) {
|
|
||||||
// final int finalI = i;
|
|
||||||
// notify = Notify.create()
|
|
||||||
// .title("Notify title " + i)
|
|
||||||
// .text("This is a notification " + i + " popup message This is a notification popup message This is a " +
|
|
||||||
// "notification popup message")
|
|
||||||
// // .hideAfter(13000)
|
|
||||||
// .position(Pos.BOTTOM_LEFT)
|
|
||||||
// // .setScreen(0)
|
|
||||||
// // .darkStyle()
|
|
||||||
// // .shake(1300, 4)
|
|
||||||
// // .shake(1300, 10)
|
|
||||||
// // .hideCloseButton()
|
|
||||||
// .onAction(new ActionHandler<Notify>() {
|
|
||||||
// @Override
|
|
||||||
// public
|
|
||||||
// void handle(final Notify arg0) {
|
|
||||||
// System.err.println("Notification " + finalI + " clicked on!");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// if (i == 0) {
|
|
||||||
// notify.image(image);
|
|
||||||
// notify.show();
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// notify.showConfirm();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// Thread.sleep(1000);
|
|
||||||
// } catch (InterruptedException e) {
|
|
||||||
// e.printStackTrace();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
176
test/dorkbox/notify/NotifyTest.kt
Executable file
176
test/dorkbox/notify/NotifyTest.kt
Executable file
|
@ -0,0 +1,176 @@
|
||||||
|
package dorkbox.notify
|
||||||
|
|
||||||
|
import dorkbox.util.ImageUtil
|
||||||
|
import dorkbox.util.LocationResolver
|
||||||
|
import dorkbox.util.ScreenUtil
|
||||||
|
import java.awt.FlowLayout
|
||||||
|
import java.awt.Image
|
||||||
|
import java.io.IOException
|
||||||
|
import javax.imageio.ImageIO
|
||||||
|
import javax.swing.JButton
|
||||||
|
import javax.swing.JFrame
|
||||||
|
import javax.swing.JLabel
|
||||||
|
import javax.swing.JPanel
|
||||||
|
|
||||||
|
object NotifyTest {
|
||||||
|
@JvmStatic
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val frame = JFrame("Test")
|
||||||
|
val panel = JPanel()
|
||||||
|
panel.layout = FlowLayout()
|
||||||
|
val label = JLabel("This is a label!")
|
||||||
|
val button = JButton()
|
||||||
|
button.text = "Press me"
|
||||||
|
button.addActionListener { println("Clicked button!") }
|
||||||
|
|
||||||
|
panel.add(label)
|
||||||
|
panel.add(button)
|
||||||
|
frame.add(panel)
|
||||||
|
frame.setSize(900, 600)
|
||||||
|
frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
|
||||||
|
frame.isVisible = true
|
||||||
|
|
||||||
|
ScreenUtil.showOnSameScreenAsMouse_Center(frame)
|
||||||
|
|
||||||
|
// The purpose of this, is to display this image AS A SQUARE!
|
||||||
|
val resourceAsStream = LocationResolver.getResourceAsStream("notify-dark.png")
|
||||||
|
var image: Image? = null
|
||||||
|
try {
|
||||||
|
image = ImageIO.read(resourceAsStream)
|
||||||
|
ImageUtil.waitForImageLoad(image)
|
||||||
|
|
||||||
|
// image = image.getScaledInstance(144, 104, Image.SCALE_SMOOTH);
|
||||||
|
// image = image.getScaledInstance(104, 144, Image.SCALE_SMOOTH);
|
||||||
|
image = image.getScaledInstance(144, 144, Image.SCALE_SMOOTH)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
// bottomRightInFrame(3, frame)
|
||||||
|
// topLeftInFrame(3, frame)
|
||||||
|
|
||||||
|
topRightMonitor(3)
|
||||||
|
// bottomLeftScaled(3, frame, image)
|
||||||
|
// bottomLeftStacking(3, frame, image)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun topRightMonitor(count: Int) {
|
||||||
|
var notify: Notify
|
||||||
|
|
||||||
|
for (i in 0 until count) {
|
||||||
|
notify = Notify.create()
|
||||||
|
.title("Notify title $i")
|
||||||
|
.text("This is a notification " + i + " popup message This is a notification popup message This is a " +
|
||||||
|
"notification popup message")
|
||||||
|
.hideAfter(13000)
|
||||||
|
.position(Pos.TOP_RIGHT)
|
||||||
|
// .setScreen(0)
|
||||||
|
.darkStyle()
|
||||||
|
// .shake(1300, 4)
|
||||||
|
.shake(4300, 10)
|
||||||
|
.hideCloseButton()
|
||||||
|
.onAction { System.err.println("Notification $i clicked on!") }
|
||||||
|
notify.show()
|
||||||
|
try {
|
||||||
|
Thread.sleep(3000)
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bottomLeftScaled(image: Image) {
|
||||||
|
val notify = Notify.create()
|
||||||
|
.title("Notify scaled")
|
||||||
|
.text("This is a notification popup message scaled This is a notification popup message This is a " +
|
||||||
|
"notification popup message scaled ") // .hideAfter(13000)
|
||||||
|
.position(Pos.BOTTOM_LEFT) // .setScreen(0)
|
||||||
|
// .darkStyle()
|
||||||
|
// .shake(1300, 4)
|
||||||
|
// .shake(1300, 10)
|
||||||
|
// .hideCloseButton()
|
||||||
|
.onAction { System.err.println("Notification scaled clicked on!") }
|
||||||
|
notify.image(image)
|
||||||
|
notify.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bottomLeftStacking(count: Int, image: Image) {
|
||||||
|
var notify: Notify
|
||||||
|
|
||||||
|
for (i in 0 until count) {
|
||||||
|
notify = Notify.create()
|
||||||
|
.title("Notify title $i")
|
||||||
|
.text("This is a notification " + i + " popup message This is a notification popup message This is a " +
|
||||||
|
"notification popup message")
|
||||||
|
// .hideAfter(13000)
|
||||||
|
.position(Pos.BOTTOM_LEFT)
|
||||||
|
// .setScreen(0)
|
||||||
|
// .darkStyle()
|
||||||
|
// .shake(1300, 4)
|
||||||
|
// .shake(1300, 10)
|
||||||
|
// .hideCloseButton()
|
||||||
|
.onAction { System.err.println("Notification $i clicked on!") }
|
||||||
|
if (i == 0) {
|
||||||
|
notify.image(image)
|
||||||
|
notify.show()
|
||||||
|
} else {
|
||||||
|
notify.showConfirm()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000)
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun topLeftInFrame(count: Int, frame: JFrame) {
|
||||||
|
var notify: Notify
|
||||||
|
for (i in 0 until count) {
|
||||||
|
notify = Notify.create()
|
||||||
|
.title("Notify title $i")
|
||||||
|
.text("This is a notification " + i + " popup message This is a notification popup message This is a " +
|
||||||
|
"notification popup message")
|
||||||
|
.hideAfter(13000)
|
||||||
|
.position(Pos.TOP_LEFT) // .position(Pos.CENTER)
|
||||||
|
// .setScreen(0)
|
||||||
|
// .darkStyle()
|
||||||
|
// .shake(1300, 4)
|
||||||
|
// .shake(1300, 10)
|
||||||
|
.attach(frame) // .hideCloseButton()
|
||||||
|
.onAction { System.err.println("Notification $i clicked on!") }
|
||||||
|
notify.showWarning()
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(3000)
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bottomRightInFrame(count: Int, frame: JFrame) {
|
||||||
|
var notify: Notify
|
||||||
|
for (i in 0 until count) {
|
||||||
|
notify = Notify.create()
|
||||||
|
.title("Notify title $i")
|
||||||
|
.text("This is a notification " + i + " popup message This is a notification popup message This is a " +
|
||||||
|
"notification popup message")
|
||||||
|
.hideAfter(13000)
|
||||||
|
.position(Pos.BOTTOM_RIGHT) // .position(Pos.CENTER)
|
||||||
|
// .setScreen(0)
|
||||||
|
.darkStyle() // .shake(1300, 4)
|
||||||
|
.shake(1300, 10)
|
||||||
|
.attach(frame)
|
||||||
|
.hideCloseButton()
|
||||||
|
.onAction { System.err.println("Notification $i clicked on!") }
|
||||||
|
notify.showWarning()
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000)
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user