Fixed issues with shake

This commit is contained in:
Robinson 2023-02-14 13:05:06 +01:00
parent 097411136b
commit b67eea2b7a
No known key found for this signature in database
GPG Key ID: 8E7DB78588BD6F5C
5 changed files with 162 additions and 152 deletions

View File

@ -45,8 +45,8 @@ internal class AppAccessor : TweenAccessor<AppNotify> {
}
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<AppNotify> {
}
SHAKE -> {
target.setLocationInternal(newValues[0].toInt(), newValues[1].toInt())
target.setLocationShake(newValues[0].toInt(), newValues[1].toInt())
return
}

View File

@ -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<AppNotify>? = null
override var moveTween: Tween<AppNotify>? = 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()

View File

@ -45,8 +45,8 @@ internal class DesktopAccessor : TweenAccessor<DesktopNotify> {
}
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<DesktopNotify> {
}
SHAKE -> {
target.setLocationInternal(newValues[0].toInt(), newValues[1].toInt())
target.setLocationShake(newValues[0].toInt(), newValues[1].toInt())
return
}

View File

@ -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<DesktopNotify>? = null
override var moveTween: Tween<DesktopNotify>? = null
override var hideTween: Tween<DesktopNotify>? = 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()
}

View File

@ -53,8 +53,6 @@ internal interface NotifyType<T> {
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