diff --git a/src/dorkbox/notify/ActionHandler.kt b/src/dorkbox/notify/ActionHandler.kt
index c83c62f..a2776c7 100755
--- a/src/dorkbox/notify/ActionHandler.kt
+++ b/src/dorkbox/notify/ActionHandler.kt
@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package dorkbox.notify;
+package dorkbox.notify
-public
interface ActionHandler {
- void handle(T value);
+ fun handle(value: T)
}
diff --git a/src/dorkbox/notify/AsApplication.kt b/src/dorkbox/notify/AsApplication.kt
index 1dd4c40..dfbb4f7 100755
--- a/src/dorkbox/notify/AsApplication.kt
+++ b/src/dorkbox/notify/AsApplication.kt
@@ -13,115 +13,86 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package dorkbox.notify;
+package dorkbox.notify
-import java.awt.Component;
-import java.awt.Frame;
-import java.awt.event.ComponentEvent;
-import java.awt.event.ComponentListener;
-import java.awt.event.WindowEvent;
-import java.awt.event.WindowStateListener;
+import dorkbox.util.SwingUtil
+import java.awt.Frame
+import java.awt.event.ComponentEvent
+import java.awt.event.ComponentListener
+import java.awt.event.WindowStateListener
+import javax.swing.ImageIcon
+import javax.swing.JFrame
+import javax.swing.JPanel
-import javax.swing.ImageIcon;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
+// this is a child to a Jframe/window (instead of globally to the screen).
+class AsApplication internal constructor(private val notification: Notify, image: ImageIcon?, private val appWindow: JFrame, theme: Theme) : INotify {
+ 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)
-@SuppressWarnings({"Duplicates", "FieldCanBeLocal", "WeakerAccess", "DanglingJavadoc"})
-public
-class AsApplication implements INotify {
- 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);
+ // NOTE: this is on the swing EDT
+ init {
+ notifyCanvas = NotifyCanvas(this, notification, image, theme)
+ look = LookAndFeel(this, appWindow, notifyCanvas, notification, appWindow.bounds, false)
// this makes sure that our notify canvas stay anchored to the parent window (if it's hidden/shown/moved/etc)
- parentListener = new ComponentListener() {
- @Override
- public
- void componentShown(final ComponentEvent e) {
- look.reLayout(appWindow.getBounds());
+ parentListener = object : ComponentListener {
+ override fun componentShown(e: ComponentEvent) {
+ look.reLayout(appWindow.bounds)
}
- @Override
- public
- void componentHidden(final ComponentEvent e) {
+ override fun componentHidden(e: ComponentEvent) {}
+
+ override fun componentResized(e: ComponentEvent) {
+ look.reLayout(appWindow.bounds)
}
- @Override
- public
- void componentResized(final ComponentEvent e) {
- look.reLayout(appWindow.getBounds());
+ override fun componentMoved(e: ComponentEvent) {}
+ }
+
+ windowStateListener = WindowStateListener { e ->
+ val state = e.newState
+ if (state and Frame.ICONIFIED == 0) {
+ look.reLayout(appWindow.bounds)
}
+ }
- @Override
- public
- void componentMoved(final ComponentEvent e) {
- }
- };
-
- 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);
- appWindow.addComponentListener(parentListener);
-
- Component glassPane_ = appWindow.getGlassPane();
- if (glassPane_ instanceof JPanel) {
- glassPane = (JPanel) glassPane_;
- String name = glassPane.getName();
-
- if (!name.equals(glassPanePrefix)) {
+ val glassPane_ = appWindow.glassPane
+ if (glassPane_ is JPanel) {
+ glassPane = glassPane_
+ val name = glassPane_.name
+ if (name != glassPanePrefix) {
// We just tweak the already existing glassPane, instead of replacing it with our own
// glassPane = new JPanel();
- glassPane.setLayout(null);
- glassPane.setName(glassPanePrefix);
+ glassPane_.layout = null
+ glassPane_.name = glassPanePrefix
// glassPane.setSize(appWindow.getSize());
// glassPane.setOpaque(false);
// appWindow.setGlassPane(glassPane);
}
- glassPane.add(notifyCanvas);
+ glassPane_.add(notifyCanvas)
- if (!glassPane.isVisible()) {
- glassPane.setVisible(true);
+ if (!glassPane_.isVisible) {
+ glassPane_.isVisible = true
}
} else {
- System.err.println("Not able to add notification to custom glassPane");
+ System.err.println("Not able to add notification to custom glassPane")
}
}
- @Override
- public
- void onClick(final int x, final int y) {
- look.onClick(x, y);
+ override fun onClick(x: Int, y: Int) {
+ look.onClick(x, y)
}
/**
@@ -130,51 +101,39 @@ class AsApplication implements INotify {
* @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.
*/
- @Override
- public
- void shake(final int durationInMillis, final int amplitude) {
- look.shake(durationInMillis, amplitude);
+ override fun shake(durationInMillis: Int, amplitude: Int) {
+ look.shake(durationInMillis, amplitude)
}
- @Override
- public
- void setVisible(final boolean visible) {
+ override fun setVisible(visible: Boolean) {
// this is because the order of operations are different based upon visibility.
- look.updatePositionsPre(visible);
- look.updatePositionsPost(visible);
+ look.updatePositionsPre(visible)
+ look.updatePositionsPost(visible)
}
- @Override
- public
- void close() {
+ override fun close() {
// this must happen in the Swing EDT. This is usually called by the active renderer
- SwingUtil.invokeLater(new Runnable() {
- @Override
- public
- void run() {
- look.close();
+ SwingUtil.invokeLater {
+ look.close()
+ glassPane!!.remove(notifyCanvas)
+ appWindow.removeWindowStateListener(windowStateListener)
+ appWindow.removeComponentListener(parentListener)
- glassPane.remove(notifyCanvas);
-
- appWindow.removeWindowStateListener(windowStateListener);
- appWindow.removeComponentListener(parentListener);
-
- boolean found = false;
- Component[] components = glassPane.getComponents();
- for (Component component : components) {
- if (component instanceof NotifyCanvas) {
- found = true;
- break;
- }
+ var found = false
+ val components = glassPane!!.components
+ for (component in components) {
+ if (component is NotifyCanvas) {
+ found = true
+ break
}
-
- if (!found) {
- // hide the glass pane if there are no more notifications on it.
- glassPane.setVisible(false);
- }
-
- notification.onClose();
}
- });
+
+ if (!found) {
+ // hide the glass pane if there are no more notifications on it.
+ glassPane!!.isVisible = false
+ }
+
+ notification.onClose()
+ }
}
}
diff --git a/src/dorkbox/notify/AsDesktop.kt b/src/dorkbox/notify/AsDesktop.kt
index 5b38464..321ddb2 100755
--- a/src/dorkbox/notify/AsDesktop.kt
+++ b/src/dorkbox/notify/AsDesktop.kt
@@ -13,86 +13,66 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package dorkbox.notify;
+package dorkbox.notify
-import java.awt.Dimension;
-import java.awt.GraphicsDevice;
-import java.awt.GraphicsEnvironment;
-import java.awt.MouseInfo;
-import java.awt.Point;
-import java.awt.Rectangle;
-
-import javax.swing.ImageIcon;
-import javax.swing.JWindow;
-
-import dorkbox.util.ScreenUtil;
-import dorkbox.util.SwingUtil;
+import dorkbox.util.ScreenUtil
+import dorkbox.util.SwingUtil
+import java.awt.Dimension
+import java.awt.GraphicsEnvironment
+import java.awt.MouseInfo
+import javax.swing.ImageIcon
+import javax.swing.JWindow
// 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
-@SuppressWarnings({"Duplicates", "FieldCanBeLocal", "WeakerAccess", "DanglingJavadoc"})
-public
-class AsDesktop extends JWindow implements INotify {
- private static final long serialVersionUID = 1L;
-
- private final LookAndFeel look;
- private final Notify notification;
-
-
- // this is on the swing EDT
- @SuppressWarnings("NumericCastThatLosesPrecision")
- AsDesktop(final Notify notification, final ImageIcon image, final Theme theme) {
- this.notification = notification;
-
- setAlwaysOnTop(true);
-
- final Dimension preferredSize = new Dimension(WIDTH, HEIGHT);
- setPreferredSize(preferredSize);
- setMaximumSize(preferredSize);
- setMinimumSize(preferredSize);
- setSize(NotifyCanvas.WIDTH, NotifyCanvas.HEIGHT);
- setLocation(Short.MIN_VALUE, Short.MIN_VALUE);
-
- Rectangle bounds;
- GraphicsDevice device;
-
- if (notification.screenNumber == Short.MIN_VALUE) {
- // set screen position based on mouse
- Point mouseLocation = MouseInfo.getPointerInfo()
- .getLocation();
-
- device = ScreenUtil.getMonitorAtLocation(mouseLocation);
- }
- else {
- // set screen position based on specified screen
- int screenNumber = notification.screenNumber;
- GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
- GraphicsDevice screenDevices[] = ge.getScreenDevices();
-
- if (screenNumber < 0) {
- screenNumber = 0;
- }
- else if (screenNumber > screenDevices.length - 1) {
- screenNumber = screenDevices.length - 1;
- }
-
- device = screenDevices[screenNumber];
- }
-
- bounds = device.getDefaultConfiguration()
- .getBounds();
-
-
- NotifyCanvas notifyCanvas = new NotifyCanvas(this, notification, image, theme);
- getContentPane().add(notifyCanvas);
-
- look = new LookAndFeel(this, this, notifyCanvas, notification, bounds, true);
+class AsDesktop internal constructor(private val notification: Notify, image: ImageIcon?, theme: Theme) : JWindow(), INotify {
+ companion object {
+ private const val serialVersionUID = 1L
}
- @Override
- public
- void onClick(final int x, final int y) {
- look.onClick(x, y);
+ private val look: LookAndFeel
+
+ // this is on the swing EDT
+ init {
+ isAlwaysOnTop = true
+
+ preferredSize = Dimension(WIDTH, HEIGHT)
+ maximumSize = preferredSize
+ minimumSize = preferredSize
+
+ setSize(NotifyCanvas.WIDTH, NotifyCanvas.HEIGHT)
+ setLocation(Short.MIN_VALUE.toInt(), Short.MIN_VALUE.toInt())
+
+ val device = if (notification.screenNumber == Short.MIN_VALUE.toInt()) {
+ // set screen position based on mouse
+ val mouseLocation = MouseInfo.getPointerInfo().location
+ ScreenUtil.getMonitorAtLocation(mouseLocation)
+ } else {
+ // set screen position based on specified screen
+ var screenNumber = notification.screenNumber
+ val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
+ val screenDevices = ge.screenDevices
+
+ if (screenNumber < 0) {
+ screenNumber = 0
+ } else if (screenNumber > screenDevices.size - 1) {
+ screenNumber = screenDevices.size - 1
+ }
+
+ screenDevices[screenNumber]
+ }
+
+ val bounds = device.defaultConfiguration.bounds
+
+
+ val notifyCanvas = NotifyCanvas(this, notification, image, theme)
+ contentPane.add(notifyCanvas)
+
+ look = LookAndFeel(this, this, notifyCanvas, notification, bounds, true)
+ }
+
+ override fun onClick(x: Int, y: Int) {
+ look.onClick(x, y)
}
/**
@@ -101,55 +81,41 @@ class AsDesktop extends JWindow implements INotify {
* @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.
*/
- @Override
- public
- void shake(final int durationInMillis, final int amplitude) {
- look.shake(durationInMillis, amplitude);
+ override fun shake(durationInMillis: Int, amplitude: Int) {
+ look.shake(durationInMillis, amplitude)
}
- @Override
- public
- void setVisible(final boolean visible) {
+ override fun setVisible(visible: Boolean) {
// was it already visible?
- if (visible == isVisible()) {
+ if (visible == isVisible) {
// prevent "double setting" visible state
- return;
+ return
}
// this is because the order of operations are different based upon visibility.
- look.updatePositionsPre(visible);
-
- super.setVisible(visible);
+ look.updatePositionsPre(visible)
+ super.setVisible(visible)
// this is because the order of operations are different based upon visibility.
- look.updatePositionsPost(visible);
-
+ look.updatePositionsPost(visible)
if (visible) {
- this.toFront();
+ toFront()
}
}
// setVisible(false) with any extra logic
- void doHide() {
- super.setVisible(false);
+ fun doHide() {
+ super.setVisible(false)
}
- @Override
- public
- void close() {
+ override fun close() {
// this must happen in the Swing EDT. This is usually called by the active renderer
- SwingUtil.invokeLater(new Runnable() {
- @Override
- public
- void run() {
- doHide();
- look.close();
-
- removeAll();
- dispose();
-
- notification.onClose();
- }
- });
+ SwingUtil.invokeLater {
+ doHide()
+ look.close()
+ removeAll()
+ dispose()
+ notification.onClose()
+ }
}
}
diff --git a/src/dorkbox/notify/ClickAdapter.kt b/src/dorkbox/notify/ClickAdapter.kt
index 8fd68ec..2981c0b 100755
--- a/src/dorkbox/notify/ClickAdapter.kt
+++ b/src/dorkbox/notify/ClickAdapter.kt
@@ -13,20 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package dorkbox.notify;
+package dorkbox.notify
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
+import java.awt.event.MouseAdapter
+import java.awt.event.MouseEvent
-class ClickAdapter extends MouseAdapter {
-
- ClickAdapter() {
- }
-
- @Override
- public
- void mouseReleased(final MouseEvent e) {
- INotify parent = ((NotifyCanvas) e.getSource()).parent;
- parent.onClick(e.getX(), e.getY());
+internal class ClickAdapter : MouseAdapter() {
+ override fun mouseReleased(e: MouseEvent) {
+ val parent = (e.source as NotifyCanvas).parent
+ parent.onClick(e.x, e.y)
}
}
diff --git a/src/dorkbox/notify/INotify.kt b/src/dorkbox/notify/INotify.kt
index 5632984..7df799a 100755
--- a/src/dorkbox/notify/INotify.kt
+++ b/src/dorkbox/notify/INotify.kt
@@ -13,15 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package dorkbox.notify;
+package dorkbox.notify
-public
interface INotify {
- void close();
-
- void shake(int durationInMillis, int amplitude);
-
- void setVisible(boolean b);
-
- void onClick(int x, int y);
+ fun close()
+ fun shake(durationInMillis: Int, amplitude: Int)
+ fun setVisible(visible: Boolean)
+ fun onClick(x: Int, y: Int)
}
diff --git a/src/dorkbox/notify/LookAndFeel.kt b/src/dorkbox/notify/LookAndFeel.kt
index 8e14286..09d84e8 100755
--- a/src/dorkbox/notify/LookAndFeel.kt
+++ b/src/dorkbox/notify/LookAndFeel.kt
@@ -13,500 +13,387 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package dorkbox.notify;
+package dorkbox.notify
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.Window;
-import java.awt.event.MouseAdapter;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Random;
+import dorkbox.swingActiveRender.ActionHandlerLong
+import dorkbox.swingActiveRender.SwingActiveRender
+import dorkbox.tweenEngine.Tween
+import dorkbox.tweenEngine.TweenCallback.Events.COMPLETE
+import dorkbox.tweenEngine.TweenEngine.Companion.create
+import dorkbox.tweenEngine.TweenEquations
+import dorkbox.util.ScreenUtil
+import java.awt.Point
+import java.awt.Rectangle
+import java.awt.Window
+import java.awt.event.MouseAdapter
+import java.util.*
-import dorkbox.swingActiveRender.ActionHandlerLong;
-import dorkbox.swingActiveRender.SwingActiveRender;
-import dorkbox.tweenEngine.BaseTween;
-import dorkbox.tweenEngine.Tween;
-import dorkbox.tweenEngine.TweenCallback;
-import dorkbox.tweenEngine.TweenEngine;
-import dorkbox.tweenEngine.TweenEquations;
-import dorkbox.util.ScreenUtil;
+internal class LookAndFeel(
+ private val notify: INotify,
+ private val parent: Window,
+ private val notifyCanvas: NotifyCanvas,
+ private val notification: Notify,
+ parentBounds: Rectangle,
+ private val isDesktopNotification: Boolean
+) {
+ companion object {
+ private val popups: MutableMap = HashMap()
-@SuppressWarnings({"FieldCanBeLocal"})
-class LookAndFeel {
- private static final Map popups = new HashMap();
+ // access is only from a single thread ever, so unsafe is preferred.
+ val animation = create().unsafe().build()
- static final TweenEngine animation = TweenEngine.Companion.create()
- .unsafe() // access is only from a single thread ever, so unsafe is preferred.
- .build();
+ val accessor = NotifyAccessor()
- static final NotifyAccessor accessor = new NotifyAccessor();
- private static final ActionHandlerLong frameStartHandler;
-
-
- static {
// this is for updating the tween engine during active-rendering
- frameStartHandler = new ActionHandlerLong() {
- @Override
- public
- void handle(final long deltaInNanos) {
- LookAndFeel.animation.update(deltaInNanos);
+ private val frameStartHandler = ActionHandlerLong { deltaInNanos -> animation.update(deltaInNanos) }
+
+ const val SPACER = 10
+ const val MARGIN = 20
+
+ private val windowListener: java.awt.event.WindowAdapter = WindowAdapter()
+ private val mouseListener: MouseAdapter = ClickAdapter()
+ private val RANDOM = Random()
+ private val MOVE_DURATION = Notify.MOVE_DURATION
+
+ private fun getAnchorX(position: Pos, bounds: Rectangle, isDesktop: Boolean): Int {
+ // we use the screen that the mouse is currently on.
+ val startX = if (isDesktop) {
+ bounds.getX().toInt()
+ } else {
+ 0
}
- };
+
+ val screenWidth = bounds.getWidth().toInt()
+ return when (position) {
+ Pos.TOP_LEFT, Pos.BOTTOM_LEFT -> MARGIN + startX
+ Pos.CENTER -> startX + screenWidth / 2 - NotifyCanvas.WIDTH / 2 - MARGIN / 2
+ Pos.TOP_RIGHT, Pos.BOTTOM_RIGHT -> startX + screenWidth - NotifyCanvas.WIDTH - MARGIN
+ }
+ }
+
+ private fun getAnchorY(position: Pos, bounds: Rectangle, isDesktop: Boolean): Int {
+ val startY = if (isDesktop) {
+ bounds.getY().toInt()
+ } else {
+ 0
+ }
+
+ val screenHeight = bounds.getHeight().toInt()
+ 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
+ private fun addPopupToMap(sourceLook: LookAndFeel) {
+ synchronized(popups) {
+ val id = sourceLook.idAndPosition
+ var looks = popups[id]
+ if (looks == null) {
+ looks = PopupList()
+ popups[id] = looks
+ }
+
+ val index = looks.size()
+ sourceLook.popupIndex = index
+
+ // the popups are ALL the same size!
+ // popups at TOP grow down, popups at BOTTOM grow up
+ val anchorX = sourceLook.anchorX
+ val anchorY = sourceLook.anchorY
+
+ val targetY = if (index == 0) {
+ anchorY
+ } else {
+ val growDown = growDown(sourceLook)
+ if (sourceLook.isDesktopNotification && index == 1) {
+ // 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
+ looks.calculateOffset(growDown, anchorX, anchorY)
+ }
+ if (growDown) {
+ anchorY + index * (NotifyCanvas.HEIGHT + SPACER) + looks.offsetY
+ } else {
+ anchorY - index * (NotifyCanvas.HEIGHT + SPACER) + looks.offsetY
+ }
+ }
+
+ looks.add(sourceLook)
+ sourceLook.setLocation(anchorX, targetY)
+
+ if (sourceLook.hideAfterDurationInSeconds > 0 && sourceLook.hideTween == null) {
+ // begin a timeline to get rid of the popup (default is 5 seconds)
+ animation.to(sourceLook, NotifyAccessor.PROGRESS, accessor, sourceLook.hideAfterDurationInSeconds)
+ .target(NotifyCanvas.WIDTH.toFloat())
+ .ease(TweenEquations.Linear)
+ .addCallback(COMPLETE) { sourceLook.notify.close() }
+ .start()
+ }
+ }
+ }
+
+ // only called on the swing app or SwingActiveRender thread
+ private fun removePopupFromMap(sourceLook: LookAndFeel): Boolean {
+ val growDown = growDown(sourceLook)
+ var popupsAreEmpty: Boolean
+
+ synchronized(popups) {
+ popupsAreEmpty = popups.isEmpty()
+ val allLooks = popups[sourceLook.idAndPosition]
+
+ // there are two loops because it is necessary to cancel + remove all tweens BEFORE adding new ones.
+ var adjustPopupPosition = false
+ val iterator = allLooks!!.iterator()
+ while (iterator.hasNext()) {
+ val look = iterator.next()
+ if (look.tween != null) {
+ look.tween!!.cancel() // cancel does its thing on the next tick of animation cycle
+ look.tween = null
+ }
+
+ if (look === sourceLook) {
+ if (look.hideTween != null) {
+ look.hideTween!!.cancel()
+ look.hideTween = null
+ }
+ adjustPopupPosition = true
+ iterator.remove()
+ }
+
+ if (adjustPopupPosition) {
+ look.popupIndex--
+ }
+ }
+
+ // have to adjust for offsets when the window-manager has a toolbar that consumes space and prevents overlap.
+ val offsetY = allLooks.offsetY
+ for (index in 0 until allLooks.size()) {
+ val look = allLooks[index]
+
+ // the popups are ALL the same size!
+ // popups at TOP grow down, popups at BOTTOM grow up
+ val changedY = if (growDown) {
+ look.anchorY + (look.popupIndex * (NotifyCanvas.HEIGHT + SPACER) + offsetY)
+ } else {
+ look.anchorY - (look.popupIndex * (NotifyCanvas.HEIGHT + SPACER) + offsetY)
+ }
+
+ // now animate that popup to its new location
+ look.tween = animation
+ .to(look, NotifyAccessor.Y_POS, accessor, MOVE_DURATION)
+ .target(changedY.toFloat())
+ .ease(TweenEquations.Linear)
+ .addCallback(COMPLETE) {
+ // make sure to remove the tween once it's done, otherwise .kill can do weird things.
+ look.tween = null
+ }
+ .start()
+ }
+ }
+ return popupsAreEmpty
+ }
+
+ private fun growDown(look: LookAndFeel): Boolean {
+ return when (look.position) {
+ Pos.TOP_LEFT, Pos.TOP_RIGHT, Pos.CENTER -> true
+ else -> false
+ }
+ }
}
- static final int SPACER = 10;
- static final int MARGIN = 20;
-
- private static final java.awt.event.WindowAdapter windowListener = new WindowAdapter();
- private static final MouseAdapter mouseListener = new ClickAdapter();
-
- private static final Random RANDOM = new Random();
-
- private static final float MOVE_DURATION = Notify.MOVE_DURATION;
- private final boolean isDesktopNotification;
+ @Volatile
+ private var anchorX: Int
- 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;
+ @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 final String idAndPosition;
- private int popupIndex;
+ private var idAndPosition: String? = null
+ private var popupIndex = 0
- private volatile Tween tween = null;
- private volatile Tween hideTween = null;
-
- private final ActionHandler 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;
+ @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);
- }
- notifyCanvas.addMouseListener(mouseListener);
-
- hideAfterDurationInSeconds = notification.hideAfterDurationInMillis / 1000.0F;
- position = notification.position;
-
- if (notification.onGeneralAreaClickAction != null) {
- onGeneralAreaClickAction = new ActionHandler() {
- @Override
- public
- void handle(final Notify value) {
- notification.onGeneralAreaClickAction.handle(notification);
- }
- };
- }
- else {
- onGeneralAreaClickAction = null;
+ parent.addWindowListener(windowListener)
}
- if (isDesktopNotification) {
- Point point = new Point((int) parentBounds.getX(), ((int) parentBounds.getY()));
- idAndPosition = ScreenUtil.getMonitorNumberAtLocation(point) + ":" + position;
+ notifyCanvas.addMouseListener(mouseListener)
+ hideAfterDurationInSeconds = notification.hideAfterDurationInMillis / 1000.0f
+ position = notification.position
+
+ idAndPosition = if (isDesktopNotification) {
+ val point = Point(parentBounds.getX().toInt(), parentBounds.getY().toInt())
+ ScreenUtil.getMonitorNumberAtLocation(point).toString() + ":" + position
} else {
- idAndPosition = parent.getName() + ":" + position;
+ parent.name + ":" + position
}
-
- anchorX = getAnchorX(position, parentBounds, isDesktopNotification);
- anchorY = getAnchorY(position, parentBounds, isDesktopNotification);
+ anchorX = getAnchorX(position, parentBounds, isDesktopNotification)
+ anchorY = getAnchorY(position, parentBounds, isDesktopNotification)
}
- void onClick(final int x, final int y) {
+ 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!
- if (onGeneralAreaClickAction != null) {
- onGeneralAreaClickAction.handle(null);
- }
+ onGeneralAreaClickAction.invoke(notification)
}
// we always close the notification popup
- notify.close();
+ notify.close()
}
// only called from an application
- void reLayout(final Rectangle bounds) {
+ 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);
+ anchorX = getAnchorX(position, bounds, isDesktopNotification)
+ anchorY = getAnchorY(position, bounds, isDesktopNotification)
- boolean growDown = growDown(this);
+ val growDown = growDown(this)
if (tween != null) {
- tween.cancel(); // cancel does its thing on the next tick of animation cycle
- tween = null;
+ tween!!.cancel() // cancel does its thing on the next tick of animation cycle
+ tween = null
}
- int changedY;
+
+ var changedY: Int
if (popupIndex == 0) {
- changedY = anchorY;
- }
- else {
- synchronized (popups) {
- String id = idAndPosition;
-
- PopupList looks = popups.get(id);
- if (looks != null) {
+ changedY = anchorY
+ } else {
+ synchronized(popups) {
+ val id = idAndPosition
+ val looks = popups[id]
+ changedY = if (looks != null) {
if (growDown) {
- changedY = anchorY + (popupIndex * (NotifyCanvas.HEIGHT + SPACER));
+ anchorY + popupIndex * (NotifyCanvas.HEIGHT + SPACER)
+ } else {
+ anchorY - popupIndex * (NotifyCanvas.HEIGHT + SPACER)
}
- else {
- changedY = anchorY - (popupIndex * (NotifyCanvas.HEIGHT + SPACER));
- }
- }
- else {
- changedY = anchorY;
+ } else {
+ anchorY
}
}
}
- setLocation(anchorX, changedY);
+ setLocation(anchorX, changedY)
}
- void close() {
+ fun close() {
if (hideTween != null) {
- hideTween.cancel();
- hideTween = null;
+ hideTween!!.cancel()
+ hideTween = null
}
if (tween != null) {
- tween.cancel();
- tween = null;
+ tween!!.cancel()
+ tween = null
}
if (isDesktopNotification) {
- parent.removeWindowListener(windowListener);
+ parent.removeWindowListener(windowListener)
}
- parent.removeMouseListener(mouseListener);
- updatePositionsPre(false);
- updatePositionsPost(false);
+ 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;
+ 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 >> 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.
- final int startX;
- if (isDesktop) {
- startX = (int) bounds.getX();
+ i1 -= amplitude shr 2
} else {
- startX = 0;
+ i1 += amplitude shr 2
+ }
+ if (i2 < 0) {
+ i2 -= amplitude shr 2
+ } else {
+ i2 += amplitude shr 2
}
- final int screenWidth = (int) bounds.getWidth();
-
- // determine location for the popup
- // get anchorX
- switch (position) {
- 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 + "'");
+ 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()
}
- private static
- int getAnchorY(final Pos position, final Rectangle bounds, final boolean isDesktop) {
- final int startY;
- if (isDesktop) {
- startY = (int) bounds.getY();
+ var y: Int
+ get() = if (isDesktopNotification) {
+ parent.y
+ } else {
+ notifyCanvas.y
}
- 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 {
- return startY + screenHeight - NotifyCanvas.HEIGHT - MARGIN - SPACER * 2;
- }
-
- default:
- throw new RuntimeException("Unknown position. '" + position + "'");
- }
- }
-
- // only called on the swing EDT thread
- private static
- void addPopupToMap(final LookAndFeel sourceLook) {
- synchronized (popups) {
- String id = sourceLook.idAndPosition;
-
- PopupList looks = popups.get(id);
- if (looks == null) {
- looks = new PopupList();
- popups.put(id, looks);
- }
- final int index = looks.size();
- sourceLook.popupIndex = index;
-
- // the popups are ALL the same size!
- // popups at TOP grow down, popups at BOTTOM grow up
- int targetY;
- int anchorX = sourceLook.anchorX;
- int anchorY = sourceLook.anchorY;
-
- if (index == 0) {
- targetY = anchorY;
+ set(y) {
+ if (isDesktopNotification) {
+ parent.setLocation(parent.x, y)
} else {
- boolean growDown = growDown(sourceLook);
-
- if (sourceLook.isDesktopNotification && index == 1) {
- // 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
- looks.calculateOffset(growDown, anchorX, anchorY);
- }
-
- if (growDown) {
- targetY = anchorY + (index * (NotifyCanvas.HEIGHT + SPACER)) + looks.getOffsetY();
- }
- else {
- targetY = anchorY - (index * (NotifyCanvas.HEIGHT + SPACER)) + looks.getOffsetY();
- }
-
- }
-
- looks.add(sourceLook);
- sourceLook.setLocation(anchorX, targetY);
-
- if (sourceLook.hideAfterDurationInSeconds > 0 && sourceLook.hideTween == null) {
- // begin a timeline to get rid of the popup (default is 5 seconds)
- animation.to(sourceLook, NotifyAccessor.PROGRESS, accessor, sourceLook.hideAfterDurationInSeconds)
- .target(NotifyCanvas.WIDTH)
- .ease(TweenEquations.Linear)
- .addCallback(new TweenCallback() {
- @Override
- public void onEvent(int type, BaseTween source) {
- if (type == Events.COMPLETE) {
- sourceLook.notify.close();
- }
- }
- })
- .start();
- }
- }
- }
-
- // only called on the swing app or SwingActiveRender thread
- private static
- boolean removePopupFromMap(final LookAndFeel sourceLook) {
- boolean growDown = growDown(sourceLook);
- boolean popupsAreEmpty;
-
- synchronized (popups) {
- popupsAreEmpty = popups.isEmpty();
- final PopupList allLooks = popups.get(sourceLook.idAndPosition);
-
- // there are two loops because it is necessary to cancel + remove all tweens BEFORE adding new ones.
- boolean adjustPopupPosition = false;
- for (Iterator iterator = allLooks.iterator(); iterator.hasNext(); ) {
- final LookAndFeel look = iterator.next();
-
- if (look.tween != null) {
- look.tween.cancel(); // cancel does its thing on the next tick of animation cycle
- look.tween = null;
- }
-
- if (look == sourceLook) {
- if (look.hideTween != null) {
- look.hideTween.cancel();
- look.hideTween = null;
- }
-
- adjustPopupPosition = true;
- iterator.remove();
- }
-
- if (adjustPopupPosition) {
- look.popupIndex--;
- }
- }
-
- // have to adjust for offsets when the window-manager has a toolbar that consumes space and prevents overlap.
- int offsetY = allLooks.getOffsetY();
-
- for (int index = 0; index < allLooks.size(); index++) {
- final LookAndFeel look = allLooks.get(index);
- // the popups are ALL the same size!
- // popups at TOP grow down, popups at BOTTOM grow up
- int changedY;
-
- if (growDown) {
- changedY = 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
- look.tween = animation.to(look, NotifyAccessor.Y_POS, accessor, MOVE_DURATION)
- .target((float) changedY)
- .ease(TweenEquations.Linear)
- .addCallback(new TweenCallback() {
- @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.
- look.tween = null;
- }
- }
- })
- .start();
+ notifyCanvas.setLocation(notifyCanvas.x, y)
}
}
- return popupsAreEmpty;
- }
+ val x: Int
+ get() = if (isDesktopNotification) {
+ parent.x
+ } else {
+ notifyCanvas.x
+ }
- private static
- boolean growDown(final LookAndFeel look) {
- switch (look.position) {
- case TOP_LEFT:
- case TOP_RIGHT:
- case CENTER: // center grows down
- return true;
- default:
- return false;
+ fun setLocation(x: Int, y: Int) {
+ if (isDesktopNotification) {
+ parent.setLocation(x, y)
+ } else {
+ notifyCanvas.setLocation(x, y)
}
}
- void setProgress(final int progress) {
- notifyCanvas.setProgress(progress);
- }
-
- int getProgress() {
- return notifyCanvas.getProgress();
- }
+ var progress: Int
+ get() = notifyCanvas.progress
+ set(progress) {
+ notifyCanvas.progress = progress
+ }
/**
* we have to remove the active renderer BEFORE we set the visibility status.
*/
- void updatePositionsPre(final boolean visible) {
+ fun updatePositionsPre(visible: Boolean) {
if (!visible) {
- boolean popupsAreEmpty = LookAndFeel.removePopupFromMap(this);
- SwingActiveRender.removeActiveRender(notifyCanvas);
-
+ val popupsAreEmpty = removePopupFromMap(this)
+ SwingActiveRender.removeActiveRender(notifyCanvas)
if (popupsAreEmpty) {
// 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
*/
- void updatePositionsPost(final boolean visible) {
+ fun updatePositionsPost(visible: Boolean) {
if (visible) {
- SwingActiveRender.addActiveRender(notifyCanvas);
+ SwingActiveRender.addActiveRender(notifyCanvas)
// start if we have previously stopped the timer
if (!SwingActiveRender.containsActiveRenderFrameStart(frameStartHandler)) {
- LookAndFeel.animation.resetUpdateTime();
- SwingActiveRender.addActiveRenderFrameStart(frameStartHandler);
+ animation.resetUpdateTime()
+ SwingActiveRender.addActiveRenderFrameStart(frameStartHandler)
}
-
- LookAndFeel.addPopupToMap(this);
+ addPopupToMap(this)
}
}
}
diff --git a/src/dorkbox/notify/Notify.kt b/src/dorkbox/notify/Notify.kt
index 0fd8c9d..7cabaa9 100755
--- a/src/dorkbox/notify/Notify.kt
+++ b/src/dorkbox/notify/Notify.kt
@@ -13,385 +13,330 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package dorkbox.notify;
+package dorkbox.notify
-import java.awt.Image;
-import java.awt.image.BufferedImage;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.SoftReference;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.imageio.ImageIO;
-import javax.swing.ImageIcon;
-import javax.swing.JFrame;
-
-import dorkbox.propertyLoader.Property;
-import dorkbox.util.ImageUtil;
-import dorkbox.util.LocationResolver;
-import dorkbox.util.SwingUtil;
+import dorkbox.propertyLoader.Property
+import dorkbox.util.ImageUtil
+import dorkbox.util.LocationResolver
+import dorkbox.util.SwingUtil
+import java.awt.Image
+import java.awt.image.BufferedImage
+import java.io.IOException
+import java.io.InputStream
+import java.lang.ref.SoftReference
+import javax.imageio.ImageIO
+import javax.swing.ImageIcon
+import javax.swing.JFrame
/**
* Popup notification messages, similar to the popular "Growl" notification system on macosx, that display in the corner of the monitor.
- *
+ *
* 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.
- *
+ *
* These notifications are for a single screen only, and cannot be anchored to an application.
*
*
- * {@code
- * Notify.create()
- * .title("Title Text")
- * .text("Hello World!")
- * .useDarkStyle()
- * .showWarning();
- * }
- *
+ * `Notify.create()
+ * .title("Title Text")
+ * .text("Hello World!")
+ * .useDarkStyle()
+ * .showWarning();
+` *
+ *
*/
-@SuppressWarnings({"WeakerAccess", "unused", "UnusedReturnValue"})
-public final
-class Notify {
+@Suppress("unused")
+class Notify private constructor() {
+ companion object {
+ const val DIALOG_CONFIRM = "dialog-confirm.png"
+ const val DIALOG_INFORMATION = "dialog-information.png"
+ const val DIALOG_WARNING = "dialog-warning.png"
+ const val DIALOG_ERROR = "dialog-error.png"
- public static final String DIALOG_CONFIRM = "dialog-confirm.png";
+ /**
+ * This is the title font used by a notification.
+ */
+ @Property
+ var TITLE_TEXT_FONT = "Source Code Pro BOLD 16"
- public static final String DIALOG_INFORMATION = "dialog-information.png";
- public static final String DIALOG_WARNING = "dialog-warning.png";
- public static final String DIALOG_ERROR = "dialog-error.png";
+ /**
+ * This is the main text font used by a notification.
+ */
+ @Property
+ var MAIN_TEXT_FONT = "Source Code Pro BOLD 12"
- /**
- * This is the title font used by a notification.
- */
- @Property
- public static String TITLE_TEXT_FONT = "Source Code Pro BOLD 16";
+ /**
+ * How long we want it to take for the popups to relocate when one is closed
+ */
+ @Property
+ var MOVE_DURATION = 1.0f
- /**
- * This is the main text font used by a notification.
- */
- @Property
- public static String MAIN_TEXT_FONT = "Source Code Pro BOLD 12";
+ /**
+ * Location of the dialog image resources. By default they must be in the 'resources' directory relative to the application
+ */
+ @Property
+ var IMAGE_PATH = "resources"
+ private val imageCache = mutableMapOf>()
- /**
- * How long we want it to take for the popups to relocate when one is closed
- */
- @Property
- public static float MOVE_DURATION = 1.0F;
+ /**
+ * Gets the version number.
+ */
+ const val version = "3.7"
- /**
- * Location of the dialog image resources. By default they must be in the 'resources' directory relative to the application
- */
- @Property
- public static String IMAGE_PATH = "resources";
-
- private static Map> imageCache = new HashMap>(4);
-
- /**
- * Gets the version number.
- */
- public static
- String getVersion() {
- return "3.7";
- }
-
- /**
- * Builder pattern to create the notification.
- */
- public static
- Notify create() {
- return new Notify();
- }
-
- /**
- * Gets the size of the image to be used in the notification, which is a 48x48 pixel image.
- */
- public static
- int getImageSize() {
- return 48;
- }
-
- /**
- * Permits one to override the default images for the dialogs. This is NOT thread safe, and must be performed BEFORE showing a
- * notification.
- *
- * The image names are as follows:
- *
- * '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 image the BufferedImage that you want to cache.
- */
- public static
- void overrideDefaultImage(String imageName, BufferedImage image) {
- 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.");
+ /**
+ * Builder pattern to create the notification.
+ */
+ fun create(): Notify {
+ return Notify()
}
- ImageUtil.waitForImageLoad(image);
+ /**
+ * Gets the size of the image to be used in the notification, which is a 48x48 pixel image.
+ */
+ val imageSize: Int
+ get() = 48
- // we only use 48x48 pixel images. Resize as necessary
- int width = image.getWidth(null);
- int height = image.getHeight(null);
-
- BufferedImage bufferedImage;
-
- // resize the image, keep aspect ratio
- if (width > height) {
- bufferedImage = ImageUtil.resizeImage(image, getImageSize(), -1);
- }
- else {
- bufferedImage = ImageUtil.resizeImage(image, -1, getImageSize());
- }
-
- imageCache.put(imageName, new SoftReference(new ImageIcon(bufferedImage)));
- }
-
- private static
- ImageIcon getImage(String imageName) {
- ImageIcon image = null;
- InputStream resourceAsStream = null;
-
- try {
- SoftReference reference = imageCache.get(imageName);
-
- if (reference != null) {
- image = reference.get();
+ /**
+ * Permits one to override the default images for the dialogs. This is NOT thread safe, and must be performed BEFORE showing a
+ * notification.
+ *
+ *
+ * The image names are as follows:
+ *
+ *
+ * '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 it cached, or one of the above.
+ * @param image the BufferedImage that you want to cache.
+ */
+ fun overrideDefaultImage(imageName: String, image: BufferedImage) {
+ if (imageCache.containsKey(imageName)) {
+ throw RuntimeException("Unable to set an image that already has been set. This action must be done as soon as possible.")
}
- if (image == null) {
- // String name = IMAGE_PATH + File.separatorChar + imageName;
- String name = imageName;
+ ImageUtil.waitForImageLoad(image)
- resourceAsStream = LocationResolver.getResourceAsStream(name);
+ // we only use 48x48 pixel images. Resize as necessary
+ val width = image.getWidth(null)
+ val height = image.getHeight(null)
- image = new ImageIcon(ImageIO.read(resourceAsStream));
- imageCache.put(imageName, new SoftReference(image));
+ // resize the image, keep aspect ratio
+ val bufferedImage = if (width > height) {
+ ImageUtil.resizeImage(image, imageSize, -1)
+ } else {
+ ImageUtil.resizeImage(image, -1, imageSize)
}
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (resourceAsStream != null) {
- try {
- resourceAsStream.close();
- } catch (IOException e) {
- e.printStackTrace();
+
+ imageCache[imageName] = SoftReference(ImageIcon(bufferedImage))
+ }
+
+ private fun getImage(imageName: String): ImageIcon? {
+ var resourceAsStream: InputStream? = null
+
+ var image = imageCache[imageName]?.get()
+
+ try {
+ if (image == null) {
+ // String name = IMAGE_PATH + File.separatorChar + imageName;
+ resourceAsStream = LocationResolver.getResourceAsStream(imageName)
+ image = ImageIcon(ImageIO.read(resourceAsStream))
+ imageCache[imageName] = SoftReference(image)
}
+ } catch (e: IOException) {
+ e.printStackTrace()
+ } finally {
+ resourceAsStream?.close()
}
+
+ return image
}
-
- return image;
}
- String title;
- String text;
+ internal var title = "Notification"
+ 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;
- int hideAfterDurationInMillis = 0;
-
- boolean hideCloseButton;
- boolean isDark = false;
- int screenNumber = Short.MIN_VALUE;
- private ImageIcon icon;
-
- ActionHandler onGeneralAreaClickAction;
- private INotify notifyPopup;
-
- private String name;
- private int shakeDurationInMillis = 0;
- private int shakeAmplitude = 0;
- private JFrame appWindow;
-
- private
- Notify() {
- }
+ private var notifyPopup: INotify? = null
+ private var name: String? = null
+ private var shakeDurationInMillis = 0
+ private var shakeAmplitude = 0
+ private var appWindow: JFrame? = null
/**
* Specifies the main text
*/
- public
- Notify text(String text) {
- this.text = text;
- return this;
+ fun text(text: String): Notify {
+ this.text = text
+ return this
}
/**
* Specifies the title
*/
- public
- Notify title(String title) {
- this.title = title;
- return this;
+ fun title(title: String): Notify {
+ this.title = title
+ return this
}
/**
* Specifies the image
*/
- public
- Notify image(Image image) {
+ fun image(image: Image): Notify {
// we only use 48x48 pixel images. Resize as necessary
- int width = image.getWidth(null);
- int height = image.getHeight(null);
-
- BufferedImage bufferedImage = ImageUtil.getBufferedImage(image);
+ val width = image.getWidth(null)
+ val height = image.getHeight(null)
+ var bufferedImage = ImageUtil.getBufferedImage(image)
// resize the image, keep aspect ratio
- if (width > height) {
- bufferedImage = ImageUtil.resizeImage(bufferedImage, 48, -1);
+ bufferedImage = if (width > height) {
+ ImageUtil.resizeImage(bufferedImage, 48, -1)
} else {
- bufferedImage = ImageUtil.resizeImage(bufferedImage, -1, 48);
+ ImageUtil.resizeImage(bufferedImage, -1, 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
- bufferedImage = ImageUtil.getSquareBufferedImage(bufferedImage);
-
- this.icon = new ImageIcon(bufferedImage);
- return this;
+ bufferedImage = ImageUtil.getSquareBufferedImage(bufferedImage)
+ icon = ImageIcon(bufferedImage)
+ 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
- Notify position(Pos position) {
- this.position = position;
- return this;
+ fun position(position: Pos): Notify {
+ this.position = position
+ return this
}
/**
* 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
*/
- public
- Notify hideAfter(int durationInMillis) {
- if (durationInMillis < 0) {
- durationInMillis = 0;
+ fun hideAfter(durationInMillis: Int): Notify {
+ hideAfterDurationInMillis = if (durationInMillis < 0) {
+ 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
* notification is clicked on). This does not apply when clicking on the "close" button
*/
- public
- Notify onAction(ActionHandler onAction) {
- this.onGeneralAreaClickAction = onAction;
- return this;
+ fun onAction(onAction: Notify.()->Unit): Notify {
+ onGeneralAreaClickAction = onAction
+ return this
}
/**
* Specifies that the notification should use the built-in dark styling, rather than the default, light-gray notification style.
*/
- public
- Notify darkStyle() {
- isDark = true;
- return this;
+ fun darkStyle(): Notify {
+ isDark = true
+ return this
}
/**
* Specifies what the theme should be, if other than the default. This will always take precedence over the defaults.
*/
- public
- Notify text(Theme theme) {
- this.theme = theme;
- return this;
+ fun text(theme: Theme?): Notify {
+ this.theme = theme
+ return this
}
/**
* Specify that the close button in the top-right corner of the notification should not be shown.
*/
- public
- Notify hideCloseButton() {
- this.hideCloseButton = true;
- return this;
+ fun hideCloseButton(): Notify {
+ hideCloseButton = true
+ return this
}
/**
* Shows the notification with the built-in 'warning' image.
*/
- public
- void showWarning() {
- name = DIALOG_WARNING;
- icon = getImage(name);
- show();
+ fun showWarning() {
+ name = DIALOG_WARNING
+ icon = getImage(DIALOG_WARNING)
+ show()
}
/**
* Shows the notification with the built-in 'information' image.
*/
- public
- void showInformation() {
- name = DIALOG_INFORMATION;
- icon = getImage(name);
- show();
+ fun showInformation() {
+ name = DIALOG_INFORMATION
+ icon = getImage(DIALOG_INFORMATION)
+ show()
}
/**
* Shows the notification with the built-in 'error' image.
*/
- public
- void showError() {
- name = DIALOG_ERROR;
- icon = getImage(name);
- show();
+ fun showError() {
+ name = DIALOG_ERROR
+ icon = getImage(DIALOG_ERROR)
+ show()
}
/**
* Shows the notification with the built-in 'confirm' image.
*/
- public
- void showConfirm() {
- name = DIALOG_CONFIRM;
- icon = getImage(name);
- show();
+ fun showConfirm() {
+ name = DIALOG_CONFIRM
+ icon = getImage(DIALOG_CONFIRM)
+ show()
}
/**
* Shows the notification. If the Notification is assigned to a screen, but shown inside a Swing/etc parent, the screen number will be
* ignored.
*/
- public
- void show() {
+ fun show() {
// must be done in the swing EDT
- //noinspection Convert2Lambda
- SwingUtil.invokeAndWaitQuietly(new Runnable() {
- @Override
- public
- void run() {
- final Notify notify = Notify.this;
- final ImageIcon image = notify.icon;
-
- Theme theme;
- if (notify.theme != null) {
- // use custom theme.
- theme = notify.theme;
- } else {
- theme = new Theme(Notify.TITLE_TEXT_FONT, Notify.MAIN_TEXT_FONT, notify.isDark);
- }
-
- if (appWindow == null) {
- notifyPopup = new AsDesktop(notify, image, theme);
- } else {
- notifyPopup = new AsApplication(notify, image, appWindow, theme);
- }
-
- notifyPopup.setVisible(true);
-
- if (shakeDurationInMillis > 0) {
- notifyPopup.shake(notify.shakeDurationInMillis, notify.shakeAmplitude);
- }
+ SwingUtil.invokeAndWaitQuietly {
+ val notify = this@Notify
+ val image = notify.icon
+ val theme = if (notify.theme != null) {
+ // use custom provided theme
+ notify.theme!!
+ } else {
+ 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)
+ }
+
+ notifyPopup.setVisible(true)
+
+ if (shakeDurationInMillis > 0) {
+ notifyPopup.shake(notify.shakeDurationInMillis, notify.shakeAmplitude)
+ }
+
+ notify.notifyPopup = notifyPopup
+ }
// 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 amplitude a measure of how much it needs to shake. 4 is a small amount of shaking, 10 is a lot.
*/
- public
- Notify shake(final int durationInMillis, final int amplitude) {
- this.shakeDurationInMillis = durationInMillis;
- this.shakeAmplitude = amplitude;
-
+ fun shake(durationInMillis: Int, amplitude: Int): Notify {
+ shakeDurationInMillis = durationInMillis
+ shakeAmplitude = amplitude
if (notifyPopup != null) {
// must be done in the swing EDT
- //noinspection Convert2Lambda
- SwingUtil.invokeLater(new Runnable() {
- @Override
- public
- void run() {
- notifyPopup.shake(durationInMillis, amplitude);
- }
- });
+ SwingUtil.invokeLater { notifyPopup!!.shake(durationInMillis, amplitude) }
}
-
- return this;
+ return this
}
/**
* Closes the notification. Particularly useful if it's an "infinite" duration notification.
*/
- public
- void close() {
+ fun close() {
if (notifyPopup == null) {
- throw new NullPointerException("NotifyPopup");
+ throw NullPointerException("NotifyPopup")
}
// must be done in the swing EDT
- //noinspection Convert2Lambda
- SwingUtil.invokeLater(new Runnable() {
- @Override
- public
- void run() {
- notifyPopup.close();
- }
- });
+ SwingUtil.invokeLater { 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.
*/
- public
- Notify setScreen(final int screenNumber) {
- this.screenNumber = screenNumber;
- return this;
+ fun setScreen(screenNumber: Int): Notify {
+ this.screenNumber = screenNumber
+ return this
}
/**
* Attaches this notification to a specific JFrame, instead of having a global notification
*/
- public
- Notify attach(final JFrame frame) {
- this.appWindow = frame;
- return this;
+ fun attach(frame: JFrame?): Notify {
+ appWindow = frame
+ return this
}
-
// called when this notification is closed.
- void onClose() {
- notifyPopup = null;
+ fun onClose() {
+ notifyPopup = null
}
-}
+
+}
diff --git a/src/dorkbox/notify/NotifyAccessor.kt b/src/dorkbox/notify/NotifyAccessor.kt
index e8c96be..58aa675 100755
--- a/src/dorkbox/notify/NotifyAccessor.kt
+++ b/src/dorkbox/notify/NotifyAccessor.kt
@@ -13,52 +13,54 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package dorkbox.notify;
+package dorkbox.notify
-import dorkbox.tweenEngine.TweenAccessor;
+import dorkbox.tweenEngine.TweenAccessor
-class NotifyAccessor implements TweenAccessor {
-
- static final int Y_POS = 1;
- static final int X_Y_POS = 2;
- static final int PROGRESS = 3;
-
-
- NotifyAccessor() {
+internal class NotifyAccessor : TweenAccessor {
+ companion object {
+ const val Y_POS = 1
+ const val X_Y_POS = 2
+ const val PROGRESS = 3
}
- @Override
- public
- int getValues(final LookAndFeel target, final int tweenType, final float[] returnValues) {
- switch (tweenType) {
- case Y_POS:
- 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;
+ override fun getValues(target: LookAndFeel, tweenType: Int, returnValues: FloatArray): Int {
+ when (tweenType) {
+ Y_POS -> {
+ returnValues[0] = target.y.toFloat()
+ return 1
+ }
+
+ X_Y_POS -> {
+ returnValues[0] = target.x.toFloat()
+ returnValues[1] = target.y.toFloat()
+ return 2
+ }
+
+ PROGRESS -> {
+ returnValues[0] = target.progress.toFloat()
+ return 1
+ }
}
- return 1;
+ return 1
}
- @SuppressWarnings({"NumericCastThatLosesPrecision", "UnnecessaryReturnStatement"})
- @Override
- public
- void setValues(final LookAndFeel target, final int tweenType, final float[] newValues) {
- switch (tweenType) {
- case Y_POS:
- target.setY((int) newValues[0]);
- return;
- case X_Y_POS:
- target.setLocation((int) newValues[0], (int) newValues[1]);
- return;
- case PROGRESS:
- 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
+ }
}
}
}
diff --git a/src/dorkbox/notify/NotifyCanvas.kt b/src/dorkbox/notify/NotifyCanvas.kt
index 566fbb3..21e33e9 100755
--- a/src/dorkbox/notify/NotifyCanvas.kt
+++ b/src/dorkbox/notify/NotifyCanvas.kt
@@ -13,88 +13,56 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package dorkbox.notify;
+package dorkbox.notify
-import java.awt.BasicStroke;
-import java.awt.Canvas;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Point;
-import java.awt.RenderingHints;
-import java.awt.Stroke;
-import java.awt.image.BufferedImage;
+import java.awt.BasicStroke
+import java.awt.Canvas
+import java.awt.Color
+import java.awt.Dimension
+import java.awt.Graphics
+import java.awt.Graphics2D
+import java.awt.RenderingHints
+import java.awt.Stroke
+import java.awt.image.BufferedImage
+import javax.swing.ImageIcon
+import javax.swing.JLabel
-import javax.swing.ImageIcon;
-import javax.swing.JLabel;
+internal class NotifyCanvas(
+ val parent: INotify,
+ private val notification: Notify,
+ private val imageIcon: ImageIcon?,
+ private val theme: Theme
+) : Canvas() {
-@SuppressWarnings("FieldCanBeLocal")
-class NotifyCanvas extends Canvas {
- 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;
+ private val showCloseButton: Boolean
+ private var cachedImage: BufferedImage
// for the progress bar. we directly draw this onscreen
// non-volatile because it's always accessed in the active render thread
- private int progress = 0;
+ var progress = 0
- private final Theme theme;
- final INotify parent;
+ init {
+ val preferredSize = Dimension(WIDTH, HEIGHT)
+ setPreferredSize(preferredSize)
+ maximumSize = preferredSize
+ minimumSize = preferredSize
+ setSize(WIDTH, HEIGHT)
-
- NotifyCanvas(final INotify parent, final Notify notification, final ImageIcon imageIcon, final Theme theme) {
- this.parent = parent;
- 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;
+ isFocusable = false
+ background = theme.panel_BG
+ showCloseButton = !notification.hideCloseButton
// 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) {
- this.progress = progress;
- }
-
- int getProgress() {
- return progress;
- }
-
- @Override
- public
- void paint(final Graphics g) {
+ override fun paint(g: Graphics) {
// 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
try {
- g.drawImage(cachedImage, 0, 0, null);
- } catch (Exception ignored) {
+ g.drawImage(cachedImage, 0, 0, null)
+ } catch (ignored: Exception) {
// 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)
// 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)
// 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 {
- g.drawImage(cachedImage, 0, 0, null);
- } catch (Exception ignored2) {
+ g.drawImage(cachedImage, 0, 0, null)
+ } catch (ignored2: Exception) {
}
}
// 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 {
if (showCloseButton) {
// 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);
- g3.setStroke(stroke);
+ val p = mousePosition
- final Point p = getMousePosition();
// reasonable position for detecting mouse over
if (p != null && p.getX() >= 280 && p.getY() <= 20) {
- g3.setColor(Color.RED);
- }
- else {
- g3.setColor(theme.closeX_FG);
+ g3.color = Color.RED
+ } else {
+ g3.color = theme.closeX_FG
}
// draw the X
- g3.drawLine(X_1, Y_1, X_2, Y_2);
- g3.drawLine(X_2, Y_1, X_1, Y_2);
+ g3.drawLine(X_1, Y_1, X_2, Y_2)
+ g3.drawLine(X_2, Y_1, X_1, Y_2)
}
// draw the progress bar along the bottom
- g2.setColor(theme.progress_FG);
- g2.fillRect(0, PROGRESS_HEIGHT, progress, 2);
+ g2.color = theme.progress_FG
+ g2.fillRect(0, PROGRESS_HEIGHT, progress, 2)
} 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').
*/
- boolean isCloseButton(final int x, final int y) {
- return showCloseButton && x >= 280 && y <= 20;
+ fun isCloseButton(x: Int, y: Int): Boolean {
+ return showCloseButton && x >= 280 && y <= 20
}
- private static
- BufferedImage renderBackgroundInfo(final String title,
- final String notificationText,
- final Theme theme,
- final ImageIcon imageIcon) {
+ companion object {
+ private val stroke: Stroke = BasicStroke(2f)
+
+ private const val closeX = 282
+ private const val closeY = 2
+
+ private const val Y_1 = closeY + 5
+ private const val X_1 = closeX + 5
+ private const val Y_2 = closeY + 11
+ private const val X_2 = closeX + 11
+
+ const val WIDTH = 300
+ const val HEIGHT = 87
+ private const val PROGRESS_HEIGHT = HEIGHT - 2
+ private fun renderBackgroundInfo(title: String, notificationText: String, theme: Theme, imageIcon: ImageIcon?): BufferedImage {
+
+ 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 {
+ g2.color = theme.panel_BG
+ g2.fillRect(0, 0, WIDTH, HEIGHT)
+
+ // Draw the title text
+ g2.color = theme.titleText_FG
+ g2.font = theme.titleTextFont
+ g2.drawString(title, 5, 20)
- BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
- Graphics2D 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);
+ var posX = 10
+ val posY = -8
+ var textLengthLimit = 108
- try {
- g2.setColor(theme.panel_BG);
- g2.fillRect(0, 0, WIDTH, HEIGHT);
+ // ICON
+ if (imageIcon != null) {
+ textLengthLimit = 88
+ posX = 60
+ // Draw the image
+ imageIcon.paintIcon(null, g2, 5, 30)
+ }
- // Draw the title text
- g2.setColor(theme.titleText_FG);
- g2.setFont(theme.titleTextFont);
- g2.drawString(title, 5, 20);
+ // Draw the main text
+ var length = notificationText.length
+ val text = StringBuilder(length)
+
+ // are we "html" already? just check for the starting tag and strip off END html tag
+ if (length >= 13 && notificationText.regionMatches(length - 7, "