Reputation: 524
I recently discovered, that my scala swing applications behave very strangely. The following test application draws a line which moves over the screen. When I ran the program the first time I was shocked on how laggy swing seems to be, since the line doesnt move smoothely in the least. BUT as soon as swing seems to recognize events, triggered by for example a mouse-hover or pressed keys, everything seems to run smoothly and as expected. However as soon as the mouse leaves the swing window,or no more keys are pressed swing is laggy again. I am not working on a slow machine and since I dont have similar problems with python or so I still think that the scala.swing library seems to very bad, to me. Is there a major mistake I am not seeing here? Am I using scala.swing incorrectly? What is it, that makes scala.swing so laggy but only ever when there are no events triggered by the user?
Here is the very small test app I have created. Please copy it and try it yourself.
object PerformanceTest {
def main(args:Array[String]): Unit ={
var i =0
val (x1, y1) = (0,0)
val (x2, y2) = (400,300)
val frame = new MainFrame{
title = "performance test"
centerOnScreen()
contents = new BorderPanel{
layout += new Panel{
override def paint(g:Graphics2D): Unit ={
g.drawLine(x1+i, y1,x2-i, y2)
}
} -> BorderPanel.Position.Center
listenTo()
}
size = new Dimension(400, 300)
visible = true
}
while(true){
frame.repaint()
i += 1
Thread.sleep(20)
}
}
}
Upvotes: 1
Views: 76
Reputation: 67280
Are you by any chance seeing this on Linux? I have discovered a refresh bug in the JDK Linux recently that sounds like the same thing. See this question.
So what I did is call .toolkit.sync()
. This is kind of horrible because it syncs the entire windowing system as far as I understand, while you actually just want to sync one individual component. But I haven't found any better workaround yet.
Also note that you should probably just refresh the panel and not the frame. Then you'll have to fill the background rectangle or declare it non-opaque. You should also not create the frame outside of the event dispatch thread. Instead of waiting on the main thread with Thread.sleep
, a better approach is to use a Swing timer. With these changes I get:
object PerformanceTest {
import scala.swing._
def main(args: Array[String]): Unit = Swing.onEDT /* ! */ {
var i = 0
val (x1, y1) = (0,0)
val (x2, y2) = (400,300)
val panel = new Panel {
opaque = false // !
override def paint(g: Graphics2D): Unit =
g.drawLine(x1 + i, y1, x2 - i, y2)
}
lazy val frame = new Frame {
title = "performance test"
contents = new BorderPanel {
layout += panel -> BorderPanel.Position.Center
}
size = new Dimension(400, 300)
centerOnScreen()
override def closeOperation(): Unit = {
timer.stop()
dispose()
}
}
lazy val timer: javax.swing.Timer = new javax.swing.Timer(20,
Swing.ActionListener { _ =>
i = (i + 1) % 400
panel.repaint()
panel.toolkit.sync() // linux bug?
}
)
timer.start()
frame.open()
}
}
PerformanceTest.main(null) // test
Upvotes: 2