Using JLabel for text on custom drawn component causes painting bug - java

My little project involves a diagram editing tool, in which different sized boxes can be linked together. The drawing requirement for this is complex to say the least. You see the idea is that one can zoom in/out as far as they like and create ever smaller/larger boxes. This is sort of like a Mandelbrot, but as a diagram tool. This is shown in the image below.
These boxes have text in them and my overall aim was for this text to be centered in the absolute center of the box. The only simple way I know for text to both center and wrap is by using JLabels. But when I tried this I came across a strange bug. The labels with multiple lines were prone to "disalign" vertically every other time they were repainted. (Sometimes this happened, other times it didn't. It has some consistency in that it keeps its behaviour when I replicate the same movement and positioning). This bug is shown in the image below.
Depending on the position, it would effectively alternate between the two images above. I have isolated the drawing of each box and ensured there was no escaping graphics transformations, and it seems this problem is down to the label painting.
I am aware that the practices I've used may not be preferable (e.g. painting JLabel directly), however I struggle to find any decent alternatives. If you guys can come up with a good alternative to using JLabels for centering and word wrap I'm happy to try it. Any ideas are welcome really.
Below is the drawing code for a Box. The colors are temporary.
public void draw(Graphics2D g, Map map, int fillAlpha, int lineAlpha, double zoom)
{
if(this == map.selectedBox)
{
if(this == map.highlightedBox)
g.setColor(new Color(200,235,235,fillAlpha));
else
g.setColor(new Color(200,225,225,fillAlpha));
}
else
{
if(this == map.highlightedBox)
g.setColor(new Color(235,235,200,fillAlpha));
else
g.setColor(new Color(225,225,200,fillAlpha));
}
g.fill(rect);
if(this == map.selectedBox)
g.setColor(new Color(0,0,255,lineAlpha));
else
g.setColor(new Color(0,0,0,lineAlpha));
g.draw(rect);
g.translate(rect.x, rect.y);
label.setForeground(g.getColor());
label.paint(g);
g.translate(-rect.x, -rect.y);
}
Below is the drawing code for the whole diagram.
public void draw(Graphics2D g, int layer, Color aboveColor)
{
for(Link link : linksObj(layer))
{
g.setColor(Color.black);
link.draw(g, this, aboveColor);
}
AffineTransform at = g.getTransform();
g.scale(getZoom(layer), getZoom(layer));
double ratio = getRatio(layer);
int fill = getAnimAlpha(255, 0, ratio);
int line = getAnimAlpha(255, 32, ratio);
for(Box box : boxesObj(layer))
box.draw(g, this, fill, line, getZoom(layer));
g.setTransform(at);
}
The label uses the following to center the text.
public String convertForLabel(String in)
{
return "<html><div style=\"text-align: center;\">"+in.replaceAll("\n", "<br>")+"</html>";
}
Edit: I have created a test project for you to see the problem yourself. It is only one class so I just uploaded that. You can find it here: JLabel Bug Class

Related

libGDX problem with getWidth() and getHeight() after resizing

Problem recorded and uploaded to Youtube
libGDX resizing problem
I am starting out with libGDX and wanted to make a game.
However... when I was just experimenting around... this happened.
I don't know why it does that or what I should do to eliminate this.
I don't know what to google either. It's almost as if libGDX's default width and height methods doesn't update correctly when the screen is being resized.
Am I doing something wrong here?
Code :
public class Metroidvania extends ApplicationAdapter {
ShapeRenderer s;
#Override
public void create () {
s = new ShapeRenderer();
}
#Override
public void render () {
ScreenUtils.clear(1, 1, 1, 1);
s.begin(ShapeRenderer.ShapeType.Filled);
s.setColor(0, 0, 0, 1);
s.rect(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
s.end();
}
#Override
public void dispose () {
s.dispose();
}
}
The video is currently stuck at 97% uploaded... Even Youtube is against me now
Basically, The code is supposed to cover the entire screen in black... even when resized. But here, when resised Gdx.graphics.getWidth() and Height() returns wrong values, causing white spaces to show.
The rect()'s shape depends on which direction you resize in.
Resize the screen smaller and the rect() become's smaller at R times the rate of resizing (R is unknown). Vice versa for reisizing it larger.
LibGDX usually uses a virtual screen and viewports i.e. you render to a different width and height than are the real ones in pixel terms.
Maybe have a look here Libgdx Window Resizing: Keeping Aspect Ratio as there is a resize method you can hook into to dynamically update your own coordinate system.

Repaint all components of a JFrame/JPanel

I'm working on some animation where I have a certain number of dots wandering around my JFrame and based on their distance they should be connected by lines of different strengths.
The base code for moving the dots works and actually I also had them displayed correctly in the beggining but I had some issues where the movement was stuttering (probably due to the repaint process). At that point the Window class handled the entire repaint procedure.
After reading some posts around here I adapted my code according to the github page linked in this post to use the individual Dots as JComponents and have them being repainted individually. However, now the problem is that although I still have 100 Dots as components on my JPanel, only one of them is being painted (however, the stuttering is gone at least). I also see that all components are being iterated and their repaint method is being called but they just don't display.
This is my paintComponent method in the Dot class:
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setPaint(new Color(0, 0, 0));
Ellipse2D.Double circle = new Ellipse2D.Double(x - 10 / 2, y - 10 / 2, 10, 10);
g2d.fill(circle);
}
And this is what my repaintTimer looks like:
final Timer repaintTimer = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for(Component component : window.getContentPane().getComponents()) {
component.repaint();
}
recalculateDots();
}
});
repaintTimer.start();
The result I get is something like this:
I tried some things of which I thought that it could solve the problem but nothing really helped and I'm really confused as to why this is happening. I'd appreciate any help very much because this issue doesn't make any sense for me.

