From ac44a42ae580189188b6a527007380e8276bf2a4 Mon Sep 17 00:00:00 2001 From: Robinson Date: Sat, 28 Jan 2023 21:15:19 +0100 Subject: [PATCH] Code polish, finish conversion to kotlin --- LICENSE | 23 ++-- src/dorkbox/notify/AsApplication.kt | 20 ++- src/dorkbox/notify/AsApplicationLAF.kt | 108 ++++++++++++++++ src/dorkbox/notify/AsDesktop.kt | 3 +- src/dorkbox/notify/AsDesktopLAF.kt | 75 +++++++++++ src/dorkbox/notify/LAFUtil.kt | 57 ++------ src/dorkbox/notify/LookAndFeel.kt | 122 +++--------------- src/dorkbox/notify/Notify.kt | 52 ++++++-- src/dorkbox/notify/NotifyCanvas.kt | 23 +++- ...WindowAdapter.kt => WindowCloseAdapter.kt} | 2 +- test/dorkbox/notify/NotifyTest.kt | 18 +-- 11 files changed, 312 insertions(+), 191 deletions(-) create mode 100755 src/dorkbox/notify/AsApplicationLAF.kt create mode 100755 src/dorkbox/notify/AsDesktopLAF.kt rename src/dorkbox/notify/{WindowAdapter.kt => WindowCloseAdapter.kt} (95%) diff --git a/LICENSE b/LICENSE index a4c8259..455f43b 100644 --- a/LICENSE +++ b/LICENSE @@ -6,6 +6,14 @@ Linux, MacOS, or Windows (notification/growl/toast/) popups for the desktop for Java 8+ Extra license information + - Kotlin - + [The Apache Software License, Version 2.0] + https://github.com/JetBrains/kotlin + Copyright 2020 + JetBrains s.r.o. and Kotlin Programming Language contributors + Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply + See: https://github.com/JetBrains/kotlin/blob/master/license/README.md + - TweenEngine - [The Apache Software License, Version 2.0] https://git.dorkbox.com/dorkbox/TweenEngine @@ -15,14 +23,8 @@ High performance and lightweight Animation/Tween framework for Java 8+ Extra license information - - Robert Penner's Easing Functions - - [BSD 3-Clause License] - http://robertpenner.com/easing - Copyright 2001 - Robert Penner - - Easing Functions - - [BSD 3-Clause License] + [Public Domain, per Creative Commons CC0] https://github.com/Michaelangel007/easing/blob/master/js/core/easing.js Copyright 2017 Michael Pohoreski @@ -665,10 +667,3 @@ https://git.dorkbox.com/dorkbox/PropertyLoader Copyright 2023 Dorkbox LLC - - - - Dorkbox TweenEngine - - [The Apache Software License, Version 2.0] - https://git.dorkbox.com/dorkbox/TweenEngine - Copyright 2023 - Dorkbox LLC diff --git a/src/dorkbox/notify/AsApplication.kt b/src/dorkbox/notify/AsApplication.kt index 0ca4805..eac79d2 100755 --- a/src/dorkbox/notify/AsApplication.kt +++ b/src/dorkbox/notify/AsApplication.kt @@ -30,8 +30,6 @@ internal class AsApplication internal constructor( private const val glassPanePrefix = "dorkbox.notify" } - private val window = notification.attachedFrame!! - private val parentListener: ComponentListener private val windowStateListener: WindowStateListener private var glassPane: JPanel @@ -41,13 +39,13 @@ internal class AsApplication internal constructor( // this makes sure that our notify canvas stay anchored to the parent window (if it's hidden/shown/moved/etc) parentListener = object : ComponentListener { override fun componentShown(e: ComponentEvent) { - notification.notifyLook?.reLayout(window.bounds) + notification.doLayoutForApp() } override fun componentHidden(e: ComponentEvent) {} override fun componentResized(e: ComponentEvent) { - notification.notifyLook?.reLayout(window.bounds) + notification.doLayoutForApp() } override fun componentMoved(e: ComponentEvent) {} @@ -56,10 +54,12 @@ internal class AsApplication internal constructor( windowStateListener = WindowStateListener { e -> val state = e.newState if (state and Frame.ICONIFIED == 0) { - notification.notifyLook?.reLayout(window.bounds) + notification.doLayoutForApp() } } + + val window = notification.attachedFrame!! window.addWindowStateListener(windowStateListener) window.addComponentListener(parentListener) @@ -88,14 +88,22 @@ internal class AsApplication internal constructor( } override fun setVisible(visible: Boolean, look: LookAndFeel) { + // was it already visible? + if (visible == glassPane.isVisible) { + // prevent "double setting" visible state + return + } + // this is because the order of operations are different based upon visibility. look.updatePositionsPre(visible) - look.updatePositionsPost(visible) + look.updatePositionsPost(visible, false) } // called on the Swing EDT. override fun close() { glassPane.remove(notifyCanvas) + + val window = notification.attachedFrame!! window.removeWindowStateListener(windowStateListener) window.removeComponentListener(parentListener) diff --git a/src/dorkbox/notify/AsApplicationLAF.kt b/src/dorkbox/notify/AsApplicationLAF.kt new file mode 100755 index 0000000..a01b42d --- /dev/null +++ b/src/dorkbox/notify/AsApplicationLAF.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.notify + +import dorkbox.notify.LAFUtil.SPACER +import dorkbox.notify.LAFUtil.growDown +import dorkbox.notify.LAFUtil.popups +import java.awt.Rectangle +import java.awt.Window + +internal class AsApplicationLAF(notification: Notify, notifyCanvas: NotifyCanvas, parent: Window, parentBounds: Rectangle): + LookAndFeel(parent, notifyCanvas, notification) { + + override val isDesktop = false + + init { + idAndPosition = parent.name + ":" + position + + anchorX = getAnchorX(position, parentBounds) + anchorY = getAnchorY(position, parentBounds) + } + + private fun getAnchorX(position: Position, bounds: Rectangle): Int { + // we use the screen that the mouse is currently on. + val startX = 0 + val screenWidth = bounds.getWidth().toInt() + + return when (position) { + Position.TOP_LEFT, Position.BOTTOM_LEFT -> LAFUtil.MARGIN + startX + Position.CENTER -> startX + screenWidth / 2 - NotifyCanvas.WIDTH / 2 - LAFUtil.MARGIN / 2 + Position.TOP_RIGHT, Position.BOTTOM_RIGHT -> startX + screenWidth - NotifyCanvas.WIDTH - LAFUtil.MARGIN + } + } + + private fun getAnchorY(position: Position, bounds: Rectangle): Int { + val startY = 0 + val screenHeight = bounds.getHeight().toInt() + + return when (position) { + Position.TOP_LEFT, Position.TOP_RIGHT -> startY + LAFUtil.MARGIN + Position.CENTER -> startY + screenHeight / 2 - NotifyCanvas.HEIGHT / 2 - LAFUtil.MARGIN / 2 - SPACER + Position.BOTTOM_LEFT, Position.BOTTOM_RIGHT -> screenHeight - NotifyCanvas.HEIGHT - LAFUtil.MARGIN - SPACER * 2 + } + } + + + // only called from an application + override 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) + anchorY = getAnchorY(position, bounds) + + val growDown = growDown(this) + + if (tween != null) { + tween!!.cancel() // cancel does its thing on the next tick of animation cycle + tween = null + } + + + var changedY: Int + if (popupIndex == 0) { + changedY = anchorY + } else { + synchronized(popups) { + val id = idAndPosition + val looks = popups[id] + changedY = if (looks != null) { + if (growDown) { + anchorY + popupIndex * (NotifyCanvas.HEIGHT + SPACER) + } else { + anchorY - popupIndex * (NotifyCanvas.HEIGHT + SPACER) + } + } else { + anchorY + } + } + } + + setLocation(anchorX, changedY) + } + + override var y: Int + get() = notifyCanvas.y + set(y) { + notifyCanvas.setLocation(notifyCanvas.x, y) + } + + override val x: Int + get() = notifyCanvas.x + + override fun setLocation(x: Int, y: Int) { + notifyCanvas.setLocation(x, y) + } +} diff --git a/src/dorkbox/notify/AsDesktop.kt b/src/dorkbox/notify/AsDesktop.kt index 5e20a07..2b8dbe6 100755 --- a/src/dorkbox/notify/AsDesktop.kt +++ b/src/dorkbox/notify/AsDesktop.kt @@ -51,7 +51,8 @@ internal class AsDesktop internal constructor(val notification: Notify, notifyCa super.setVisible(visible) // this is because the order of operations are different based upon visibility. - look.updatePositionsPost(visible) + look.updatePositionsPost(visible, true) + if (visible) { toFront() } diff --git a/src/dorkbox/notify/AsDesktopLAF.kt b/src/dorkbox/notify/AsDesktopLAF.kt new file mode 100755 index 0000000..6e95d0f --- /dev/null +++ b/src/dorkbox/notify/AsDesktopLAF.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.notify + +import dorkbox.notify.LAFUtil.SPACER +import dorkbox.notify.LAFUtil.windowCloseListener +import dorkbox.util.ScreenUtil +import java.awt.Point +import java.awt.Rectangle +import java.awt.Window + +internal class AsDesktopLAF(notification: Notify, notifyCanvas: NotifyCanvas, private val parent: Window, parentBounds: Rectangle) + : LookAndFeel(parent, notifyCanvas, notification) { + + override val isDesktop = true + + init { + parent.addWindowListener(windowCloseListener) + + val point = Point(parentBounds.getX().toInt(), parentBounds.getY().toInt()) + idAndPosition = ScreenUtil.getMonitorNumberAtLocation(point).toString() + ":" + position + + anchorX = getAnchorX(position, parentBounds) + anchorY = getAnchorY(position, parentBounds) + } + + private fun getAnchorX(position: Position, bounds: Rectangle): Int { + // we use the screen that the mouse is currently on. + val startX = bounds.getX().toInt() + val screenWidth = bounds.getWidth().toInt() + + return when (position) { + Position.TOP_LEFT, Position.BOTTOM_LEFT -> LAFUtil.MARGIN + startX + Position.CENTER -> startX + screenWidth / 2 - NotifyCanvas.WIDTH / 2 - LAFUtil.MARGIN / 2 + Position.TOP_RIGHT, Position.BOTTOM_RIGHT -> startX + screenWidth - NotifyCanvas.WIDTH - LAFUtil.MARGIN + } + } + + private fun getAnchorY(position: Position, bounds: Rectangle): Int { + val startY = bounds.getY().toInt() + val screenHeight = bounds.getHeight().toInt() + + return when (position) { + Position.TOP_LEFT, Position.TOP_RIGHT -> startY + LAFUtil.MARGIN + Position.CENTER -> startY + screenHeight / 2 - NotifyCanvas.HEIGHT / 2 - LAFUtil.MARGIN / 2 - SPACER + Position.BOTTOM_LEFT, Position.BOTTOM_RIGHT -> startY + screenHeight - NotifyCanvas.HEIGHT - LAFUtil.MARGIN + } + } + + override var y: Int + get() = parent.y + set(y) { + parent.setLocation(parent.x, y) + } + + override val x: Int + get() = parent.x + + override fun setLocation(x: Int, y: Int) { + parent.setLocation(x, y) + } +} diff --git a/src/dorkbox/notify/LAFUtil.kt b/src/dorkbox/notify/LAFUtil.kt index dcabc73..b9d078f 100755 --- a/src/dorkbox/notify/LAFUtil.kt +++ b/src/dorkbox/notify/LAFUtil.kt @@ -16,9 +16,9 @@ package dorkbox.notify import dorkbox.swingActiveRender.ActionHandlerLong -import dorkbox.tweenEngine.TweenCallback.Events.COMPLETE import dorkbox.tweenEngine.TweenEngine.Companion.create import dorkbox.tweenEngine.TweenEquations +import dorkbox.tweenEngine.TweenEvents import dorkbox.util.ScreenUtil import java.awt.GraphicsEnvironment import java.awt.MouseInfo @@ -27,7 +27,7 @@ import java.awt.event.MouseAdapter import java.util.* internal object LAFUtil{ - val popups: MutableMap = HashMap() + val popups = mutableMapOf() // access is only from a single thread ever, so unsafe is preferred. val animation = create().unsafe().build() @@ -40,7 +40,7 @@ internal object LAFUtil{ const val SPACER = 10 const val MARGIN = 20 - val windowListener: java.awt.event.WindowAdapter = WindowAdapter() + val windowCloseListener: java.awt.event.WindowAdapter = WindowCloseAdapter() val mouseListener: MouseAdapter = ClickAdapter() val RANDOM = Random() @@ -69,43 +69,8 @@ internal object LAFUtil{ return device.defaultConfiguration.bounds } - fun getAnchorX(position: Position, 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) { - Position.TOP_LEFT, Position.BOTTOM_LEFT -> MARGIN + startX - Position.CENTER -> startX + screenWidth / 2 - NotifyCanvas.WIDTH / 2 - MARGIN / 2 - Position.TOP_RIGHT, Position.BOTTOM_RIGHT -> startX + screenWidth - NotifyCanvas.WIDTH - MARGIN - } - } - - fun getAnchorY(position: Position, bounds: Rectangle, isDesktop: Boolean): Int { - val startY = if (isDesktop) { - bounds.getY().toInt() - } else { - 0 - } - - val screenHeight = bounds.getHeight().toInt() - return when (position) { - Position.TOP_LEFT, Position.TOP_RIGHT -> startY + MARGIN - Position.CENTER -> startY + screenHeight / 2 - NotifyCanvas.HEIGHT / 2 - MARGIN / 2 - SPACER - Position.BOTTOM_LEFT, Position.BOTTOM_RIGHT -> if (isDesktop) { - startY + screenHeight - NotifyCanvas.HEIGHT - MARGIN - } else { - screenHeight - NotifyCanvas.HEIGHT - MARGIN - SPACER * 2 - } - } - } - // only called on the swing EDT thread - fun addPopupToMap(sourceLook: LookAndFeel) { + fun addPopupToMap(sourceLook: LookAndFeel, isDesktop: Boolean) { synchronized(popups) { val id = sourceLook.idAndPosition var looks = popups[id] @@ -126,7 +91,7 @@ internal object LAFUtil{ anchorY } else { val growDown = growDown(sourceLook) - if (sourceLook.isDesktopNotification && index == 1) { + if (isDesktop && 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) @@ -141,13 +106,16 @@ internal object LAFUtil{ looks.add(sourceLook) sourceLook.setLocation(anchorX, targetY) - if (sourceLook.hideAfterDurationInSeconds > 0 && sourceLook.hideTween == null) { + if (index == 0 && sourceLook.hideAfterDurationInSeconds > 0 && sourceLook.hideTween == null) { + println("start timeline") // begin a timeline to get rid of the popup (default is 5 seconds) - animation.to(sourceLook, NotifyAccessor.PROGRESS, accessor, sourceLook.hideAfterDurationInSeconds) + val x = animation.to(sourceLook, NotifyAccessor.PROGRESS, accessor, sourceLook.hideAfterDurationInSeconds) .target(NotifyCanvas.WIDTH.toFloat()) .ease(TweenEquations.Linear) - .addCallback(COMPLETE) { sourceLook.notification.onClose() } + .addCallback(TweenEvents.COMPLETE) { sourceLook.notification.onClose() } .start() + + println("started $x") } } } @@ -158,6 +126,7 @@ internal object LAFUtil{ var popupsAreEmpty: Boolean synchronized(popups) { + println("remove") popupsAreEmpty = popups.isEmpty() val allLooks = popups[sourceLook.idAndPosition] @@ -203,7 +172,7 @@ internal object LAFUtil{ .to(look, NotifyAccessor.Y_POS, accessor, MOVE_DURATION) .target(changedY.toFloat()) .ease(TweenEquations.Linear) - .addCallback(COMPLETE) { + .addCallback(TweenEvents.COMPLETE) { // make sure to remove the tween once it's done, otherwise .kill can do weird things. look.tween = null } diff --git a/src/dorkbox/notify/LookAndFeel.kt b/src/dorkbox/notify/LookAndFeel.kt index 8e4e33c..41f51d6 100755 --- a/src/dorkbox/notify/LookAndFeel.kt +++ b/src/dorkbox/notify/LookAndFeel.kt @@ -16,43 +16,31 @@ package dorkbox.notify import dorkbox.notify.LAFUtil.RANDOM -import dorkbox.notify.LAFUtil.SPACER import dorkbox.notify.LAFUtil.accessor import dorkbox.notify.LAFUtil.addPopupToMap import dorkbox.notify.LAFUtil.animation import dorkbox.notify.LAFUtil.frameStartHandler -import dorkbox.notify.LAFUtil.getAnchorX -import dorkbox.notify.LAFUtil.getAnchorY -import dorkbox.notify.LAFUtil.growDown import dorkbox.notify.LAFUtil.mouseListener -import dorkbox.notify.LAFUtil.popups import dorkbox.notify.LAFUtil.removePopupFromMap -import dorkbox.notify.LAFUtil.windowListener import dorkbox.swingActiveRender.SwingActiveRender import dorkbox.tweenEngine.Tween import dorkbox.tweenEngine.TweenEquations -import dorkbox.util.ScreenUtil -import java.awt.Point import java.awt.Rectangle import java.awt.Window -internal class LookAndFeel( - private val parent: Window, - private val notifyCanvas: NotifyCanvas, - val notification: Notify, - parentBounds: Rectangle, - val isDesktopNotification: Boolean -) { - @Volatile - var anchorX: Int +internal abstract class LookAndFeel(private val parent: Window, val notifyCanvas: NotifyCanvas, val notification: Notify) { @Volatile - var anchorY: Int + var anchorX = 0 + + @Volatile + var anchorY = 0 + val hideAfterDurationInSeconds: Float val position: Position // this is used in combination with position, so that we can track which screen and what position a popup is in - var idAndPosition: String + var idAndPosition = "" var popupIndex = 0 @Volatile @@ -61,64 +49,19 @@ internal class LookAndFeel( @Volatile var hideTween: Tween<*>? = null + abstract val isDesktop: Boolean + init { - if (isDesktopNotification) { - parent.addWindowListener(windowListener) - } - 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 { - parent.name + ":" + position - } - - anchorX = getAnchorX(position, parentBounds, isDesktopNotification) - anchorY = getAnchorY(position, parentBounds, isDesktopNotification) } // only called from an application - fun reLayout(bounds: Rectangle) { - // when the parent window moves, we stop all animation and snap the popup into place. This simplifies logic greatly - anchorX = getAnchorX(position, bounds, isDesktopNotification) - anchorY = getAnchorY(position, bounds, isDesktopNotification) + open fun reLayout(bounds: Rectangle) {} - val growDown = growDown(this) - - if (tween != null) { - tween!!.cancel() // cancel does its thing on the next tick of animation cycle - tween = null - } - - - var changedY: Int - if (popupIndex == 0) { - changedY = anchorY - } else { - synchronized(popups) { - val id = idAndPosition - val looks = popups[id] - changedY = if (looks != null) { - if (growDown) { - anchorY + popupIndex * (NotifyCanvas.HEIGHT + SPACER) - } else { - anchorY - popupIndex * (NotifyCanvas.HEIGHT + SPACER) - } - } else { - anchorY - } - } - } - - setLocation(anchorX, changedY) - } - - fun close() { + open fun close() { if (hideTween != null) { hideTween!!.cancel() hideTween = null @@ -129,13 +72,9 @@ internal class LookAndFeel( tween = null } - if (isDesktopNotification) { - parent.removeWindowListener(windowListener) - } - parent.removeMouseListener(mouseListener) updatePositionsPre(false) - updatePositionsPost(false) + updatePositionsPost(false, isDesktop) } fun shake(durationInMillis: Int, amplitude: Int) { @@ -170,34 +109,12 @@ internal class LookAndFeel( .start() } - var y: Int - get() = if (isDesktopNotification) { - parent.y - } else { - notifyCanvas.y - } - set(y) { - if (isDesktopNotification) { - parent.setLocation(parent.x, y) - } else { - notifyCanvas.setLocation(notifyCanvas.x, y) - } - } + abstract var y: Int - val x: Int - get() = if (isDesktopNotification) { - parent.x - } else { - notifyCanvas.x - } + abstract val x: Int + + abstract fun setLocation(x: Int, y: Int) - fun setLocation(x: Int, y: Int) { - if (isDesktopNotification) { - parent.setLocation(x, y) - } else { - notifyCanvas.setLocation(x, y) - } - } var progress: Int get() = notifyCanvas.progress @@ -210,6 +127,7 @@ internal class LookAndFeel( */ fun updatePositionsPre(visible: Boolean) { if (!visible) { + println("remove post") val popupsAreEmpty = removePopupFromMap(this) SwingActiveRender.removeActiveRender(notifyCanvas) if (popupsAreEmpty) { @@ -222,8 +140,9 @@ internal class LookAndFeel( /** * when using active rendering, we have to add it AFTER we have set the visibility status */ - fun updatePositionsPost(visible: Boolean) { + fun updatePositionsPost(visible: Boolean, isDesktop: Boolean) { if (visible) { + println("add post") SwingActiveRender.addActiveRender(notifyCanvas) // start if we have previously stopped the timer @@ -231,7 +150,8 @@ internal class LookAndFeel( animation.resetUpdateTime() SwingActiveRender.addActiveRenderFrameStart(frameStartHandler) } - addPopupToMap(this) + + addPopupToMap(this, isDesktop) } } } diff --git a/src/dorkbox/notify/Notify.kt b/src/dorkbox/notify/Notify.kt index 19b65c5..b1e4203 100755 --- a/src/dorkbox/notify/Notify.kt +++ b/src/dorkbox/notify/Notify.kt @@ -151,6 +151,8 @@ class Notify private constructor() { } } + @Volatile + private var notifyCanvas: NotifyCanvas? = null @Volatile internal var notifyPopup: NotifyType? = null @Volatile @@ -158,12 +160,24 @@ class Notify private constructor() { @Volatile var title = "Notification" + set(value) { + field = value + notifyCanvas?.refresh() + } @Volatile var text = "Lorem ipsum" + set(value) { + field = value + notifyCanvas?.refresh() + } @Volatile var theme = Theme.defaultLight + set(value) { + field = value + notifyCanvas?.refresh() + } @Volatile var position = Position.BOTTOM_RIGHT @@ -176,12 +190,20 @@ class Notify private constructor() { */ @Volatile var hideCloseButton = false + set(value) { + field = value + notifyCanvas?.refresh() + } @Volatile var screen = Short.MIN_VALUE.toInt() @Volatile var image: ImageIcon? = null + set(value) { + field = value + notifyCanvas?.refresh() + } /** * Called when the notification is closed, either via close button or via close() @@ -195,9 +217,6 @@ class Notify private constructor() { @Volatile var onClickAction: Notify.()->Unit = {} - @Volatile - var name = DIALOG_ERROR - @Volatile var shakeDurationInMillis = 0 @@ -293,6 +312,7 @@ class Notify private constructor() { */ fun theme(theme: Theme): Notify { this.theme = theme + notifyCanvas?.refresh() return this } @@ -301,6 +321,7 @@ class Notify private constructor() { */ fun hideCloseButton(): Notify { hideCloseButton = true + notifyCanvas?.refresh() return this } @@ -308,7 +329,7 @@ class Notify private constructor() { * Shows the notification with the built-in 'warning' image. */ fun showWarning() { - name = DIALOG_WARNING + title = DIALOG_WARNING image = getImage(DIALOG_WARNING) show() } @@ -317,7 +338,7 @@ class Notify private constructor() { * Shows the notification with the built-in 'information' image. */ fun showInformation() { - name = DIALOG_INFORMATION + title = "Information" image = getImage(DIALOG_INFORMATION) show() } @@ -326,7 +347,7 @@ class Notify private constructor() { * Shows the notification with the built-in 'error' image. */ fun showError() { - name = DIALOG_ERROR + title = "Error" image = getImage(DIALOG_ERROR) show() } @@ -335,7 +356,7 @@ class Notify private constructor() { * Shows the notification with the built-in 'confirm' image. */ fun showConfirm() { - name = DIALOG_CONFIRM + title = "Confirm" image = getImage(DIALOG_CONFIRM) show() } @@ -349,6 +370,10 @@ class Notify private constructor() { // must be done in the swing EDT SwingUtil.invokeAndWaitQuietly { + if (notify.notifyCanvas != null) { + return@invokeAndWaitQuietly + } + val window = notify.attachedFrame val shakeDuration = notify.shakeDurationInMillis val shakeAmp = notify.shakeAmplitude @@ -360,10 +385,10 @@ class Notify private constructor() { if (window == null) { notifyPopup = AsDesktop(notify, notifyCanvas) - look = LookAndFeel(notifyPopup, notifyCanvas, notify, LAFUtil.getGraphics(notify.screen), true) + look = AsDesktopLAF(notify, notifyCanvas, notifyPopup, LAFUtil.getGraphics(notify.screen)) } else { notifyPopup = AsApplication(notify, notifyCanvas) - look = LookAndFeel(window, notifyCanvas, notify, window.bounds, false) + look = AsApplicationLAF(notify, notifyCanvas, window, window.bounds) } notifyPopup.setVisible(true, look) @@ -372,6 +397,7 @@ class Notify private constructor() { look.shake(shakeDuration, shakeAmp) } + notify.notifyCanvas = notifyCanvas notify.notifyPopup = notifyPopup notify.notifyLook = look } @@ -431,12 +457,20 @@ class Notify private constructor() { // called when this notification is closed. called in the swing EDT! internal fun onClose() { this.notifyPopup!!.close() + this.notifyLook!!.close() + this.onCloseAction.invoke(this) + notifyPopup = null notifyLook = null + notifyCanvas = null } internal fun onClickAction() { this.onClickAction.invoke(this) } + + internal fun doLayoutForApp() { + notifyLook?.reLayout(attachedFrame!!.bounds) + } } diff --git a/src/dorkbox/notify/NotifyCanvas.kt b/src/dorkbox/notify/NotifyCanvas.kt index 1b99c11..9c023d8 100755 --- a/src/dorkbox/notify/NotifyCanvas.kt +++ b/src/dorkbox/notify/NotifyCanvas.kt @@ -48,11 +48,14 @@ internal class NotifyCanvas( isFocusable = false background = theme.panel_BG - // now we setup the rendering of the image cachedImage = renderBackgroundInfo(notification.title, notification.text, theme, imageIcon) } + fun refresh() { + cachedImage = renderBackgroundInfo(notification.title, notification.text, theme, imageIcon) + } + override fun paint(g: Graphics) { // we cache the text + image (to another image), and then always render the close + progressbar @@ -111,6 +114,13 @@ internal class NotifyCanvas( // draw the progress bar along the bottom g2.color = theme.progress_FG g2.fillRect(0, PROGRESS_HEIGHT, progress, 2) +// if (notification.title == "Notify title 0") { +// println("asd") +// Toolkit.getDefaultToolkit().sync() +// } + + } catch (e: Exception) { + e.printStackTrace() } finally { g2.dispose() } @@ -148,7 +158,7 @@ internal class NotifyCanvas( 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 { + private fun renderBackgroundInfo(title: String, textBody: String, theme: Theme, imageIcon: ImageIcon?): BufferedImage { val image = BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB) val g2 = image.createGraphics() @@ -185,17 +195,17 @@ internal class NotifyCanvas( } // Draw the main text - var length = notificationText.length + var length = textBody.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) + if (length >= 13 && textBody.regionMatches(length - 7, "", 0, 7, ignoreCase = true)) { + text.append(textBody) text.delete(text.length - 7, text.length) length -= 7 } else { text.append("") - text.append(notificationText) + text.append(textBody) } // make sure the text is the correct length @@ -218,6 +228,7 @@ internal class NotifyCanvas( } finally { g2.dispose() } + return image } } diff --git a/src/dorkbox/notify/WindowAdapter.kt b/src/dorkbox/notify/WindowCloseAdapter.kt similarity index 95% rename from src/dorkbox/notify/WindowAdapter.kt rename to src/dorkbox/notify/WindowCloseAdapter.kt index d3c5d82..25b6d81 100755 --- a/src/dorkbox/notify/WindowAdapter.kt +++ b/src/dorkbox/notify/WindowCloseAdapter.kt @@ -18,7 +18,7 @@ package dorkbox.notify import java.awt.event.WindowAdapter import java.awt.event.WindowEvent -internal class WindowAdapter : WindowAdapter() { +internal class WindowCloseAdapter : WindowAdapter() { override fun windowClosing(e: WindowEvent) { if (e.newState != WindowEvent.WINDOW_CLOSED) { val source = e.source as AsDesktop diff --git a/test/dorkbox/notify/NotifyTest.kt b/test/dorkbox/notify/NotifyTest.kt index 10b4595..8ec48c8 100755 --- a/test/dorkbox/notify/NotifyTest.kt +++ b/test/dorkbox/notify/NotifyTest.kt @@ -51,8 +51,8 @@ object NotifyTest { // bottomRightInFrame(3, frame) // topLeftInFrame(3, frame) - react() -// topRightMonitor(3) +// react() + topRightMonitor(2) // bottomLeftScaled(3, frame, image) // bottomLeftStacking(3, frame, image) } @@ -70,7 +70,7 @@ object NotifyTest { .shake(4300, 10) // .hideCloseButton() // if the hideButton is visible, then it's possible to change things when clicked .onClickAction { - notify.text = "HOWDY" + text = "HOWDY" System.err.println("Notification clicked on!") } notify.show() @@ -81,7 +81,7 @@ object NotifyTest { } } - fun topRightMonitor(count: Int) { + private fun topRightMonitor(count: Int) { var notify: Notify for (i in 0 until count) { @@ -89,17 +89,17 @@ object NotifyTest { .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) + .hideAfter(130000) .position(Position.TOP_RIGHT) // .setScreen(0) - .theme(Theme.defaultDark) + .theme(Theme.defaultDark) // .shake(1300, 4) - .shake(4300, 10) - .hideCloseButton() +// .shake(4300, 10) +// .hideCloseButton() .onClickAction { System.err.println("Notification $i clicked on!") } notify.show() try { - Thread.sleep(3000) + Thread.sleep(2000) } catch (e: InterruptedException) { e.printStackTrace() }