Java Forum / GUI / August 2004
Rendering performance while dragging
sh_java_int - 27 Aug 2004 01:31 GMT Hi all,
I wrote the following code which implements a custom panel with rectangles drawn on it, which can be moved or stretched using the mouse. To move a rectangle, click inside the rectangle and drag. To stretch a rectangle, drag its left or right edge. The code works, but I'm not very satisfied with the performance while dragging. If some of you experts could improve the performance I'd be very interested in learning the techniques.
The problem is clearly that while dragging, a number of repaints get issued and each repaint paints everything which takes a while. One solution is clearly to reduce the time of the repaint, but I'm more interested in solutions which, while dragging, repaint in a special way.
I've tried several such solutions, none has been quite satisfactory; either it doesn't improve performance, or the dragged rectangle leaves a trail while dragging.
Here's the code. Thanks a lot in advance for your help.
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.*;
public class DragPerformance extends JPanel {
int minArc = 10; int maxArc = 20;
Vector rectList = new Vector(); RectData draggedEvent;
public static void main(String[] args) { DragPerformance dp = new DragPerformance(); dp.setPreferredSize(new Dimension(600, 600)); int num = 60; int offset = 600/num; for (int i = 0; i < 600; i+= offset) { dp.add("This is rectangle " + i, i, i, 100, 15); } JFrame fr = new JFrame(); fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); fr.getContentPane().add(dp); fr.pack(); fr.show(); }
public DragPerformance() { addMouseListener(new DragMouseListener(this)); }
public void add(String name, int x, int y, int w, int h) { RectData rd = new RectData(); rd.x = x; rd.y = y; rd.w = w; rd.h = h; rd.name = name; rd.editable = true; rectList.add(rd); }
public void paintComponent(Graphics g) { paintPanelBackground(g); paintEvents(g); }
public void paintEvents(Graphics g) { int sz = rectList.size(); for (int i = 0; i < sz; i++) { RectData rd = (RectData) rectList.get(i); if (rd == draggedEvent) continue; renderEvent(g, rd, false); } if (draggedEvent != null) { renderEvent(g, draggedEvent, true); } }
AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
protected void renderEvent(Graphics g, RectData ed, boolean isDragged) { Composite comp = null; if (isDragged) { Graphics2D g2 = (Graphics2D) g; comp = g2.getComposite(); g2.setComposite(ac); } int arc = ed.w/2; if (arc < minArc) arc = minArc; if (arc > maxArc) arc = maxArc;
renderBackground(g, ed, isDragged, arc); g.setColor(Color.black);
renderText(g, ed); if (isDragged) { ((Graphics2D)g).setComposite(comp); } }
Color BACKGROUND = new Color(200, 200, 50);
private void renderBackground(Graphics g, RectData ed, boolean isDragged, int arc) { Color col = BACKGROUND;
GradientPaint gp = createGradient(ed.x, ed.y, ed.w, ed.h, col, col.brighter());
Graphics2D g2d = (Graphics2D)g; Paint p = g2d.getPaint(); g2d.setPaint(gp); g2d.fillRoundRect(ed.x, ed.y, ed.w, ed.h, arc, arc); g2d.setPaint(p);
Color borderColor = col; g.setColor(borderColor); g.drawRoundRect(ed.x, ed.y, ed.w, ed.h, arc, arc); }
private GradientPaint createGradient(int x, int y, int w, int h, Color startCol, Color endCol) {
Color start = startCol; Color end = endCol; int endx = x+w; int endy = y; GradientPaint gp = new GradientPaint(x,y, start, endx, endy, end);
return gp; }
static int YOFF = 15; static int XOFF = 5;
protected void renderText(Graphics g, RectData ed) { g.setFont(getFont()); renderMultiLineTextInBox(g, ed.name, ed.x+4, ed.y, ed.w-8, ed.h);
}
static JTextArea _ta = new JTextArea(); static Dimension _tadim = new Dimension(); synchronized public static void renderMultiLineTextInBox(Graphics g, String text, int x, int y, int w, int h) { if (w <= 0 || h <= 0) return; _ta.setLineWrap(true); _ta.setWrapStyleWord(true); _ta.setOpaque(false); _ta.setFont(g.getFont()); _ta.setText(text); _tadim.setSize(w, h); _ta.setSize(_tadim); Graphics ga = g.create(x,y,w,h); _ta.getUI().paint(ga, _ta); Dimension d = _ta.getPreferredSize(); if (d.height > h + 10) { ga.setColor(Color.red); ga.fillRect(w-4,h-4,4,4); }
}
public void paintPanelBackground(Graphics g) { Dimension d = getSize(); g.setColor(Color.white); g.fillRect(0, 0, d.width, d.height); g.setColor(Color.gray);
int xinc = d.width/10; int yinc = d.height/10; for (int x = 0; x < d.width; x+= xinc) { g.drawLine(x, 0, x, d.height); } for (int y = 0; y < d.width; y+= yinc) { g.drawLine(0, y, d.width, y); } }
RectData getEventContaining(int x, int y) { int lx = x; int ly = y; int sz = rectList.size(); RectData event = null; for (int i = 0; i < sz; i++) { RectData ed = (RectData) rectList.get(i); if (ed.contains(lx,ly)) { event = ed; } } return event; }
}
class DragMouseListener implements MouseListener, MouseMotionListener { static protected int DRAG_NONE= 0; static protected int DRAG_MOVE= 1; static protected int DRAG_START_TIME= 2; static protected int DRAG_END_TIME = 3;
static private final Cursor defaultCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); static private final Cursor leftDragCursor = Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR); static private final Cursor rightDragCursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR); static private final Cursor upDragCursor = Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR); static private final Cursor downDragCursor = Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR); static private final Cursor wholeDragCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
protected Point lastPress; protected Point lastDragPosition; protected int eventDragOffsetX; protected int eventDragOffsetY;
protected int dragState;
DragPerformance view;
public DragMouseListener(DragPerformance view) { this.view = view; }
public void mouseClicked(MouseEvent e) { } public void mousePressed(MouseEvent e) { lastPress = e.getPoint(); } public void mouseReleased(MouseEvent e) { if (dragState != DRAG_NONE) { view.repaint(); } setDragState(DRAG_NONE); view.draggedEvent = null; } public void mouseEntered(MouseEvent e) { view.addMouseMotionListener(this); }
public void mouseExited(MouseEvent e) { view.removeMouseMotionListener(this); cancelDrag(); }
private void cancelDrag() { setDragState(DRAG_NONE); if (view.draggedEvent != null) { view.draggedEvent.restoreGeometry(); } }
public void mouseDragged(MouseEvent e) { if (dragState == DRAG_NONE) { setDragState(setDragType()); if (dragState == DRAG_MOVE) { eventDragOffsetX = e.getX() - view.draggedEvent.x; eventDragOffsetY = e.getY() - view.draggedEvent.y; } } if (dragState != DRAG_NONE) { updateViewForDrag(e); } lastDragPosition = e.getPoint(); }
private void updateViewForDrag(MouseEvent e) { int x = e.getX(); int y = e.getY(); if (dragState == DRAG_START_TIME) { setDraggedEventStart(x, y); } else if (dragState == DRAG_END_TIME) { setDraggedEventEnd(x, y); } else if (dragState == DRAG_MOVE) { setDraggedEventPosition(x, y); } view.repaint(); }
private void setDraggedEventPosition(int x, int y) { int px = x - eventDragOffsetX; int py = y - eventDragOffsetY; view.draggedEvent.x = px; view.draggedEvent.y = py; } private void setDraggedEventEnd(int x, int y) { int lx = x; if (lx < view.draggedEvent.x) { setDragState(DRAG_START_TIME); setDraggedEventStart(x, y); } else { view.draggedEvent.w = lx - view.draggedEvent.x; } } private void setDraggedEventStart(int x, int y) { int lx = x; if (lx > view.draggedEvent.x + view.draggedEvent.w) { setDragState(DRAG_END_TIME); setDraggedEventEnd(x, y); } else { RectData ed = view.draggedEvent; int end = ed.x + ed.w; view.draggedEvent.x = lx; view.draggedEvent.w = end - lx; } } protected int setDragType() { int x = lastPress.x; int y = lastPress.y; RectData ed = view.getEventContaining(x, y); if (ed != null && ed.isEditable()) { int lx = x; int ly = y;
ed.saveGeometry(); view.draggedEvent = ed; if (ed.nearStart(lx,ly)) { return DRAG_START_TIME; } else if (ed.nearEnd(lx,ly)) { return DRAG_END_TIME; } else { return DRAG_MOVE; } } return DRAG_NONE; }
public void mouseMoved(MouseEvent e) { int x = e.getX(); int y = e.getY(); RectData ed = view.getEventContaining(x, y); if (ed != null && ed.isEditable()) { int lx = x; int ly = y;
if (ed.nearStart(lx,ly)) { setStartDragCursor(); } else if (ed.nearEnd(lx, ly)) { setEndDragCursor(); } else { setMoveCursor(); } } else { setDefaultCursor(); } }
private void setStartDragCursor() { view.setCursor(leftDragCursor); } private void setEndDragCursor() { view.setCursor(rightDragCursor); } private void setMoveCursor() { view.setCursor(wholeDragCursor); } private void setDefaultCursor() { view.setCursor(defaultCursor); }
private void setDragState(int state) { dragState = state; if (state != DRAG_NONE) { //view.setInDrag(true); } else { //view.setInDrag(false); } }
}
class RectData { static private int START_END_NEAR = 5;
// temp String name;
// Logical values of x and y public int x; public int y; public int h; public int w;
private boolean geometrySaved;
private int save_x; private int save_y; private int save_h; private int save_w;
public boolean editable; public int z; public void restoreGeometry() { if (geometrySaved) { x = save_x; y = save_y; w = save_w; h = save_h; geometrySaved = false; } }
public void saveGeometry() { save_x = x; save_y = y; save_w = w; save_h = h; geometrySaved = true; }
public void setEditable(boolean b) { editable = b; }
public boolean isEditable() { return editable; }
public boolean nearEnd(int lx, int ly) { int gap = 100; gap = x+w - lx; return (START_END_NEAR > Math.abs(gap));
}
public boolean nearStart(int lx, int ly) { int gap = 100; gap = x - lx; return (START_END_NEAR > Math.abs(gap)); }
public boolean contains(int lx, int ly) { return (!(lx < x || lx > x + w || ly < y || ly > y + h)); }
public boolean intersects(RectData ed) { return ( !(x+w < ed.x || x > ed.x + ed.w || y+h < ed.y || y > ed.y + ed.h)); }
}
Larry Barowski - 27 Aug 2004 09:11 GMT > Hi all, > [quoted text clipped - 11 lines] > interested in solutions which, while dragging, repaint in a special > way. Every time the rectangle changes, call repaint(int, int, int, int) twice, once with the old bounds and once with the new bounds.
You can also use an Image as a buffer for everything that is not being dragged. When the drag state changes (start drag, end drag), paint everything but the active item to the buffer. For paintComponent(), draw the buffer followed by the active item, if any.
ak - 27 Aug 2004 12:40 GMT > I wrote the following code which implements a custom panel with > rectangles drawn on it, which can be moved or stretched using the [quoted text clipped - 13 lines] > either it doesn't improve performance, or the dragged rectangle leaves > a trail while dragging. your problem is that you draws every time all your rects. instead you should determine "damaged" area and change 2 methods:
Rectangle damagedArea;
public void paintComponent(Graphics g) { if (draggedEvent != null) { g.setClip(damagedAred); } paintPanelBackground(g); paintEvents(g); g.setClip(null); }
public void paintEvents(Graphics g) { int sz = rectList.size(); for (int i = 0; i < sz; i++) { RectData rd = (RectData) rectList.get(i); if (rd == draggedEvent) continue; if (draggedEvent == null || damagedArea.intersects(new Rectangle(rd.x, rd.y, rd.w, rd.h))) { renderEvent(g, rd, false); } } if (draggedEvent != null) { renderEvent(g, draggedEvent, true); } }
 Signature Andrei Kouznetsov http://uio.dev.java.net Unified I/O for Java http://reader.imagero.com Java image reader
Larry Barowski - 27 Aug 2004 19:46 GMT > your problem is that you draws every time all your rects. > instead you should determine "damaged" area and change 2 methods: [quoted text clipped - 4 lines] > if (draggedEvent != null) { > g.setClip(damagedAred); This is not a good idea. For one thing, paint events may be delayed. You may get two drag events before there is one paint event. The damaged area computed during the first drag will be lost. Also, the real "damaged area" may include more than just the rectangle. Suppose some small window from another application pops up over your application while you are dragging, then pops back down on its own. The area it covered will not be repainted using this method.
Better to limit the repaint area on the front end using repaint(int, int, int, int). Note that calling repaint(int, int, int, int) twice, once with the old rectangle bounds and once with the new bounds, will usually result in just one paint event with the clip set to the smallest rectangle covering the two. There is no need to do such calculations yourself.
ak - 27 Aug 2004 21:47 GMT > This is not a good idea. For one thing, paint events > may be delayed. You may get two drag events [quoted text clipped - 15 lines] > the two. There is no need to do such calculations > yourself. thats right, however you can instead of view.repaint() call view.paintImmediately(damagedArea); also you can save your damaged areas in some list and check d.areas for intersection while repainting.
May be it is better on first drag event paint component into offscreen image, and then on further events u have to draw this image and draggedEvent.
 Signature Andrei Kouznetsov http://uio.dev.java.net Unified I/O for Java http://reader.imagero.com Java image reader
sh_java_int - 27 Aug 2004 21:15 GMT Thanks very much for your suggestions.
To complete the picture I just wanted to report on the effects of the suggestions. There were two suggestions
1) While dragging, clip the Graphics object to the "damaged" area and draw only the rectangles that intersect the damaged area.
Method 1 results in no perceptible improvement.
2) While dragging, draw everything except the dragged rectangle from a stored Image object, and draw the dragged rectangle as usual.
Method 2 gives responsive dragging, with the caveat that there is a longish hesitation period at the start of the drag. This is because the background image is constructed at the start of the drag. Once the background image has been constructed, dragging is fast and responsive.
All in all, I think I'll go with method 2.
ak - 27 Aug 2004 21:42 GMT > 2) While dragging, draw everything except the dragged rectangle from a > stored Image object, and draw the dragged rectangle as usual. [quoted text clipped - 4 lines] > background image has been constructed, dragging is fast and > responsive. note, that you need to create background image only if size of view changes, otherwise reuse existing one.
 Signature Andrei Kouznetsov http://uio.dev.java.net Unified I/O for Java http://reader.imagero.com Java image reader