How can you create a grid in andengine?

I am interested in simple game programming with AndEngine. And I would like to do some basic round based strategy stuff. So I would like to have a top down view to some grid. Every square in this chess like grid should be clickable (touchable on android of course). But I can´t imagine drawing lines and squares is the best solution to do so. I would like to have some kind of abstract object list, which I can extend to different behaviours (one click triggers menu to open, on other field a click triggers an explosion, whatever...).
I have been searching for this in several engines over the years now. It is a hobby, but I would like to find a solution for it. Can anyone give me a hint about how this could be done in an elegant way? Double buffering, rendering and stuff isn´t that important. The important thing is, every square should be an object that gets painted.
Anyone? ;)
Regards.
Simplet solution would be something like that (lets say that your screen is 800x480 in portrait mode, and I assume that you have GLES2-AC andEngine branch).
in your onCreateScene() method:
Rectangle rect1 = new Rectangle(80, 133, 160, 266, engine.getVertexBufferObjectManager()){
#Override
public boolean onAreaTouched(final TouchEvent pSceneTouchEvent, final float pTouchAreaLocalX, final float pTouchAreaLocalY){
if(pSceneTouchEvent.getAction() == TouchEvent.ACTION_DOWN){
//here put what you want to be done after your button was pressed
}
}
Rectangle rect2 = new Rectangle(240, 133, 160, 266, engine.getVertexBufferObjectManager()){
#Override
public boolean onAreaTouched(final TouchEvent pSceneTouchEvent, final float pTouchAreaLocalX, final float pTouchAreaLocalY){
if(pSceneTouchEvent.getAction() == TouchEvent.ACTION_DOWN){
//here put what you want to be done after your button was pressed
}
}
and you continue 7 more times with changing first two numbers in new Rectangle(x, y....) which are coordinates of the center of rectangle(second pair of numbers is the height and wisth of the rectangle). (so bottomrow has coordinates: (80, 133), (240, 133) and (400, 133). Middle row: (80, 399), (240, 399) and (400, 399). Third row you can figure out. Of course it does not give you completely covered screen. This you can achieve by dividing your WIDTH and HEIGHT of the screen by certain numers und use it as a coordinates for rectangles. remember to attach all rectangles to your scene and add
this.setOnSceneTouchListener(getOnSceneTouchListener());
this.setOnSceneTouchListenerBindingOnActionDownEnabled(true);
and register touch area for all rectangles.
More difficult way would be to create class for example MyButton.class that extends rectangle or sprite class that has a methods that will be used when "button" is pressed. It depends on what you really need.

Translate/Rotate/Move a graphics object without messing up the whole screen

I'm coding a GUI that will be doing some graphics translations/rotations, etc.
My problem is that when I try to translate my graphics,
(a) The entire screen translates instead of my one little painted area
(b) The old paint stays there, leaving a big paint blob instead of a translated image
(c) If I use the clearRect method to allow me to avoid (b), the entire screen goes white and (a) is still a problem
my DrawPanel class (I called it "LaunchTubeImage" for whatever reason)
private class LaunchTubeImage extends JPanel {
private Color colour;
public LaunchTubeImage(Color color) {
super();
this.colour = color;
}
public void paintComponent(Graphics g) {
Graphics2D gg = (Graphics2D)g;
double theta = (double)angle.getValue();
theta = Math.toRadians(theta);
gg.rotate(theta,tubeImage.getSize().width/2 + 10,
tubeImage.getSize().height - 50);
g.setColor(colour);
g.clearRect(0,0,getWidth(),getHeight());
g.fillRect(tubeImage.getSize().width/2,
tubeImage.getSize().height - 100 , 10, 50);
}
}
where this is called in my code
tubeImage = new LaunchTubeImage(Color.MAGENTA);
angle.addChangeListener(new ChangeListener(){
public void stateChanged(ChangeEvent e) {
tubeImage.repaint();
}
});
Case 1: Comment out clearRect in that 1st block of code I posted
http://i58.tinypic.com/2d1l5w2_th.png
Black background as desired. Not rotated yet. Looks good so far.
http://oi60.tinypic.com/1zw1sm.jpg
Rotated it with my JSpinner... you see that the previous location was not removed (and note how my buttons randomly doubled and put themselves at the top of the screen).
Case 2: Keeping in the clearRect method
oi57.tinypic.com/2s84307.jpg
Layout is fine so far, but I wanted the background to be black
oi57.tinypic.com/4rde8x.jpg
Yay! It rotated. But note the weird behavior of that random "15" that appeared in my top right corner
oi58.tinypic.com/vymljm.jpg
And finally... when I resize the window you see that my entire screen was rotated - not just the pink image I wanted to rotate
Tips/fixes/advice? Thanks!! I hope I've provided enough information
(P.s. if you insist on us asking clear/useful questions.... then DON'T limit the number of images you can post... :/ )
The first line of an overridden paintComponent method should usually be super.paintComponent(g). On a JPanel, this will cause the drawing area to be cleared with the background color. If you want to clear the background with a different color, you can do this by manually filling a rectangle (clearRect is discouraged, see the JavaDoc), but of course, this has to be done before applying any transform.
So your method should probably look like this:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(colour);
g.fillRect(0,0,getWidth(),getHeight());
Graphics2D gg = (Graphics2D)g;
double theta = (double)angle.getValue();
theta = Math.toRadians(theta);
gg.rotate(theta,tubeImage.getSize().width/2 + 10,tubeImage.getSize().height - 50);
gg.fillRect(tubeImage.getSize().width/2,tubeImage.getSize().height - 100 , 10, 50);
}

How can I repaint efficiently when using big custom component in Swing?

I have made a custom component (derived from JComponent) which represents
a draggable Bezier-curve.
(looks like a hanging cable, someone might know it
from Bender or Cubase)
My problem is: The curve may become really long,
let's say from top left to bottom right corners of the desktop.
This makes Swing's repaint functionality inefficient:
The area of the curve is perhaps few hundred pixels, but the area of
the component (being mostly 'transparent') is millions of pixels big.
My subjection impression is:
The longer the curve, the more flicker I get when dragging it.
I hope I made myself clear about the problem.
Perhaps it would help when I somehow could choose by myself, which regions
of the component needs repainting at all.
EDIT:
Such a mess! I'm profiling the application using Netbeans, which helps to
find inefficient code normally, but this Swing framework is making hundreds
of nested calls! I just can't figure out, what is slow and why.
By the way, disabling super.paint(...) or super.paintComponent(...) doesn't help.
Check out Filthy Rich Clients by Chet Haase and Romain Guy. They address these very optimizations among others along the way to producing responsive and graphically impressive UI.
Doing all of your bezier mathematics on the paint thread everytime the component is refreshed is (as you've gathered) a bad idea. Does your curve change often? If not then why not paint it to a BufferedImage as and when it changes, and change your paint() code to simply draw the buffered image to the component instead.
class CurveComponent extends JComponent {
private BufferedImage image;
#Override
public void paintComponent( Graphics g ) {
if ( image == null ) {
return;
}
g.drawImage( image, 0, 0, this );
}
private void updateCurve() {
image = new BufferedImage( getWidth(), getHeight(), BufferedImage.ARGB );
Graphics g = image.getGraphics();
// draw the curve onto image using g.
g.dispose();
}
}
Only call updateCurve() when you need to and all that expensive mathematics won't be needlessly repeated. Painting should be pretty responsive, even for a fullscreen window. drawImage() will be doing a straightforward memory copy and should be lightning fast.
Try writing a tiny test app, which consists of nothing except what you need to reproduce this problem. This will make profiling easier. Then post that app here, so we can take a look at possible solutions.
I found your question interesting so I wrote a test app myself. This draws a Bezier curve which is continually resized as you drag. I created a gradient background to ensure this works well with a nasty background. I get good performance and low flicker, although I use top-notch machine.
It pays to read "Filthy Rich Clients" to learn all the tricks of writing custom Swing components that perform really well.
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Point2D;
public class CustomComponent extends JComponent {
private Point2D start = new Point2D.Double(0, 0);
private Point2D end = new Point2D.Double(300, 200);
private CustomComponent() {
this.setOpaque(true);
final MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
setEnd(e.getPoint());
}
};
this.addMouseListener(mouseAdapter);
this.addMouseMotionListener(mouseAdapter);
}
public void setStart(Point2D start) {
this.start = start;
repaint();
}
public void setEnd(Point2D end) {
this.end = end;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
final Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// draw gradient background
final int width = getWidth();
final int height = getHeight();
g2.setPaint(new GradientPaint(0, 0, Color.WHITE, width, height, Color.YELLOW));
g2.fillRect(0, 0, width, height);
// draw Bezier curve
final Shape shape = new CubicCurve2D.Double(start.getX(), start.getY(), start.getX(), end.getY(), end.getX(), start.getY(), end.getX(), end.getY());
g2.setColor(Color.BLACK);
g2.draw(shape);
g2.drawString("Click and drag to test for flickering", 100, 20);
}
public static void main(String[] args) {
final CustomComponent component = new CustomComponent();
final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
final Dimension size = new Dimension(screenSize.width - 20, screenSize.height - 100);
component.setPreferredSize(size);
final JFrame frame = new JFrame();
frame.add(component);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Some things to note:
only overwrite paintComponent(Graphics g), not the other paintXXX() methods
set custom component to opaque if possible
only use repaint() to request repainting. Never directly order a repaint directly in your code. This lets Swing handle it well.
There is no efficient way to create lots of small clip rectangles for a diagonal structure which leaves you with two strategies to avoid flickering:
Double buffering. This needs an enormous amount of memory but the memory copy is very fast (it usually happens in the time the "electron beam" goes back from lower right to upper left ... if there was still a beam in your LCD).
Don't call super.paint() (which draws or "erases" the background) and draw the curve a second time with the background color to erase it.
For more details, see this document.
[EDIT] If fillRect() wasn't abstract, you could set a break point :) Set a break point in paint(), check who calls it and whether the background got cleared at that time. It should be since rendering would be completely wrong. Then set break points further up in the call chain.
You can redraw a smaller portion of the screen using repaint(Rectangle r)
http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JComponent.html#repaint(java.awt.Rectangle)
Then you mention flicker. Since you are using swing, which uses double buffering your flickering must be coming from something else. Are you clearing the screen in paintComponent(...)? I.e. call to fillRect(...)? Don't do that, it's not needed (IIRC).
Which method do yo use to paint your curve? paint or paintComponent?
My solution was a partial re-design:
Now I don't represent each "cable"-element by a component.
Now, cables are just dummy objects (with no involved JComponent).
The repaint takes place "globally", on the content pane of the parent JFrame.
Now it's efficient, and flickers less.
just use getVisibleRect(); inside paintComponent(Graphics g) to get the area you actually need to redraw

Categories