From b67eea2b7a1e18668f1b1db168d0525b847b9d55 Mon Sep 17 00:00:00 2001 From: Robinson Date: Tue, 14 Feb 2023 13:05:06 +0100 Subject: [PATCH] Fixed issues with shake --- src/dorkbox/notify/AppAccessor.kt | 6 +- src/dorkbox/notify/AppNotify.kt | 98 +++++++------ src/dorkbox/notify/DesktopAccessor.kt | 6 +- src/dorkbox/notify/DesktopNotify.kt | 202 ++++++++++++++------------ src/dorkbox/notify/NotifyType.kt | 2 - 5 files changed, 162 insertions(+), 152 deletions(-) diff --git a/src/dorkbox/notify/AppAccessor.kt b/src/dorkbox/notify/AppAccessor.kt index 172640f..6c3b523 100644 --- a/src/dorkbox/notify/AppAccessor.kt +++ b/src/dorkbox/notify/AppAccessor.kt @@ -45,8 +45,8 @@ internal class AppAccessor : TweenAccessor { } SHAKE -> { - returnValues[0] = target.x.toFloat() - returnValues[1] = target.y.toFloat() + returnValues[0] = target.shakeX.toFloat() + returnValues[1] = target.shakeY.toFloat() return 2 } @@ -76,7 +76,7 @@ internal class AppAccessor : TweenAccessor { } SHAKE -> { - target.setLocationInternal(newValues[0].toInt(), newValues[1].toInt()) + target.setLocationShake(newValues[0].toInt(), newValues[1].toInt()) return } diff --git a/src/dorkbox/notify/AppNotify.kt b/src/dorkbox/notify/AppNotify.kt index 5e4fb8b..8fb6d9e 100755 --- a/src/dorkbox/notify/AppNotify.kt +++ b/src/dorkbox/notify/AppNotify.kt @@ -72,10 +72,12 @@ internal class AppNotify(override val notification: Notify): Canvas(), NotifyTyp // for the progress bar. we directly draw this onscreen // non-volatile because it's always accessed in the active render thread + private var prevProgress = 0 override var progress = 0 - - @Volatile - var mouseOver = false + set(value) { + prevProgress = field + field = value + } override var shakeTween: Tween? = null override var moveTween: Tween? = null @@ -88,6 +90,11 @@ internal class AppNotify(override val notification: Notify): Canvas(), NotifyTyp override var anchorX = 0 override var anchorY = 0 + // the ONLY reason "shake" works, is because we configure the target X/Y location for moving based on where we WANT the popup to go + // and completely ignoring its current position + var shakeX = 0 + var shakeY = 0 + @Volatile var mouseY = 0 @@ -99,6 +106,9 @@ internal class AppNotify(override val notification: Notify): Canvas(), NotifyTyp // this is on the swing EDT init { + addMouseListener(mouseListener) + addMouseMotionListener(mouseListener) + val actualSize = Dimension(Notify.WIDTH, Notify.HEIGHT) preferredSize = actualSize @@ -110,10 +120,6 @@ internal class AppNotify(override val notification: Notify): Canvas(), NotifyTyp background = notification.theme.panel_BG - addMouseListener(mouseListener) - addMouseMotionListener(mouseListener) - - idAndPosition = parent.name + ":" + notification.position anchorX = getAnchorX(notification.position, parent.bounds) @@ -152,14 +158,6 @@ internal class AppNotify(override val notification: Notify): Canvas(), NotifyTyp refresh() } - override fun getX(): Int { - return super.getX() - } - - override fun getY(): Int { - return super.getY() - } - override fun refresh() { cachedImage = renderBackgroundInfo(notification.title, notification.text, notification.theme, notification.image) cachedClose = renderCloseButton(notification.theme, false) @@ -173,16 +171,8 @@ internal class AppNotify(override val notification: Notify): Canvas(), NotifyTyp override fun paint(g: Graphics) { // we cache the text + image (to an image), the two stats of the close "button" 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) - - if (mouseOver && mouseX >= 280 && mouseY <= 20) { - g.drawImage(cachedCloseEnabled, 0, 0, null) - } else { - g.drawImage(cachedClose, 0, 0, null) - } + draw(g) } 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) @@ -203,25 +193,44 @@ internal class AppNotify(override val notification: Notify): Canvas(), NotifyTyp // try to draw again try { - g.drawImage(cachedImage, 0, 0, null) - - if (mouseOver && mouseX >= NotifyType.closeX && mouseY <= 20) { - g.drawImage(cachedCloseEnabled, 0, 0, null) - } else { - g.drawImage(cachedClose, 0, 0, null) - } + draw(g) } catch (ignored2: Exception) { } } // the progress bar can change, so we always draw it every time - if (progress > 0) { + if (progress > 0 && prevProgress != progress) { // draw the progress bar along the bottom g.color = notification.theme.progress_FG g.fillRect(0, Notify.HEIGHT - 2, progress, 2) } } + private fun draw(g: Graphics) { + g.drawImage(cachedImage, 0, 0, null) + + if (!notification.hideCloseButton) { + if (mouseX >= 280 && mouseY <= 20) { + g.drawImage(cachedCloseEnabled, 0, 0, null) + } else { + g.drawImage(cachedClose, 0, 0, null) + } + } + } + + fun onClick(x: Int, y: Int) { + // Check - we were over the 'X' (and thus no notify), or was it in the general area? + val isClickOnCloseButton = !notification.hideCloseButton && x >= 280 && y <= 20 + + if (isClickOnCloseButton) { + // we always close the notification popup + notification.onClose() + } else { + // only call the general click handler IF we click in the general area! + notification.onClickAction() + } + } + override fun setupHide() { if (hideTween == null && notification.hideAfterDurationInMillis > 0) { // begin a timeline to get rid of the popup (default is 5 seconds) @@ -259,10 +268,10 @@ internal class AppNotify(override val notification: Notify): Canvas(), NotifyTyp override fun doShake(count: Int, targetX: Float, targetY: Float) { if (shakeTween != null) { shakeTween!!.valueRelative(targetX, targetY) - .repeatAutoReverse(count, 0f) + .repeatAutoReverse(count, 0f) } else { val tween = tweenEngine - .to(this, AppAccessor.X_Y_POS, tweenAccessor, 0.05f) + .to(this, AppAccessor.SHAKE, tweenAccessor, 0.05f) .valueRelative(targetX, targetY) .repeatAutoReverse(count, 0f) .ease(TweenEquations.Linear) @@ -272,19 +281,12 @@ internal class AppNotify(override val notification: Notify): Canvas(), NotifyTyp } } - fun onClick(x: Int, y: Int) { - // Check - we were over the 'X' (and thus no notify), or was it in the general area? - - val isClickOnCloseButton = !notification.hideCloseButton && x >= 280 && y <= 20 - - // reasonable position for detecting mouse over - if (isClickOnCloseButton) { - // we always close the notification popup - notification.onClose() - } else { - // only call the general click handler IF we click in the general area! - notification.onClickAction() - } + fun setLocationShake(x: Int, y: Int) { + val x1 = getX() - shakeX + val y1 = getY() - shakeY + shakeX = x + shakeY = y + setLocationInternal(x1 + x, y1 + y) } override fun setLocationInternal(x: Int, y: Int) { @@ -292,6 +294,7 @@ internal class AppNotify(override val notification: Notify): Canvas(), NotifyTyp } // this is called during parent initialization (before we are initialized), so we cannot access objects here properly! + // NOTE: This is present here because DesktopNotify requires it (and consistency is important) // override fun setLocation(x: Int, y: Int) { // super.setLocation(x, y) // } @@ -302,7 +305,6 @@ internal class AppNotify(override val notification: Notify): Canvas(), NotifyTyp updatePositionsPost(this, this, visible) } - // called on the Swing EDT. override fun close() { cancelMove() diff --git a/src/dorkbox/notify/DesktopAccessor.kt b/src/dorkbox/notify/DesktopAccessor.kt index 1c86881..a93a725 100644 --- a/src/dorkbox/notify/DesktopAccessor.kt +++ b/src/dorkbox/notify/DesktopAccessor.kt @@ -45,8 +45,8 @@ internal class DesktopAccessor : TweenAccessor { } SHAKE -> { - returnValues[0] = target.x.toFloat() - returnValues[1] = target.y.toFloat() + returnValues[0] = target.shakeX.toFloat() + returnValues[1] = target.shakeY.toFloat() return 2 } @@ -76,7 +76,7 @@ internal class DesktopAccessor : TweenAccessor { } SHAKE -> { - target.setLocationInternal(newValues[0].toInt(), newValues[1].toInt()) + target.setLocationShake(newValues[0].toInt(), newValues[1].toInt()) return } diff --git a/src/dorkbox/notify/DesktopNotify.kt b/src/dorkbox/notify/DesktopNotify.kt index 0bd8cd4..1637c94 100755 --- a/src/dorkbox/notify/DesktopNotify.kt +++ b/src/dorkbox/notify/DesktopNotify.kt @@ -71,14 +71,16 @@ internal class DesktopNotify(override val notification: Notify) : JWindow(), Not // for the progress bar. we directly draw this onscreen // non-volatile because it's always accessed in the active render thread + private var prevProgress = 0 override var progress = 0 + set(value) { + prevProgress = field + field = value + } // The button is "hittable" from the entire corner private val closeButton: Rectangle - @Volatile - var mouseOver = false - override var shakeTween: Tween? = null override var moveTween: Tween? = null override var hideTween: Tween? = null @@ -90,6 +92,11 @@ internal class DesktopNotify(override val notification: Notify) : JWindow(), Not override var anchorX = 0 override var anchorY = 0 + // the ONLY reason "shake" works, is because we configure the target X/Y location for moving based on where we WANT the popup to go + // and completely ignoring its current position + var shakeX = 0 + var shakeY = 0 + // this is on the swing EDT init { @@ -115,14 +122,6 @@ internal class DesktopNotify(override val notification: Notify) : JWindow(), Not refresh() } - override fun getX(): Int { - return super.getX() - } - - override fun getY(): Int { - return super.getY() - } - override fun refresh() { cachedImage = renderBackgroundInfo(notification.title, notification.text, notification.theme, notification.image) cachedClose = renderCloseButton(notification.theme, false) @@ -138,8 +137,92 @@ internal class DesktopNotify(override val notification: Notify) : JWindow(), Not anchorY = getAnchorY(notification.position, bounds) + calculateOffset(growDown, point) } + /** + * have to adjust for offsets when the window-manager has a toolbar that consumes space and prevents overlap. + */ + private fun calculateOffset(showFromTop: Boolean, point: Point): Int { + val gc = ScreenUtil.getMonitorAtLocation(point).defaultConfiguration + val screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc) + + if (showFromTop) { + if (screenInsets.top > 0) { + return screenInsets.top - Notify.MARGIN + } + } else { + if (screenInsets.bottom > 0) { + return screenInsets.bottom + Notify.MARGIN + } + } + + return 0 + } + + override fun paint(g: Graphics) { + // we cache the text + image (to an image), the two states of the close "button" and then always render the progressbar + try { + draw(g) + } 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) + // at sun.java2d.SurfaceDataProxy.updateSurfaceData(SurfaceDataProxy.java:498) + // at sun.java2d.SurfaceDataProxy.replaceData(SurfaceDataProxy.java:455) + // at sun.java2d.SurfaceData.getSourceSurfaceData(SurfaceData.java:233) + // at sun.java2d.pipe.DrawImage.renderImageCopy(DrawImage.java:566) + // at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:67) + // at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:1014) + // at sun.java2d.pipe.ValidatePipe.copyImage(ValidatePipe.java:186) + // at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3318) + // at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3296) + // at dorkbox.notify.NotifyCanvas.paint(NotifyCanvas.java:92) + + // redo the cache + refresh() + + // try to draw again + try { + draw(g) + } catch (ignored2: Exception) { + } + } + + // the progress bar can change (only getting bigger!), so we always draw it when it grows + if (progress > 0 && prevProgress != progress) { + // draw the progress bar along the bottom + g.color = notification.theme.progress_FG + g.fillRect(0, Notify.HEIGHT - 2, progress, 2) + } + } + + private fun draw(g: Graphics) { + g.drawImage(cachedImage, 0, 0, null) + + if (!notification.hideCloseButton) { + if (closeButton.contains(MouseInfo.getPointerInfo().location)) { + g.drawImage(cachedCloseEnabled, 0, 0, null) + } else { + g.drawImage(cachedClose, 0, 0, null) + } + } + } + + fun onClick() { + // Check - we were over the 'X' (and thus no notify), or was it in the general area + val isClickOnCloseButton = !notification.hideCloseButton && closeButton.contains(MouseInfo.getPointerInfo().location) + + if (isClickOnCloseButton) { + // we always close the notification popup + notification.onClose() + } else { + // only call the general click handler IF we click in the general area! + notification.onClickAction() + } + } + override fun setupHide() { - if (hideTween == null && notification.hideAfterDurationInMillis > 0) { + if (hideTween != null) { + hideTween!!.value(Notify.WIDTH.toFloat()) + } else if (notification.hideAfterDurationInMillis > 0) { // begin a timeline to get rid of the popup (default is 5 seconds) val tween = tweenEngine .to(this, DesktopAccessor.PROGRESS, tweenAccessor, notification.hideAfterDurationInMillis / 1000.0f) @@ -174,12 +257,12 @@ internal class DesktopNotify(override val notification: Notify) : JWindow(), Not override fun doShake(count: Int, targetX: Float, targetY: Float) { if (shakeTween != null) { - shakeTween!!.valueRelative(targetX, targetY) + shakeTween!!.value(targetX, targetY) .repeatAutoReverse(count, 0f) } else { val tween = tweenEngine - .to(this, DesktopAccessor.X_Y_POS, tweenAccessor, 0.05f) - .valueRelative(targetX, targetY) + .to(this, DesktopAccessor.SHAKE, tweenAccessor, 0.05f) + .value(targetX, targetY) .repeatAutoReverse(count, 0f) .ease(TweenEquations.Linear) @@ -188,85 +271,12 @@ internal class DesktopNotify(override val notification: Notify) : JWindow(), Not } } - override fun paint(g: Graphics) { - // we cache the text + image (to an image), the two stats of the close "button" 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) - - if (mouseOver && closeButton.contains(MouseInfo.getPointerInfo().location)) { - g.drawImage(cachedCloseEnabled, 0, 0, null) - } else { - g.drawImage(cachedClose, 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) - // at sun.java2d.SurfaceDataProxy.updateSurfaceData(SurfaceDataProxy.java:498) - // at sun.java2d.SurfaceDataProxy.replaceData(SurfaceDataProxy.java:455) - // at sun.java2d.SurfaceData.getSourceSurfaceData(SurfaceData.java:233) - // at sun.java2d.pipe.DrawImage.renderImageCopy(DrawImage.java:566) - // at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:67) - // at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:1014) - // at sun.java2d.pipe.ValidatePipe.copyImage(ValidatePipe.java:186) - // at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3318) - // at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3296) - // at dorkbox.notify.NotifyCanvas.paint(NotifyCanvas.java:92) - - // redo the cache - refresh() - - // try to draw again - try { - g.drawImage(cachedImage, 0, 0, null) - - if (mouseOver && closeButton.contains(MouseInfo.getPointerInfo().location)) { - g.drawImage(cachedCloseEnabled, 0, 0, null) - } else { - g.drawImage(cachedClose, 0, 0, null) - } - } catch (ignored2: Exception) { - } - } - - // the progress bar can change, so we always draw it every time - if (progress > 0) { - // draw the progress bar along the bottom - g.color = notification.theme.progress_FG - g.fillRect(0, Notify.HEIGHT - 2, progress, 2) - } - } - fun onClick() { - // Check - we were over the 'X', or was it in the general area? - if (mouseOver && closeButton.contains(MouseInfo.getPointerInfo().location)) { - // we always close the notification popup - notification.onClose() - } else { - // only call the general click handler IF we click in the general area! - notification.onClickAction() - } - } - - /** - * have to adjust for offsets when the window-manager has a toolbar that consumes space and prevents overlap. - */ - private fun calculateOffset(showFromTop: Boolean, point: Point): Int { - val gc = ScreenUtil.getMonitorAtLocation(point).defaultConfiguration - val screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc) - - if (showFromTop) { - if (screenInsets.top > 0) { - return screenInsets.top - Notify.MARGIN - } - } else { - if (screenInsets.bottom > 0) { - return screenInsets.bottom + Notify.MARGIN - } - } - - return 0 + fun setLocationShake(x: Int, y: Int) { + val x1 = getX() - shakeX + val y1 = getY() - shakeY + shakeX = x + shakeY = y + setLocationInternal(x1 + x, y1 + y) } override fun setLocationInternal(x: Int, y: Int) { @@ -301,14 +311,14 @@ internal class DesktopNotify(override val notification: Notify) : JWindow(), Not cancelHide() cancelShake() - super.setVisible(false) - removeWindowListener(windowCloseListener) removeMouseMotionListener(mouseListener) removeMouseListener(mouseListener) updatePositionsPre(component = this, notify = this, visible = false) + super.setVisible(false) + removeAll() dispose() } diff --git a/src/dorkbox/notify/NotifyType.kt b/src/dorkbox/notify/NotifyType.kt index 7500e60..d79fe23 100755 --- a/src/dorkbox/notify/NotifyType.kt +++ b/src/dorkbox/notify/NotifyType.kt @@ -53,8 +53,6 @@ internal interface NotifyType { var anchorX: Int var anchorY: Int - fun getX(): Int - fun getY(): Int // for the progress bar. we directly draw this onscreen // non-volatile because it's always accessed in the active render thread