Code polish, finish conversion to kotlin

This commit is contained in:
Robinson 2023-01-28 21:15:19 +01:00
parent b286ee9cf1
commit ac44a42ae5
No known key found for this signature in database
GPG Key ID: 8E7DB78588BD6F5C
11 changed files with 312 additions and 191 deletions

23
LICENSE
View File

@ -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

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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<String, PopupList> = HashMap()
val popups = mutableMapOf<String, PopupList>()
// 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
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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, "</html>", 0, 7, ignoreCase = true)) {
text.append(notificationText)
if (length >= 13 && textBody.regionMatches(length - 7, "</html>", 0, 7, ignoreCase = true)) {
text.append(textBody)
text.delete(text.length - 7, text.length)
length -= 7
} else {
text.append("<html>")
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
}
}

View File

@ -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

View File

@ -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()
}