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+ Linux, MacOS, or Windows (notification/growl/toast/) popups for the desktop for Java 8+
Extra license information 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 - - TweenEngine -
[The Apache Software License, Version 2.0] [The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/TweenEngine https://git.dorkbox.com/dorkbox/TweenEngine
@ -15,14 +23,8 @@
High performance and lightweight Animation/Tween framework for Java 8+ High performance and lightweight Animation/Tween framework for Java 8+
Extra license information Extra license information
- Robert Penner's Easing Functions -
[BSD 3-Clause License]
http://robertpenner.com/easing
Copyright 2001
Robert Penner
- Easing Functions - - Easing Functions -
[BSD 3-Clause License] [Public Domain, per Creative Commons CC0]
https://github.com/Michaelangel007/easing/blob/master/js/core/easing.js https://github.com/Michaelangel007/easing/blob/master/js/core/easing.js
Copyright 2017 Copyright 2017
Michael Pohoreski Michael Pohoreski
@ -665,10 +667,3 @@
https://git.dorkbox.com/dorkbox/PropertyLoader https://git.dorkbox.com/dorkbox/PropertyLoader
Copyright 2023 Copyright 2023
Dorkbox LLC 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 const val glassPanePrefix = "dorkbox.notify"
} }
private val window = notification.attachedFrame!!
private val parentListener: ComponentListener private val parentListener: ComponentListener
private val windowStateListener: WindowStateListener private val windowStateListener: WindowStateListener
private var glassPane: JPanel 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) // this makes sure that our notify canvas stay anchored to the parent window (if it's hidden/shown/moved/etc)
parentListener = object : ComponentListener { parentListener = object : ComponentListener {
override fun componentShown(e: ComponentEvent) { override fun componentShown(e: ComponentEvent) {
notification.notifyLook?.reLayout(window.bounds) notification.doLayoutForApp()
} }
override fun componentHidden(e: ComponentEvent) {} override fun componentHidden(e: ComponentEvent) {}
override fun componentResized(e: ComponentEvent) { override fun componentResized(e: ComponentEvent) {
notification.notifyLook?.reLayout(window.bounds) notification.doLayoutForApp()
} }
override fun componentMoved(e: ComponentEvent) {} override fun componentMoved(e: ComponentEvent) {}
@ -56,10 +54,12 @@ internal class AsApplication internal constructor(
windowStateListener = WindowStateListener { e -> windowStateListener = WindowStateListener { e ->
val state = e.newState val state = e.newState
if (state and Frame.ICONIFIED == 0) { if (state and Frame.ICONIFIED == 0) {
notification.notifyLook?.reLayout(window.bounds) notification.doLayoutForApp()
} }
} }
val window = notification.attachedFrame!!
window.addWindowStateListener(windowStateListener) window.addWindowStateListener(windowStateListener)
window.addComponentListener(parentListener) window.addComponentListener(parentListener)
@ -88,14 +88,22 @@ internal class AsApplication internal constructor(
} }
override fun setVisible(visible: Boolean, look: LookAndFeel) { 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. // this is because the order of operations are different based upon visibility.
look.updatePositionsPre(visible) look.updatePositionsPre(visible)
look.updatePositionsPost(visible) look.updatePositionsPost(visible, false)
} }
// called on the Swing EDT. // called on the Swing EDT.
override fun close() { override fun close() {
glassPane.remove(notifyCanvas) glassPane.remove(notifyCanvas)
val window = notification.attachedFrame!!
window.removeWindowStateListener(windowStateListener) window.removeWindowStateListener(windowStateListener)
window.removeComponentListener(parentListener) 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) super.setVisible(visible)
// this is because the order of operations are different based upon visibility. // this is because the order of operations are different based upon visibility.
look.updatePositionsPost(visible) look.updatePositionsPost(visible, true)
if (visible) { if (visible) {
toFront() 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 package dorkbox.notify
import dorkbox.swingActiveRender.ActionHandlerLong import dorkbox.swingActiveRender.ActionHandlerLong
import dorkbox.tweenEngine.TweenCallback.Events.COMPLETE
import dorkbox.tweenEngine.TweenEngine.Companion.create import dorkbox.tweenEngine.TweenEngine.Companion.create
import dorkbox.tweenEngine.TweenEquations import dorkbox.tweenEngine.TweenEquations
import dorkbox.tweenEngine.TweenEvents
import dorkbox.util.ScreenUtil import dorkbox.util.ScreenUtil
import java.awt.GraphicsEnvironment import java.awt.GraphicsEnvironment
import java.awt.MouseInfo import java.awt.MouseInfo
@ -27,7 +27,7 @@ import java.awt.event.MouseAdapter
import java.util.* import java.util.*
internal object LAFUtil{ 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. // access is only from a single thread ever, so unsafe is preferred.
val animation = create().unsafe().build() val animation = create().unsafe().build()
@ -40,7 +40,7 @@ internal object LAFUtil{
const val SPACER = 10 const val SPACER = 10
const val MARGIN = 20 const val MARGIN = 20
val windowListener: java.awt.event.WindowAdapter = WindowAdapter() val windowCloseListener: java.awt.event.WindowAdapter = WindowCloseAdapter()
val mouseListener: MouseAdapter = ClickAdapter() val mouseListener: MouseAdapter = ClickAdapter()
val RANDOM = Random() val RANDOM = Random()
@ -69,43 +69,8 @@ internal object LAFUtil{
return device.defaultConfiguration.bounds 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 // only called on the swing EDT thread
fun addPopupToMap(sourceLook: LookAndFeel) { fun addPopupToMap(sourceLook: LookAndFeel, isDesktop: Boolean) {
synchronized(popups) { synchronized(popups) {
val id = sourceLook.idAndPosition val id = sourceLook.idAndPosition
var looks = popups[id] var looks = popups[id]
@ -126,7 +91,7 @@ internal object LAFUtil{
anchorY anchorY
} else { } else {
val growDown = growDown(sourceLook) 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. // have to adjust for offsets when the window-manager has a toolbar that consumes space and prevents overlap.
// this is only done when the 2nd popup is added to the list // this is only done when the 2nd popup is added to the list
looks.calculateOffset(growDown, anchorX, anchorY) looks.calculateOffset(growDown, anchorX, anchorY)
@ -141,13 +106,16 @@ internal object LAFUtil{
looks.add(sourceLook) looks.add(sourceLook)
sourceLook.setLocation(anchorX, targetY) 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) // 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()) .target(NotifyCanvas.WIDTH.toFloat())
.ease(TweenEquations.Linear) .ease(TweenEquations.Linear)
.addCallback(COMPLETE) { sourceLook.notification.onClose() } .addCallback(TweenEvents.COMPLETE) { sourceLook.notification.onClose() }
.start() .start()
println("started $x")
} }
} }
} }
@ -158,6 +126,7 @@ internal object LAFUtil{
var popupsAreEmpty: Boolean var popupsAreEmpty: Boolean
synchronized(popups) { synchronized(popups) {
println("remove")
popupsAreEmpty = popups.isEmpty() popupsAreEmpty = popups.isEmpty()
val allLooks = popups[sourceLook.idAndPosition] val allLooks = popups[sourceLook.idAndPosition]
@ -203,7 +172,7 @@ internal object LAFUtil{
.to(look, NotifyAccessor.Y_POS, accessor, MOVE_DURATION) .to(look, NotifyAccessor.Y_POS, accessor, MOVE_DURATION)
.target(changedY.toFloat()) .target(changedY.toFloat())
.ease(TweenEquations.Linear) .ease(TweenEquations.Linear)
.addCallback(COMPLETE) { .addCallback(TweenEvents.COMPLETE) {
// make sure to remove the tween once it's done, otherwise .kill can do weird things. // make sure to remove the tween once it's done, otherwise .kill can do weird things.
look.tween = null look.tween = null
} }

View File

@ -16,43 +16,31 @@
package dorkbox.notify package dorkbox.notify
import dorkbox.notify.LAFUtil.RANDOM import dorkbox.notify.LAFUtil.RANDOM
import dorkbox.notify.LAFUtil.SPACER
import dorkbox.notify.LAFUtil.accessor import dorkbox.notify.LAFUtil.accessor
import dorkbox.notify.LAFUtil.addPopupToMap import dorkbox.notify.LAFUtil.addPopupToMap
import dorkbox.notify.LAFUtil.animation import dorkbox.notify.LAFUtil.animation
import dorkbox.notify.LAFUtil.frameStartHandler 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.mouseListener
import dorkbox.notify.LAFUtil.popups
import dorkbox.notify.LAFUtil.removePopupFromMap import dorkbox.notify.LAFUtil.removePopupFromMap
import dorkbox.notify.LAFUtil.windowListener
import dorkbox.swingActiveRender.SwingActiveRender import dorkbox.swingActiveRender.SwingActiveRender
import dorkbox.tweenEngine.Tween import dorkbox.tweenEngine.Tween
import dorkbox.tweenEngine.TweenEquations import dorkbox.tweenEngine.TweenEquations
import dorkbox.util.ScreenUtil
import java.awt.Point
import java.awt.Rectangle import java.awt.Rectangle
import java.awt.Window import java.awt.Window
internal class LookAndFeel( internal abstract class LookAndFeel(private val parent: Window, val notifyCanvas: NotifyCanvas, val notification: Notify) {
private val parent: Window,
private val notifyCanvas: NotifyCanvas,
val notification: Notify,
parentBounds: Rectangle,
val isDesktopNotification: Boolean
) {
@Volatile
var anchorX: Int
@Volatile @Volatile
var anchorY: Int var anchorX = 0
@Volatile
var anchorY = 0
val hideAfterDurationInSeconds: Float val hideAfterDurationInSeconds: Float
val position: Position val position: Position
// this is used in combination with position, so that we can track which screen and what position a popup is in // 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 var popupIndex = 0
@Volatile @Volatile
@ -61,64 +49,19 @@ internal class LookAndFeel(
@Volatile @Volatile
var hideTween: Tween<*>? = null var hideTween: Tween<*>? = null
abstract val isDesktop: Boolean
init { init {
if (isDesktopNotification) {
parent.addWindowListener(windowListener)
}
notifyCanvas.addMouseListener(mouseListener) notifyCanvas.addMouseListener(mouseListener)
hideAfterDurationInSeconds = notification.hideAfterDurationInMillis / 1000.0f hideAfterDurationInSeconds = notification.hideAfterDurationInMillis / 1000.0f
position = notification.position 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 // only called from an application
fun reLayout(bounds: Rectangle) { open fun reLayout(bounds: Rectangle) {}
// when the parent window moves, we stop all animation and snap the popup into place. This simplifies logic greatly
anchorX = getAnchorX(position, bounds, isDesktopNotification)
anchorY = getAnchorY(position, bounds, isDesktopNotification)
val growDown = growDown(this) open fun close() {
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() {
if (hideTween != null) { if (hideTween != null) {
hideTween!!.cancel() hideTween!!.cancel()
hideTween = null hideTween = null
@ -129,13 +72,9 @@ internal class LookAndFeel(
tween = null tween = null
} }
if (isDesktopNotification) {
parent.removeWindowListener(windowListener)
}
parent.removeMouseListener(mouseListener) parent.removeMouseListener(mouseListener)
updatePositionsPre(false) updatePositionsPre(false)
updatePositionsPost(false) updatePositionsPost(false, isDesktop)
} }
fun shake(durationInMillis: Int, amplitude: Int) { fun shake(durationInMillis: Int, amplitude: Int) {
@ -170,34 +109,12 @@ internal class LookAndFeel(
.start() .start()
} }
var y: Int abstract var y: Int
get() = if (isDesktopNotification) {
parent.y
} else {
notifyCanvas.y
}
set(y) {
if (isDesktopNotification) {
parent.setLocation(parent.x, y)
} else {
notifyCanvas.setLocation(notifyCanvas.x, y)
}
}
val x: Int abstract val x: Int
get() = if (isDesktopNotification) {
parent.x abstract fun setLocation(x: Int, y: Int)
} else {
notifyCanvas.x
}
fun setLocation(x: Int, y: Int) {
if (isDesktopNotification) {
parent.setLocation(x, y)
} else {
notifyCanvas.setLocation(x, y)
}
}
var progress: Int var progress: Int
get() = notifyCanvas.progress get() = notifyCanvas.progress
@ -210,6 +127,7 @@ internal class LookAndFeel(
*/ */
fun updatePositionsPre(visible: Boolean) { fun updatePositionsPre(visible: Boolean) {
if (!visible) { if (!visible) {
println("remove post")
val popupsAreEmpty = removePopupFromMap(this) val popupsAreEmpty = removePopupFromMap(this)
SwingActiveRender.removeActiveRender(notifyCanvas) SwingActiveRender.removeActiveRender(notifyCanvas)
if (popupsAreEmpty) { 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 * 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) { if (visible) {
println("add post")
SwingActiveRender.addActiveRender(notifyCanvas) SwingActiveRender.addActiveRender(notifyCanvas)
// start if we have previously stopped the timer // start if we have previously stopped the timer
@ -231,7 +150,8 @@ internal class LookAndFeel(
animation.resetUpdateTime() animation.resetUpdateTime()
SwingActiveRender.addActiveRenderFrameStart(frameStartHandler) 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 @Volatile
internal var notifyPopup: NotifyType? = null internal var notifyPopup: NotifyType? = null
@Volatile @Volatile
@ -158,12 +160,24 @@ class Notify private constructor() {
@Volatile @Volatile
var title = "Notification" var title = "Notification"
set(value) {
field = value
notifyCanvas?.refresh()
}
@Volatile @Volatile
var text = "Lorem ipsum" var text = "Lorem ipsum"
set(value) {
field = value
notifyCanvas?.refresh()
}
@Volatile @Volatile
var theme = Theme.defaultLight var theme = Theme.defaultLight
set(value) {
field = value
notifyCanvas?.refresh()
}
@Volatile @Volatile
var position = Position.BOTTOM_RIGHT var position = Position.BOTTOM_RIGHT
@ -176,12 +190,20 @@ class Notify private constructor() {
*/ */
@Volatile @Volatile
var hideCloseButton = false var hideCloseButton = false
set(value) {
field = value
notifyCanvas?.refresh()
}
@Volatile @Volatile
var screen = Short.MIN_VALUE.toInt() var screen = Short.MIN_VALUE.toInt()
@Volatile @Volatile
var image: ImageIcon? = null var image: ImageIcon? = null
set(value) {
field = value
notifyCanvas?.refresh()
}
/** /**
* Called when the notification is closed, either via close button or via close() * Called when the notification is closed, either via close button or via close()
@ -195,9 +217,6 @@ class Notify private constructor() {
@Volatile @Volatile
var onClickAction: Notify.()->Unit = {} var onClickAction: Notify.()->Unit = {}
@Volatile
var name = DIALOG_ERROR
@Volatile @Volatile
var shakeDurationInMillis = 0 var shakeDurationInMillis = 0
@ -293,6 +312,7 @@ class Notify private constructor() {
*/ */
fun theme(theme: Theme): Notify { fun theme(theme: Theme): Notify {
this.theme = theme this.theme = theme
notifyCanvas?.refresh()
return this return this
} }
@ -301,6 +321,7 @@ class Notify private constructor() {
*/ */
fun hideCloseButton(): Notify { fun hideCloseButton(): Notify {
hideCloseButton = true hideCloseButton = true
notifyCanvas?.refresh()
return this return this
} }
@ -308,7 +329,7 @@ class Notify private constructor() {
* Shows the notification with the built-in 'warning' image. * Shows the notification with the built-in 'warning' image.
*/ */
fun showWarning() { fun showWarning() {
name = DIALOG_WARNING title = DIALOG_WARNING
image = getImage(DIALOG_WARNING) image = getImage(DIALOG_WARNING)
show() show()
} }
@ -317,7 +338,7 @@ class Notify private constructor() {
* Shows the notification with the built-in 'information' image. * Shows the notification with the built-in 'information' image.
*/ */
fun showInformation() { fun showInformation() {
name = DIALOG_INFORMATION title = "Information"
image = getImage(DIALOG_INFORMATION) image = getImage(DIALOG_INFORMATION)
show() show()
} }
@ -326,7 +347,7 @@ class Notify private constructor() {
* Shows the notification with the built-in 'error' image. * Shows the notification with the built-in 'error' image.
*/ */
fun showError() { fun showError() {
name = DIALOG_ERROR title = "Error"
image = getImage(DIALOG_ERROR) image = getImage(DIALOG_ERROR)
show() show()
} }
@ -335,7 +356,7 @@ class Notify private constructor() {
* Shows the notification with the built-in 'confirm' image. * Shows the notification with the built-in 'confirm' image.
*/ */
fun showConfirm() { fun showConfirm() {
name = DIALOG_CONFIRM title = "Confirm"
image = getImage(DIALOG_CONFIRM) image = getImage(DIALOG_CONFIRM)
show() show()
} }
@ -349,6 +370,10 @@ class Notify private constructor() {
// must be done in the swing EDT // must be done in the swing EDT
SwingUtil.invokeAndWaitQuietly { SwingUtil.invokeAndWaitQuietly {
if (notify.notifyCanvas != null) {
return@invokeAndWaitQuietly
}
val window = notify.attachedFrame val window = notify.attachedFrame
val shakeDuration = notify.shakeDurationInMillis val shakeDuration = notify.shakeDurationInMillis
val shakeAmp = notify.shakeAmplitude val shakeAmp = notify.shakeAmplitude
@ -360,10 +385,10 @@ class Notify private constructor() {
if (window == null) { if (window == null) {
notifyPopup = AsDesktop(notify, notifyCanvas) notifyPopup = AsDesktop(notify, notifyCanvas)
look = LookAndFeel(notifyPopup, notifyCanvas, notify, LAFUtil.getGraphics(notify.screen), true) look = AsDesktopLAF(notify, notifyCanvas, notifyPopup, LAFUtil.getGraphics(notify.screen))
} else { } else {
notifyPopup = AsApplication(notify, notifyCanvas) notifyPopup = AsApplication(notify, notifyCanvas)
look = LookAndFeel(window, notifyCanvas, notify, window.bounds, false) look = AsApplicationLAF(notify, notifyCanvas, window, window.bounds)
} }
notifyPopup.setVisible(true, look) notifyPopup.setVisible(true, look)
@ -372,6 +397,7 @@ class Notify private constructor() {
look.shake(shakeDuration, shakeAmp) look.shake(shakeDuration, shakeAmp)
} }
notify.notifyCanvas = notifyCanvas
notify.notifyPopup = notifyPopup notify.notifyPopup = notifyPopup
notify.notifyLook = look notify.notifyLook = look
} }
@ -431,12 +457,20 @@ class Notify private constructor() {
// called when this notification is closed. called in the swing EDT! // called when this notification is closed. called in the swing EDT!
internal fun onClose() { internal fun onClose() {
this.notifyPopup!!.close() this.notifyPopup!!.close()
this.notifyLook!!.close()
this.onCloseAction.invoke(this) this.onCloseAction.invoke(this)
notifyPopup = null notifyPopup = null
notifyLook = null notifyLook = null
notifyCanvas = null
} }
internal fun onClickAction() { internal fun onClickAction() {
this.onClickAction.invoke(this) this.onClickAction.invoke(this)
} }
internal fun doLayoutForApp() {
notifyLook?.reLayout(attachedFrame!!.bounds)
}
} }

View File

@ -48,11 +48,14 @@ internal class NotifyCanvas(
isFocusable = false isFocusable = false
background = theme.panel_BG background = theme.panel_BG
// now we setup the rendering of the image // now we setup the rendering of the image
cachedImage = renderBackgroundInfo(notification.title, notification.text, theme, imageIcon) cachedImage = renderBackgroundInfo(notification.title, notification.text, theme, imageIcon)
} }
fun refresh() {
cachedImage = renderBackgroundInfo(notification.title, notification.text, theme, imageIcon)
}
override fun paint(g: Graphics) { override fun paint(g: Graphics) {
// we cache the text + image (to another image), and then always render the close + progressbar // we cache the text + image (to another image), and then always render the close + progressbar
@ -111,6 +114,13 @@ internal class NotifyCanvas(
// draw the progress bar along the bottom // draw the progress bar along the bottom
g2.color = theme.progress_FG g2.color = theme.progress_FG
g2.fillRect(0, PROGRESS_HEIGHT, progress, 2) g2.fillRect(0, PROGRESS_HEIGHT, progress, 2)
// if (notification.title == "Notify title 0") {
// println("asd")
// Toolkit.getDefaultToolkit().sync()
// }
} catch (e: Exception) {
e.printStackTrace()
} finally { } finally {
g2.dispose() g2.dispose()
} }
@ -148,7 +158,7 @@ internal class NotifyCanvas(
const val WIDTH = 300 const val WIDTH = 300
const val HEIGHT = 87 const val HEIGHT = 87
private const val PROGRESS_HEIGHT = HEIGHT - 2 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 image = BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB)
val g2 = image.createGraphics() val g2 = image.createGraphics()
@ -185,17 +195,17 @@ internal class NotifyCanvas(
} }
// Draw the main text // Draw the main text
var length = notificationText.length var length = textBody.length
val text = StringBuilder(length) val text = StringBuilder(length)
// are we "html" already? just check for the starting tag and strip off END html tag // are we "html" already? just check for the starting tag and strip off END html tag
if (length >= 13 && notificationText.regionMatches(length - 7, "</html>", 0, 7, ignoreCase = true)) { if (length >= 13 && textBody.regionMatches(length - 7, "</html>", 0, 7, ignoreCase = true)) {
text.append(notificationText) text.append(textBody)
text.delete(text.length - 7, text.length) text.delete(text.length - 7, text.length)
length -= 7 length -= 7
} else { } else {
text.append("<html>") text.append("<html>")
text.append(notificationText) text.append(textBody)
} }
// make sure the text is the correct length // make sure the text is the correct length
@ -218,6 +228,7 @@ internal class NotifyCanvas(
} finally { } finally {
g2.dispose() g2.dispose()
} }
return image return image
} }
} }

View File

@ -18,7 +18,7 @@ package dorkbox.notify
import java.awt.event.WindowAdapter import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent import java.awt.event.WindowEvent
internal class WindowAdapter : WindowAdapter() { internal class WindowCloseAdapter : WindowAdapter() {
override fun windowClosing(e: WindowEvent) { override fun windowClosing(e: WindowEvent) {
if (e.newState != WindowEvent.WINDOW_CLOSED) { if (e.newState != WindowEvent.WINDOW_CLOSED) {
val source = e.source as AsDesktop val source = e.source as AsDesktop

View File

@ -51,8 +51,8 @@ object NotifyTest {
// bottomRightInFrame(3, frame) // bottomRightInFrame(3, frame)
// topLeftInFrame(3, frame) // topLeftInFrame(3, frame)
react() // react()
// topRightMonitor(3) topRightMonitor(2)
// bottomLeftScaled(3, frame, image) // bottomLeftScaled(3, frame, image)
// bottomLeftStacking(3, frame, image) // bottomLeftStacking(3, frame, image)
} }
@ -70,7 +70,7 @@ object NotifyTest {
.shake(4300, 10) .shake(4300, 10)
// .hideCloseButton() // if the hideButton is visible, then it's possible to change things when clicked // .hideCloseButton() // if the hideButton is visible, then it's possible to change things when clicked
.onClickAction { .onClickAction {
notify.text = "HOWDY" text = "HOWDY"
System.err.println("Notification clicked on!") System.err.println("Notification clicked on!")
} }
notify.show() notify.show()
@ -81,7 +81,7 @@ object NotifyTest {
} }
} }
fun topRightMonitor(count: Int) { private fun topRightMonitor(count: Int) {
var notify: Notify var notify: Notify
for (i in 0 until count) { for (i in 0 until count) {
@ -89,17 +89,17 @@ object NotifyTest {
.title("Notify title $i") .title("Notify title $i")
.text("This is a notification " + i + " popup message This is a notification popup message This is a " + .text("This is a notification " + i + " popup message This is a notification popup message This is a " +
"notification popup message") "notification popup message")
.hideAfter(13000) .hideAfter(130000)
.position(Position.TOP_RIGHT) .position(Position.TOP_RIGHT)
// .setScreen(0) // .setScreen(0)
.theme(Theme.defaultDark) .theme(Theme.defaultDark)
// .shake(1300, 4) // .shake(1300, 4)
.shake(4300, 10) // .shake(4300, 10)
.hideCloseButton() // .hideCloseButton()
.onClickAction { System.err.println("Notification $i clicked on!") } .onClickAction { System.err.println("Notification $i clicked on!") }
notify.show() notify.show()
try { try {
Thread.sleep(3000) Thread.sleep(2000)
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
e.printStackTrace() e.printStackTrace()
} }