sh_java_int - 28 Aug 2004 06:54 GMT > note, that you need to create background image only if size of view changes, > otherwise > reuse existing one. For the record, this isn't quite right. The background image has to be computed whenever the view size changes, but it also has to be computed whenever the dragged object changes, which is just about every drag.
ak - 28 Aug 2004 09:28 GMT > > note, that you need to create background image only if size of view changes, > > otherwise [quoted text clipped - 4 lines] > computed whenever the dragged object changes, which is just about > every drag. Offscreen image should be created only if size changes - better only is size of view bigger then size of image, and you should paint your view into it every time drag object changes. Second one is not really expensive.
 Signature Andrei Kouznetsov http://uio.dev.java.net Unified I/O for Java http://reader.imagero.com Java image reader
Larry Barowski - 29 Aug 2004 05:52 GMT > Thanks very much for your suggestions. > [quoted text clipped - 7 lines] > background image has been constructed, dragging is fast and > responsive. I wouldn't expect much delay on modern hardware. How many rectangles are you dealing with?
Note that you only need to repaint the part of the background image covered by the rectangle that will be dragged. You can also save some time by not drawing rectangles that don't intersect the clipping bounds - the computations to do that take no significant time compared to actually drawing the rectangles.
Also, you may want to show the rectangle in a different color, or something like that, when it is being dragged. That will cause less frustration for the user, since they will know when the delay is over.
Jacob - 28 Aug 2004 14:05 GMT > I wrote the following code which implements a custom panel with > rectangles drawn on it, which can be moved or stretched using the [quoted text clipped - 3 lines] > you experts could improve the performance I'd be very interested in > learning the techniques. Unless this is an excercise you do in order to learn Java 2D, I suggest you look at libraries that handle concepts like vector graphics, damage, double buffering, background image and interactions already.
There are several; I know http://geosoft.no/graphics/
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|