From: Frederick W. <fre...@us...> - 2012-01-14 21:19:32
|
rails/ui/swing/ORPanel.java | 2 rails/ui/swing/ORUIManager.java | 40 +++++++++-- rails/ui/swing/UpgradesPanel.java | 8 -- rails/ui/swing/hexmap/GUIHex.java | 23 +++--- rails/ui/swing/hexmap/HexMap.java | 129 +++++++++++++++++++++++++++++++++++--- 5 files changed, 169 insertions(+), 33 deletions(-) New commits: commit cca1aa0b9824993fe84cbdc24262be77bded9815 Author: Frederick Weld <fre...@gm...> Date: Sat Jan 14 22:14:00 2012 +0100 Fixed hex map's dirty regions mgmt (multiple non-intersecting repaints) Before, each layer had a flag whether the image buffer was dirty. Yet, merely having a flag is not enough as several repaints could have been triggered before paintComponent. This means that each layer has to keep track of each dirty region. This is now implemented. diff --git a/rails/ui/swing/ORPanel.java b/rails/ui/swing/ORPanel.java index 0d0c5ec..bfab7d8 100644 --- a/rails/ui/swing/ORPanel.java +++ b/rails/ui/swing/ORPanel.java @@ -1223,6 +1223,8 @@ implements ActionListener, KeyListener, RevenueListener { button1.setEnabled(false); orCompIndex = -1; + + orUIManager.getMap().setTrainPaths(null); } // TEMPORARY diff --git a/rails/ui/swing/ORUIManager.java b/rails/ui/swing/ORUIManager.java index 0faaaae..4bed76d 100644 --- a/rails/ui/swing/ORUIManager.java +++ b/rails/ui/swing/ORUIManager.java @@ -1022,6 +1022,8 @@ public class ORUIManager implements DialogOwner { if (orWindow.process(executedAction)) { upgradePanel.clear(); map.selectHex(null); + //ensure painting the token (model update currently does not arrive at UI) + map.repaintTokens(selectedHex.getBounds()); selectedHex = null; } } diff --git a/rails/ui/swing/hexmap/HexMap.java b/rails/ui/swing/hexmap/HexMap.java index e220614..5d2188d 100644 --- a/rails/ui/swing/hexmap/HexMap.java +++ b/rails/ui/swing/hexmap/HexMap.java @@ -31,17 +31,118 @@ import rails.util.Util; public abstract class HexMap implements MouseListener, MouseMotionListener { + /** + * class for managing sets of rectangles. Apart from several convenience methods, + * this class aims at keeping the set as minimal as possible. + */ + private class RectangleSet { + private List<Rectangle> rs = new LinkedList<Rectangle>(); + /** + * @param rOp Rectangle to be added to the set. Only added if not contained in + * a rectangle of the set. If added, all of the set's rectangles which are a + * sub-area of this rectangle are dropped (in order to keep the rectangle list + * as small as possible). + */ + public void add(Rectangle rOp) { + // exit if rectangle already contained in set of rectangles + for (Rectangle r : rs) { + if (r.contains(rOp)) return; + } + + // build new set (do not include rectangles contained by new rectangle) + List<Rectangle> newRs = new LinkedList<Rectangle>(); + for (Rectangle r : rs) { + if (!rOp.contains(r)) newRs.add(r); + } + newRs.add(rOp); + rs = newRs; + } + + /** + * As a side-effect, the area defined by the given rectangle is removed from the + * area defined by the set of rectangles. This might lead to splitting the set's + * rectangles if only parts of their areas become removed. + * @return The intersection between the given rectangle and the set of rectangles. + * Returns null if the intersection is empty. + */ + public Rectangle getIntersectionAndRemoveFromSet(Rectangle rOp) { + Rectangle intersection = null; + RectangleSet newRs = new RectangleSet(); + for (Rectangle r : rs) { + Rectangle intersectionPart = null; + + //check for the most common case: set's rectangle is a sub-area + //of the given rectangle (common because repaint creates unions) + //avoid further (complex) processing for this case + if (rOp.contains(r)) { + intersectionPart = r; + } else if (r.intersects(rOp)) { + //update intersection region + intersectionPart = r.intersection(rOp); + + //adjust rectangle: potentially split into 4 sub-rectangles + // *************************** + // * | 3 | * + // * ************* * + // * 1 * rOp * 2 * + // * ************* * + // * | 4 | * + // *************************** + + //region 1 + if (r.x < rOp.x && (r.x + r.width) > rOp.x) { + newRs.add(new Rectangle(r.x,r.y,(rOp.x-r.x),r.height)); + } + //region 2 + if ((r.x + r.width) > (rOp.x + rOp.width) && r.x < (rOp.x + rOp.width)) { + newRs.add(new Rectangle((rOp.x+rOp.width),r.y, + (r.x+r.width-rOp.x-rOp.width),r.height)); + } + //region 3 + if (r.y < rOp.y) { + int x1 = Math.max(r.x, rOp.x); + int x2 = Math.min(r.x+r.width, rOp.x+rOp.width); + if (x1 < x2) newRs.add(new Rectangle(x1,r.y,x2-x1,rOp.y-r.y)); + } + //region 4 + if ((r.y + r.height) > (rOp.y + rOp.height)) { + int x1 = Math.max(r.x, rOp.x); + int x2 = Math.min(r.x+r.width, rOp.x+rOp.width); + if (x1 < x2) newRs.add(new Rectangle(x1,(rOp.y+rOp.height), + x2-x1,(r.y+r.height-rOp.y-rOp.height))); + } + } + + if (intersectionPart == null) { + //if no intersection part, this rectangle remains unchanged in the set + newRs.add(r); + } else { + //expand the intersection region if intersection part found + if (intersection == null) { + intersection = (Rectangle)intersectionPart.clone(); + } else { + intersection.add(intersectionPart); + } + } + } + rs = newRs.rs; + return intersection; + } + } private abstract class HexLayer extends JComponent { private static final long serialVersionUID = 1L; private BufferedImage bufferedImage; - private boolean isBufferDirty = false; + /* + * list of regions for which the layer's image buffer is dirty + */ + private RectangleSet bufferDirtyRegions = new RectangleSet();; protected abstract void paintImage(Graphics g); final public void repaint() { - isBufferDirty = true; + bufferDirtyRegions.add( new Rectangle(0,0,getWidth(),getHeight()) ); super.repaint(); } public void repaint(Rectangle r) { - isBufferDirty = true; + bufferDirtyRegions.add( r ); super.repaint(r); } final public void paintComponent(Graphics g) { @@ -60,18 +161,26 @@ public abstract class HexMap implements MouseListener, || bufferedImage.getHeight() != getHeight() ) { //create new buffer image bufferedImage = new BufferedImage(getWidth(), getHeight(),BufferedImage.TYPE_INT_ARGB); - isBufferDirty = true; + + //clear information of the image buffer's dirty regions + bufferDirtyRegions = new RectangleSet();; + bufferDirtyRegions.add( new Rectangle(0,0,getWidth(),getHeight()) ); //since the buffered image is empty, it has to be completely redrawn rectClip = new Rectangle (0, 0, getWidth(), getHeight()); } - if (isBufferDirty) { + //determine which parts of the clip are dirty and have to be redrawn + Rectangle dirtyClipArea = bufferDirtyRegions.getIntersectionAndRemoveFromSet(rectClip); + if (dirtyClipArea != null) { //buffer redraw is necessary Graphics2D imageGraphics = (Graphics2D)bufferedImage.getGraphics(); //apply the clip of the component's repaint to its image buffer - imageGraphics.setClip(rectClip.x, rectClip.y, rectClip.width, rectClip.height); + imageGraphics.setClip(dirtyClipArea.x, + dirtyClipArea.y, + dirtyClipArea.width, + dirtyClipArea.height); //set the background to transparent so that only drawn parts of the //buffer will be taken over @@ -80,16 +189,18 @@ public abstract class HexMap implements MouseListener, //clear the clip (for a non-virtual graphic, this would have been //done by super.paintComponent) - imageGraphics.clearRect(rectClip.x, rectClip.y, rectClip.width, rectClip.height); + imageGraphics.clearRect(dirtyClipArea.x, + dirtyClipArea.y, + dirtyClipArea.width, + dirtyClipArea.height); //paint within the buffer paintImage(imageGraphics); imageGraphics.dispose(); - isBufferDirty = false; } - //buffer is valid and can be used + //now buffer is valid and can be used BufferedImage bufferedRect = bufferedImage.getSubimage( rectClip.x, rectClip.y, rectClip.width, rectClip.height); g.drawImage(bufferedRect, rectClip.x, rectClip.y, null); commit 4c1048ef3816c429aa208275985c2725a6faf892 Author: Frederick Weld <fre...@gm...> Date: Sat Jan 14 18:09:08 2012 +0100 Excluded layable tiles from upgrade panel if no valid rotation exists Before, selecting a tile of the upgrade panel could lead to the message "This tile cannot be laid in a valid orientation." Now, tiles are excluded from the upgrade panel selection if such message would be issued upon clicking on them. Stated differently, every tile listed in the upgrade panel can now be laid by the user. diff --git a/rails/ui/swing/ORUIManager.java b/rails/ui/swing/ORUIManager.java index 410605e..0faaaae 100644 --- a/rails/ui/swing/ORUIManager.java +++ b/rails/ui/swing/ORUIManager.java @@ -705,12 +705,7 @@ public class ORUIManager implements DialogOwner { } // Check if the new tile must be connected to some other track - boolean mustConnect = - tile.getColourName().equalsIgnoreCase(Tile.YELLOW_COLOUR_NAME) - // Does not apply to the current company's home hex(es) - && !hex.getHexModel().isHomeFor(orComp) - // Does not apply to special tile lays - && !isUnconnectedTileLayTarget(hex); + boolean mustConnect = getMustConnectRequirement(hex,tile); if (hex.dropTile(tileId, mustConnect)) { /* Lay tile */ @@ -724,13 +719,38 @@ public class ORUIManager implements DialogOwner { upgradePanel.showUpgrades(); } } + + private boolean getMustConnectRequirement (GUIHex hex,TileI tile) { + if (tile == null || hex == null) return false; + return tile.getColourName().equalsIgnoreCase(Tile.YELLOW_COLOUR_NAME) + // Does not apply to the current company's home hex(es) + && !hex.getHexModel().isHomeFor(orComp) + // Does not apply to special tile lays + && !isUnconnectedTileLayTarget(hex.getHexModel()); + } + + public void addTileUpgradeIfValid(GUIHex hex, int tileId) { + addTileUpgradeIfValid (hex, + gameUIManager.getGameManager().getTileManager().getTile(tileId)); + } + + public void addTileUpgradeIfValid(GUIHex hex, TileI tile) { + if (!tileUpgrades.contains(tile) && isTileUpgradeValid(hex,tile)) { + tileUpgrades.add(tile); + } + } + + private boolean isTileUpgradeValid(GUIHex hex, TileI tile) { + // Check if the new tile must be connected to some other track + return hex.isTileUpgradeValid(tile.getId(), + getMustConnectRequirement(hex,tile)); + } - protected boolean isUnconnectedTileLayTarget(GUIHex hex) { + protected boolean isUnconnectedTileLayTarget(MapHex hex) { - MapHex mapHex = hex.getHexModel(); for (LayTile action : possibleActions.getType(LayTile.class)) { if (action.getType() == LayTile.SPECIAL_PROPERTY - && action.getSpecialProperty().getLocations().contains(mapHex)) { + && action.getSpecialProperty().getLocations().contains(hex)) { // log.debug(hex.getName()+" is a special property target"); return true; } diff --git a/rails/ui/swing/UpgradesPanel.java b/rails/ui/swing/UpgradesPanel.java index 4ff56e8..7c67112 100644 --- a/rails/ui/swing/UpgradesPanel.java +++ b/rails/ui/swing/UpgradesPanel.java @@ -98,16 +98,14 @@ public class UpgradesPanel extends Box implements MouseListener, ActionListener // Skip if not allowed in LayTile //if (!layTile.isTileColourAllowed(tile.getColourName())) continue; - if (!orUIManager.tileUpgrades.contains(tile) && layTile.isTileColourAllowed(tile.getColourName())) - orUIManager.tileUpgrades.add(tile); + if (layTile.isTileColourAllowed(tile.getColourName())) + orUIManager.addTileUpgradeIfValid(uiHex,tile); } } else { for (TileI tile : tiles) { // Skip if colour is not allowed yet if (!allowedColours.contains(tile.getColourName())) continue; - - if (!orUIManager.tileUpgrades.contains(tile)) - orUIManager.tileUpgrades.add(tile); + orUIManager.addTileUpgradeIfValid(uiHex,tile); } } } diff --git a/rails/ui/swing/hexmap/GUIHex.java b/rails/ui/swing/hexmap/GUIHex.java index 52165a7..f67de31 100644 --- a/rails/ui/swing/hexmap/GUIHex.java +++ b/rails/ui/swing/hexmap/GUIHex.java @@ -879,21 +879,24 @@ public class GUIHex implements ViewObject { public boolean dropTile(int tileId, boolean upgradeMustConnect) { this.upgradeMustConnect = upgradeMustConnect; - provisionalGUITile = new GUITile(tileId, this); - /* Check if we can find a valid orientation of this tile */ - if (provisionalGUITile.rotate(0, currentGUITile, upgradeMustConnect)) { - /* If so, accept it */ + provisionalGUITile = createUpgradeTileIfValid (tileId, upgradeMustConnect); + if (provisionalGUITile != null) { provisionalGUITile.setScale(SELECTED_SCALE); toolTip = "Click to rotate"; hexMap.repaintMarks(getBounds()); hexMap.repaintTiles(getBounds()); // provisional tile resides in tile layer - return true; - } else { - /* If not, refuse it */ - provisionalGUITile = null; - return false; } - + return (provisionalGUITile != null); + } + + private GUITile createUpgradeTileIfValid (int tileId, boolean upgradeMustConnect) { + GUITile t = new GUITile(tileId, this); + /* Check if we can find a valid orientation of this tile */ + return ( t.rotate(0, currentGUITile, upgradeMustConnect) ? t : null); + } + + public boolean isTileUpgradeValid (int tileId, boolean upgradeMustConnect) { + return ( createUpgradeTileIfValid(tileId, upgradeMustConnect) != null ); } /** forces the tile to drop */ |