From a218bfb8db149ea39228264a08677536b97ad4fe Mon Sep 17 00:00:00 2001 From: Robinson Date: Fri, 27 Jan 2023 02:15:05 +0100 Subject: [PATCH] Initial import to kotlin --- src/dorkbox/notify/ActionHandler.kt | 5 +- src/dorkbox/notify/AsApplication.kt | 197 +++----- src/dorkbox/notify/AsDesktop.kt | 180 +++---- src/dorkbox/notify/ClickAdapter.kt | 20 +- src/dorkbox/notify/INotify.kt | 14 +- src/dorkbox/notify/LookAndFeel.kt | 712 +++++++++++---------------- src/dorkbox/notify/Notify.kt | 525 +++++++++----------- src/dorkbox/notify/NotifyAccessor.kt | 80 +-- src/dorkbox/notify/NotifyCanvas.kt | 282 +++++------ src/dorkbox/notify/PopupList.kt | 58 +-- src/dorkbox/notify/Pos.kt | 7 +- src/dorkbox/notify/Theme.kt | 83 ++-- src/dorkbox/notify/WindowAdapter.kt | 35 +- src9/dorkbox/notify/EmptyClass.java | 24 + src9/module-info.java | 11 + src9/notify-dark.png | Bin 0 -> 6277 bytes test/NotifyTest.java | 227 --------- test/dorkbox/notify/NotifyTest.kt | 176 +++++++ 18 files changed, 1153 insertions(+), 1483 deletions(-) create mode 100755 src9/dorkbox/notify/EmptyClass.java create mode 100644 src9/module-info.java create mode 100755 src9/notify-dark.png delete mode 100755 test/NotifyTest.java create mode 100755 test/dorkbox/notify/NotifyTest.kt 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, "", 0, 7, ignoreCase = true)) { + text.append(notificationText) + text.delete(text.length - 7, text.length) + length -= 7 + } else { + text.append("") + text.append(notificationText) + } + + // make sure the text is the correct length + if (length > textLengthLimit) { + text.delete(6 + textLengthLimit, text.length) + text.append("...") + } + text.append("") - int posX = 10; - int posY = -8; - int textLengthLimit = 108; + val mainTextLabel = JLabel() + mainTextLabel.foreground = theme.mainText_FG + mainTextLabel.font = theme.mainTextFont + mainTextLabel.text = text.toString() + mainTextLabel.setBounds(0, 0, WIDTH - posX - 2, HEIGHT) - // ICON - if (imageIcon != null) { - textLengthLimit = 88; - posX = 60; - // Draw the image - imageIcon.paintIcon(null, g2, 5, 30); + g2.translate(posX, posY) + mainTextLabel.paint(g2) + g2.translate(-posX, -posY) + } finally { + g2.dispose() } - - // Draw the main text - int length = notificationText.length(); - StringBuilder text = new StringBuilder(length); - - // are we "html" already? just check for the starting tag and strip off END html tag - if (length >= 13 && notificationText.regionMatches(true, length - 7, "", 0, 7)) { - text.append(notificationText); - text.delete(text.length() - 7, text.length()); - - length -= 7; - } - else { - text.append(""); - text.append(notificationText); - } - - // make sure the text is the correct length - if (length > textLengthLimit) { - text.delete(6 + textLengthLimit, text.length()); - text.append("..."); - } - text.append(""); - - 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); - mainTextLabel.paint(g2); - g2.translate(-posX, -posY); - } finally { - g2.dispose(); + return image } - - return image; } } diff --git a/src/dorkbox/notify/PopupList.kt b/src/dorkbox/notify/PopupList.kt index d7cab32..5fd5e5c 100755 --- a/src/dorkbox/notify/PopupList.kt +++ b/src/dorkbox/notify/PopupList.kt @@ -13,68 +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.GraphicsConfiguration; -import java.awt.Insets; -import java.awt.Point; -import java.awt.Toolkit; -import java.util.ArrayList; -import java.util.Iterator; - -import dorkbox.util.ScreenUtil; +import dorkbox.util.ScreenUtil +import java.awt.Point +import java.awt.Toolkit /** * Contains a list of notification popups + the Y offset (if any) */ -class PopupList { - private int offsetY = 0; - private ArrayList popups = new ArrayList(4); +internal class PopupList { + var offsetY = 0 + private set + + private val popups = ArrayList(4) /** * 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 */ - void calculateOffset(final boolean showFromTop, final int anchorX, final int anchorY) { + fun calculateOffset(showFromTop: Boolean, anchorX: Int, anchorY: Int) { if (offsetY == 0) { - Point point = new Point(anchorX, anchorY); - GraphicsConfiguration gc = ScreenUtil.getMonitorAtLocation(point) - .getDefaultConfiguration(); - - Insets screenInsets = Toolkit.getDefaultToolkit() - .getScreenInsets(gc); - + val point = Point(anchorX, anchorY) + val gc = ScreenUtil.getMonitorAtLocation(point).defaultConfiguration + val screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc) if (showFromTop) { if (screenInsets.top > 0) { - offsetY = screenInsets.top - LookAndFeel.MARGIN; + offsetY = screenInsets.top - LookAndFeel.MARGIN } } else { if (screenInsets.bottom > 0) { - offsetY = screenInsets.bottom + LookAndFeel.MARGIN; + offsetY = screenInsets.bottom + LookAndFeel.MARGIN } } } } - int getOffsetY() { - return offsetY; + fun size(): Int { + return popups.size } - - int size() { - return popups.size(); + fun add(lookAndFeel: LookAndFeel) { + popups.add(lookAndFeel) } - void add(final LookAndFeel lookAndFeel) { - popups.add(lookAndFeel); + operator fun iterator(): MutableIterator { + return popups.iterator() } - Iterator iterator() { - return popups.iterator(); - } - - LookAndFeel get(final int index) { - return popups.get(index); + operator fun get(index: Int): LookAndFeel { + return popups[index] } } diff --git a/src/dorkbox/notify/Pos.kt b/src/dorkbox/notify/Pos.kt index cd329a3..36df713 100755 --- a/src/dorkbox/notify/Pos.kt +++ b/src/dorkbox/notify/Pos.kt @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dorkbox.notify; +package dorkbox.notify -public -enum Pos { +enum class Pos { /** * top vertically, left horizontally */ @@ -40,5 +39,5 @@ enum Pos { /** * bottom vertically, right horizontally */ - BOTTOM_RIGHT, + BOTTOM_RIGHT } diff --git a/src/dorkbox/notify/Theme.kt b/src/dorkbox/notify/Theme.kt index bdd47c2..38c9679 100755 --- a/src/dorkbox/notify/Theme.kt +++ b/src/dorkbox/notify/Theme.kt @@ -13,59 +13,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dorkbox.notify; +package dorkbox.notify -import java.awt.Color; -import java.awt.Font; - -import dorkbox.util.FontUtil; +import dorkbox.util.FontUtil +import java.awt.Color +import java.awt.Font /** * Settings available to change the theme */ -public class Theme { - final Color panel_BG; - final Color titleText_FG; - final Color mainText_FG; - final Color closeX_FG; - final Color progress_FG; + val panel_BG: Color + val titleText_FG: Color + val mainText_FG: Color + val closeX_FG: Color + val progress_FG: Color + val titleTextFont: Font + val mainTextFont: Font - final Font titleTextFont; - final Font mainTextFont; - - - Theme(final String titleTextFont, final String mainTextFont, boolean isDarkTheme) { - this.titleTextFont = FontUtil.parseFont(titleTextFont); - this.mainTextFont = FontUtil.parseFont(mainTextFont); + constructor(titleTextFont: String, mainTextFont: String, isDarkTheme: Boolean) { + this.titleTextFont = FontUtil.parseFont(titleTextFont) + this.mainTextFont = FontUtil.parseFont(mainTextFont) if (isDarkTheme) { - panel_BG = Color.DARK_GRAY; - titleText_FG = Color.GRAY; - mainText_FG = Color.LIGHT_GRAY; - closeX_FG = Color.GRAY; - progress_FG = Color.gray; - } - else { - panel_BG = Color.WHITE; - titleText_FG = Color.GRAY.darker(); - mainText_FG = Color.GRAY; - closeX_FG = Color.LIGHT_GRAY; - progress_FG = new Color(0x42A5F5); + panel_BG = Color.DARK_GRAY + titleText_FG = Color.GRAY + mainText_FG = Color.LIGHT_GRAY + closeX_FG = Color.GRAY + progress_FG = Color.gray + } else { + panel_BG = Color.WHITE + titleText_FG = Color.GRAY.darker() + mainText_FG = Color.GRAY + closeX_FG = Color.LIGHT_GRAY + progress_FG = Color(0x42A5F5) } } - public - Theme(final String titleTextFont, final String mainTextFont, - final Color panel_BG, final Color titleText_FG, final Color mainText_FG, - final Color closeX_FG, final Color progress_FG) { - this.titleTextFont = FontUtil.parseFont(titleTextFont); - this.mainTextFont = FontUtil.parseFont(mainTextFont); - - 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; + constructor( + titleTextFont: String, + mainTextFont: String, + panel_BG: Color, + titleText_FG: Color, + mainText_FG: Color, + closeX_FG: Color, + progress_FG: Color + ) { + this.titleTextFont = FontUtil.parseFont(titleTextFont) + this.mainTextFont = FontUtil.parseFont(mainTextFont) + 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 } } diff --git a/src/dorkbox/notify/WindowAdapter.kt b/src/dorkbox/notify/WindowAdapter.kt index 68b9325..9ff06aa 100755 --- a/src/dorkbox/notify/WindowAdapter.kt +++ b/src/dorkbox/notify/WindowAdapter.kt @@ -13,31 +13,28 @@ * See the License for the specific language governing permissions and * 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 { - @Override - public - void windowClosing(WindowEvent e) { - if (e.getNewState() != WindowEvent.WINDOW_CLOSED) { - AsDesktop source = (AsDesktop) e.getSource(); - source.close(); +internal class WindowAdapter : WindowAdapter() { + override fun windowClosing(e: WindowEvent) { + if (e.newState != WindowEvent.WINDOW_CLOSED) { + val source = e.source as AsDesktop + source.close() } } - @Override - public - void windowLostFocus(WindowEvent e) { - if (e.getNewState() != WindowEvent.WINDOW_CLOSED) { - AsDesktop source = (AsDesktop) e.getSource(); + override fun windowLostFocus(e: WindowEvent) { + if (e.newState != WindowEvent.WINDOW_CLOSED) { + val source = e.source as AsDesktop // these don't work - //toFront(); - //requestFocus(); - //requestFocusInWindow(); - source.setAlwaysOnTop(false); - source.setAlwaysOnTop(true); + //toFront() + //requestFocus() + //requestFocusInWindow() + source.isAlwaysOnTop = false + source.isAlwaysOnTop = true } } } diff --git a/src9/dorkbox/notify/EmptyClass.java b/src9/dorkbox/notify/EmptyClass.java new file mode 100755 index 0000000..bd131c7 --- /dev/null +++ b/src9/dorkbox/notify/EmptyClass.java @@ -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 {} diff --git a/src9/module-info.java b/src9/module-info.java new file mode 100644 index 0000000..277903e --- /dev/null +++ b/src9/module-info.java @@ -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; +} diff --git a/src9/notify-dark.png b/src9/notify-dark.png new file mode 100755 index 0000000000000000000000000000000000000000..c9478cae516778324a490bd9966881dd7242ec98 GIT binary patch literal 6277 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 literal 0 HcmV?d00001 diff --git a/test/NotifyTest.java b/test/NotifyTest.java deleted file mode 100755 index d83d2a6..0000000 --- a/test/NotifyTest.java +++ /dev/null @@ -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() { -// @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() { -// @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() { -// @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() { -// @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(); -// } -// } - } -} diff --git a/test/dorkbox/notify/NotifyTest.kt b/test/dorkbox/notify/NotifyTest.kt new file mode 100755 index 0000000..bf9178c --- /dev/null +++ b/test/dorkbox/notify/NotifyTest.kt @@ -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) { + 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() + } + } + } +}