/* * Copyright (C) 2009 Quadduc <quadduc@gmail.com> * Copyright (C) 2014, egofree * * This file is part of LateralGM. * LateralGM is free software and comes with ABSOLUTELY NO WARRANTY. * See LICENSE for details. */ package org.lateralgm.ui.swing.visuals; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Toolkit; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.FilteredImageSource; import java.awt.image.ImageFilter; import java.awt.image.RGBImageFilter; import java.awt.image.RasterFormatException; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import javax.swing.ImageIcon; import javax.swing.SwingUtilities; import org.lateralgm.main.LGM; import org.lateralgm.main.Prefs; import org.lateralgm.main.UpdateSource.UpdateEvent; import org.lateralgm.main.UpdateSource.UpdateListener; import org.lateralgm.main.Util; import org.lateralgm.resources.Background; import org.lateralgm.resources.GmObject; import org.lateralgm.resources.GmObject.PGmObject; import org.lateralgm.resources.ResourceReference; import org.lateralgm.resources.Room; import org.lateralgm.resources.Room.PRoom; import org.lateralgm.resources.Room.Piece; import org.lateralgm.resources.Sprite; import org.lateralgm.resources.Sprite.PSprite; import org.lateralgm.resources.sub.BackgroundDef; import org.lateralgm.resources.sub.BackgroundDef.PBackgroundDef; import org.lateralgm.resources.sub.Instance; import org.lateralgm.resources.sub.Instance.PInstance; import org.lateralgm.resources.sub.Tile; import org.lateralgm.resources.sub.Tile.PTile; import org.lateralgm.resources.sub.View; import org.lateralgm.resources.sub.View.PView; import org.lateralgm.util.ActiveArrayList; import org.lateralgm.util.ActiveArrayList.ListUpdateEvent; import org.lateralgm.util.PropertyMap.PropertyUpdateEvent; import org.lateralgm.util.PropertyMap.PropertyUpdateListener; public class RoomVisual extends AbstractVisual implements BoundedVisual,UpdateListener { protected static final ImageIcon EMPTY_SPRITE = LGM.getIconForKey("Resource.EMPTY_OBJ"); //$NON-NLS-1$ protected static final BufferedImage EMPTY_IMAGE = EMPTY_SPRITE.getIconWidth() <= 0 ? null : new BufferedImage(EMPTY_SPRITE.getIconWidth(),EMPTY_SPRITE.getIconHeight(), BufferedImage.TYPE_INT_ARGB); private final BinVisual binVisual; private final GridVisual gridVisual; public final Room room; // These variables are here to keep the managers from being GC'd. protected final InstanceVisualListManager ivlm; protected final TileVisualListManager tvlm; private final RoomPropertyListener rpl = new RoomPropertyListener(); private final BgDefPropertyListener bdpl = new BgDefPropertyListener(); private final ViewPropertyListener viewPropertyListener = new ViewPropertyListener(); // Contains the region selected by the user private Rectangle selection = null; // The position of the mouse cursor private Point mousePosition = null; // Image of the region made by the user private BufferedImage selectionImage = null; // Show if the user has pasted a region private boolean pasteMode = false; private EnumSet<Show> show; private int gridFactor = 1; private int gridX, gridY; private boolean viewsVisible; private Integer visibleLayer = null; public enum Show { BACKGROUNDS,INSTANCES,TILES,FOREGROUNDS,GRID,VIEWS } public RoomVisual(VisualContainer vc, Room r) { this(vc,r,EnumSet.range(Show.BACKGROUNDS,Show.GRID)); } public RoomVisual(VisualContainer vc, Room r, EnumSet<Show> s) { super(vc); room = r; show = EnumSet.copyOf(s); binVisual = new BinVisual(vc,128,(Integer) r.get(PRoom.WIDTH),(Integer) r.get(PRoom.HEIGHT)); gridVisual = new GridVisual((Boolean) r.get(PRoom.ISOMETRIC),(Integer) r.get(PRoom.SNAP_X), (Integer) r.get(PRoom.SNAP_Y)); r.properties.updateSource.addListener(rpl); ivlm = new InstanceVisualListManager(); tvlm = new TileVisualListManager(); // Set the property listener for each background for (BackgroundDef bd : room.backgroundDefs) { bd.properties.updateSource.addListener(bdpl); bd.updateSource.addListener(this); } // Set the property listener for each view for (View view : room.views) view.properties.updateSource.addListener(viewPropertyListener); } public int getSelectionImageWidth() { return selectionImage.getWidth(); } public int getSelectionImageHeight() { return selectionImage.getHeight(); } // Deactivate the paste mode public void deactivatePasteMode() { pasteMode = false; repaint(null); } // Activate the paste mode public void activatePasteMode() { pasteMode = true; repaint(null); } // Make an image of the region selected by the user public void setSelectionImage(List<Instance> selectedInstances, List<Tile> selectedTiles) { // Create an empty image BufferedImage selectionImage = new BufferedImage(selection.width,selection.height, BufferedImage.TYPE_INT_ARGB); Graphics g = selectionImage.getGraphics(); Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2.setColor(Util.convertGmColorWithAlpha(Prefs.multipleSelectionInsideColor)); // If the option 'Fill rectangle' is set if (Prefs.useFilledRectangleForMultipleSelection) g2.fillRect(1,1,selection.width - 2,selection.height - 2); else g2.drawRect(1,1,selection.width - 3,selection.height - 3); g.setColor(Util.convertGmColorWithAlpha(Prefs.multipleSelectionOutsideColor)); // Draw the outside border if (Prefs.useFilledRectangleForMultipleSelection) g2.drawRect(0,0,selection.width - 1,selection.height - 1); else g2.drawRect(0,0,selection.width - 1,selection.height - 1); AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,0.5f); g2.setComposite(ac); // If the user selected instances if (selectedInstances != null) { // Get each selected instance and draw it on the buffer image for (Instance instance : selectedInstances) { Graphics2D g3 = (Graphics2D) g2.create(); // Get instance's properties Point2D scale = instance.getScale(); int alpha = instance.getAlpha(); double rotation = instance.getRotation(); Point position = instance.getPosition(); // Sprite's origin int originx = 0, originy = 0; // Used to modify the position when scaling int offsetx = 0, offsety = 0; // Get the relative position of the instance in the selection Point newPosition = new Point(position.x - selection.x,position.y - selection.y); // Get the instance's image ResourceReference<GmObject> instanceObject = instance.properties.get(PInstance.OBJECT); BufferedImage instanceImage = instanceObject.get().getDisplayImage(); // If there is no image, draw a sphere if (instanceImage == null || alpha == 0) { g3.drawImage(EMPTY_SPRITE.getImage(),newPosition.x,newPosition.y,null); g3.dispose(); continue; } // Get sprite's origin ResourceReference<Sprite> sprite = instanceObject.get().get(PGmObject.SPRITE); originx = (Integer) sprite.get().get(PSprite.ORIGIN_X); originy = (Integer) sprite.get().get(PSprite.ORIGIN_Y); if (originx != 0 || originy != 0) newPosition.translate(-(int) (originx * scale.getX()),-(int) (originy * scale.getY())); // Ensure that the position stays the same when there is a scaling if (scale.getX() != 1.0 || scale.getY() != 1.0) { offsetx = (int) (newPosition.x * scale.getX() - newPosition.x); offsety = (int) (newPosition.y * scale.getY() - newPosition.y); } // Apply scaling, rotation and translation if (offsetx != 0 || offsety != 0) g3.translate(-offsetx,-offsety); if (rotation != 0) g3.rotate(Math.toRadians(-rotation),newPosition.x + offsetx,newPosition.y + offsety); g3.scale(scale.getX(),scale.getY()); Image newImage; Color selectedColor = instance.getAWTColor(); // If a color has been selected, apply color blending if (!Color.WHITE.equals(selectedColor)) { ImageFilter filter = new ColorFilter(selectedColor); FilteredImageSource filteredSrc = new FilteredImageSource(instanceImage.getSource(), filter); newImage = Toolkit.getDefaultToolkit().createImage(filteredSrc); } else { newImage = instanceImage; } // If instance's alpha value is lower than the default one, apply alpha if (alpha > 0 && alpha < ac.getAlpha() * 255) { AlphaComposite newAc = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) (alpha / 255.0)); g3.setComposite(newAc); } g3.drawImage(newImage,newPosition.x,newPosition.y,null); g3.dispose(); } } else { // Get each selected tile and draw it on the buffer image for (Tile tile : selectedTiles) { Point newPosition = tile.getPosition(); // Get tile's background ResourceReference<Background> background = tile.properties.get(PTile.BACKGROUND); BufferedImage backgroundImage = background.get().getDisplayImage(); Point tilePosition = tile.getBackgroundPosition(); Dimension tileSize = tile.getSize(); // Get tile's image BufferedImage tileImage = backgroundImage.getSubimage(tilePosition.x,tilePosition.y, tileSize.width,tileSize.height); g2.drawImage(tileImage,newPosition.x - selection.x,newPosition.y - selection.y,null); } } this.selectionImage = selectionImage; } // Update the mouse position. Needed for displaying the selected region public void setMousePosition(Point mousePosition) { this.mousePosition = mousePosition; repaint(null); } // set the region selected by the user public void setSelection(Rectangle selection) { this.selection = selection; repaint(null); } // Set if the views should visible or not (used when the 'views' tab is selected) public void setViewsVisible(boolean visible) { viewsVisible = visible; repaint(null); } // Set the visible layer property public void setVisibleLayer(Integer layer) { visibleLayer = layer; repaint(null); } public void extendBounds(Rectangle b) { b.add(new Rectangle(0,0,(Integer) room.get(PRoom.WIDTH),(Integer) room.get(PRoom.HEIGHT))); binVisual.extendBounds(b); } public void paint(Graphics g) { int width = (Integer) room.get(PRoom.WIDTH); int height = (Integer) room.get(PRoom.HEIGHT); Graphics g2 = g.create(); g2.clipRect(0,0,width,height); if (room.get(PRoom.DRAW_BACKGROUND_COLOR)) { g2.setColor((Color) room.get(PRoom.BACKGROUND_COLOR)); g2.fillRect(0,0,width,height); } if (show.contains(Show.BACKGROUNDS)) for (BackgroundDef bd : room.backgroundDefs) if (shouldPaint(bd,false)) paintBackground(g2,bd,width,height); // Paint pieces and tiles on the unclipped g, so that they are visible // even if outside the room if (show.contains(Show.INSTANCES) || show.contains(Show.TILES)) binVisual.paint(g); if (show.contains(Show.FOREGROUNDS)) for (BackgroundDef bd : room.backgroundDefs) if (shouldPaint(bd,true)) paintBackground(g2,bd,width,height); if (show.contains(Show.GRID)) { g2.translate(gridX - (room.get(PRoom.ISOMETRIC) ? (Integer) room.get(PRoom.SNAP_X) * (gridFactor - 1) / 2 : 0),gridY); gridVisual.paint(g2); } // If 'Show views' option has been set or if the 'Views' tab is selected if (show.contains(Show.VIEWS) || viewsVisible) { boolean viewsEnabled = room.get(PRoom.VIEWS_ENABLED); // Display the view when the views are enabled if (viewsEnabled) for (View view : room.views) if (view.properties.get(PView.VISIBLE)) paintView(g2,view); } // If the user is moving a selected region, display it if (pasteMode) g2.drawImage(selectionImage,mousePosition.x,mousePosition.y,null); // If there is a selection, display it if (selection != null) paintSelection(g2); g2.dispose(); } // Display the selection made by the user private void paintSelection(Graphics g) { // If the option 'Invert colors' is set if (Prefs.useInvertedColorForMultipleSelection) g.setXORMode(Util.convertGmColorWithAlpha(Prefs.multipleSelectionInsideColor)); else g.setColor(Util.convertGmColorWithAlpha(Prefs.multipleSelectionInsideColor)); // If the option 'Fill rectangle' is set if (Prefs.useFilledRectangleForMultipleSelection) g.fillRect(selection.x + 1,selection.y + 1,selection.width - 1,selection.height - 1); else g.drawRect(selection.x + 1,selection.y + 1,selection.width - 2,selection.height - 2); // If the option 'Invert colors' is set if (Prefs.useInvertedColorForMultipleSelection) g.setXORMode(Util.convertGmColorWithAlpha(Prefs.multipleSelectionOutsideColor)); else g.setColor(Util.convertGmColorWithAlpha(Prefs.multipleSelectionOutsideColor)); // Draw the outside border g.drawRect(selection.x,selection.y,selection.width,selection.height); } // Display a view on the panel private void paintView(Graphics g, View view) { Graphics2D g2 = (Graphics2D) g; // View location int x; int y; int objectFollowingX = view.properties.get(PView.OBJECT_FOLLOWING_X); int objectFollowingY = view.properties.get(PView.OBJECT_FOLLOWING_Y); // Get the view dimension int width = view.properties.get(PView.VIEW_W); int height = view.properties.get(PView.VIEW_H); // If the view is following an object, center the view around the object if (objectFollowingX > -1) { x = objectFollowingX; y = objectFollowingY; } else { // Use the 'normal' view location x = view.properties.get(PView.VIEW_X); y = view.properties.get(PView.VIEW_Y); } // If the option 'invert colors' is set if (Prefs.useInvertedColorForViews) g2.setXORMode(Util.convertGmColorWithAlpha(Prefs.viewOutsideColor)); else g2.setColor(Util.convertGmColorWithAlpha(Prefs.viewOutsideColor)); // Draw the 'outside' rectangle if (Prefs.useFilledRectangleForViews) { g2.drawRect(x - 2,y - 2,width + 3,height + 3); g2.drawRect(x - 1,y - 1,width + 1,height + 1); } else { g2.drawRect(x,y,width,height); g2.drawRect(x + 2,y + 2,width - 4,height - 4); } // If the option 'invert colors' is set if (Prefs.useInvertedColorForViews) g2.setXORMode(Util.convertGmColorWithAlpha(Prefs.viewInsideColor)); else g2.setColor(Util.convertGmColorWithAlpha(Prefs.viewInsideColor)); // Draw the 'inside' rectangle if (Prefs.useFilledRectangleForViews) g2.fillRect(x,y,width,height); else g2.drawRect(x + 1,y + 1,width - 2,height - 2); // If the view is following an object if (objectFollowingX > -1) { // Get the border zone properties int borderH = view.properties.get(PView.BORDER_H); int borderV = view.properties.get(PView.BORDER_V); // If the border zone is not empty if (!(borderH == 0 & borderV == 0)) { if (Prefs.useFilledRectangleForViews) { // Define the stroke for the border zone float dash[] = { 10.0f }; BasicStroke dashed = new BasicStroke(2.0f,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER, 10.0f,dash,0.0f); // Draw the border zone g2.setColor(Util.convertGmColorWithAlpha(Prefs.viewOutsideColor)); g2.setStroke(dashed); g2.drawRect(x + borderH,y + borderV,width - borderH * 2,height - borderV * 2); } else { // Define the strokes for the border zone float outside[] = { 10.0f }; float inside[] = { 8.0f,12.0f }; BasicStroke dashed_black = new BasicStroke(3.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,10.0f,outside,0.0f); BasicStroke dashed_white = new BasicStroke(1.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,10.0f,inside,19.0f); // Draw the border zone g2.setColor(Util.convertGmColorWithAlpha(Prefs.viewOutsideColor)); g2.setStroke(dashed_black); g2.drawRect(x + borderH,y + borderV,width - borderH * 2,height - borderV * 2); g2.setColor(Util.convertGmColorWithAlpha(Prefs.viewInsideColor)); g2.setStroke(dashed_white); g2.drawRect(x + borderH,y + borderV,width - borderH * 2,height - borderV * 2); } g2.setStroke(new BasicStroke()); } } } private static boolean shouldPaint(BackgroundDef bd, Boolean fg) { if (!(Boolean) bd.properties.get(PBackgroundDef.VISIBLE)) return false; return fg.equals(bd.properties.get(PBackgroundDef.FOREGROUND)); } public void setVisible(Show s, boolean v) { if (v ? show.add(s) : show.remove(s)) { if (s == Show.GRID && !v) gridVisual.flush(true); repaint(null); } } public void setGridFactor(int f) { gridFactor = f; gridVisual.setWidth(gridFactor * (Integer) room.get(PRoom.SNAP_X)); gridVisual.setHeight(gridFactor * (Integer) room.get(PRoom.SNAP_Y)); } public void setGridOffset(int x, int y) { if (gridX == x && gridY == y) return; gridX = x; gridY = y; if (show.contains(Show.GRID)) repaint(null); } public void setGridXOffset(int x) { if (gridX == x) return; gridX = x; if (show.contains(Show.GRID)) repaint(null); } public void setGridYOffset(int y) { if (gridY == y) return; gridY = y; if (show.contains(Show.GRID)) repaint(null); } public <P extends Piece>Iterator<P> intersect(Rectangle r, Class<P> p) { return new PieceIterator<P>(binVisual.intersect(r,getVisualClass(p))); } public <P extends Piece>Iterator<P> intersect(Rectangle r, Class<P> p, int depth) { return new PieceIterator<P>(binVisual.intersect(r,getVisualClass(p),depth)); } private static class PieceIterator<P extends Piece> implements Iterator<P> { private Iterator<PieceVisual<P>> vi; public PieceIterator(Iterator<PieceVisual<P>> vi) { this.vi = vi; } public boolean hasNext() { return vi.hasNext(); } public P next() { return vi.next().piece; } public void remove() { vi.remove(); } } public boolean intersects(Rectangle r, Piece p) { Iterator<Piece> pi = intersect(r); while (pi.hasNext()) if (pi.next() == p) return true; return false; } @SuppressWarnings("unchecked") private static <P extends Piece, V extends PieceVisual<P>>Class<V> getVisualClass(Class<P> p) { if (p == Piece.class) return (Class<V>) PieceVisual.class; if (p == Instance.class) return (Class<V>) InstanceVisual.class; if (p == Tile.class) return (Class<V>) TileVisual.class; throw new IllegalArgumentException(); } public Iterator<Tile> intersectTiles(Rectangle r, int depth) { return intersect(r,Tile.class,depth); } public Iterator<Instance> intersectInstances(Rectangle r) { return intersect(r,Instance.class); } public Iterator<Piece> intersect(Rectangle r) { return intersect(r,Piece.class); } private static void paintBackground(Graphics g, BackgroundDef bd, int width, int height) { Rectangle c = g.getClipBounds(); ResourceReference<Background> rb = bd.properties.get(PBackgroundDef.BACKGROUND); Background b = Util.deRef(rb); if (b == null) return; BufferedImage bi = b.getDisplayImage(); if (bi == null) return; boolean stretch = bd.properties.get(PBackgroundDef.STRETCH); int w = stretch ? width : bi.getWidth(); int h = stretch ? height : bi.getHeight(); boolean tileHoriz = bd.properties.get(PBackgroundDef.TILE_HORIZ); boolean tileVert = bd.properties.get(PBackgroundDef.TILE_VERT); int x = bd.properties.get(PBackgroundDef.X); int y = bd.properties.get(PBackgroundDef.Y); if (tileHoriz || tileVert) { int ncol = 1; int nrow = 1; if (tileHoriz) { x = 1 + c.x + ((x + w - 1 - c.x) % w) - w; ncol = 1 + (c.x + c.width - x - 1) / w; } if (tileVert) { y = 1 + c.y + ((y + h - 1 - c.y) % h) - h; nrow = 1 + (c.y + c.height - y - 1) / h; } for (int row = 0; row < nrow; row++) for (int col = 0; col < ncol; col++) g.drawImage(bi,(x + w * col),(y + h * row),w,h,null); } else g.drawImage(bi,x,y,w,h,null); } private abstract class PieceVisual<P extends Piece> extends VisualBox { protected final ResourceUpdateListener rul = new ResourceUpdateListener(); public final P piece; private boolean invalid; public PieceVisual(P p) { super(binVisual); piece = p; } protected abstract void validate(); protected final void invalidate() { if (invalid) return; invalid = true; SwingUtilities.invokeLater(new Runnable() { public void run() { try { if (invalid) validate(); } finally { invalid = false; } } }); } protected class ResourceUpdateListener implements UpdateListener { public void updated(UpdateEvent e) { invalidate(); } } } // Apply a color filter to an image class ColorFilter extends RGBImageFilter { // The RGB components of the new color byte newColorRed; byte newColorGreen; byte newColorBlue; public ColorFilter(Color color) { newColorRed = (byte) color.getRed(); newColorGreen = (byte) color.getGreen(); newColorBlue = (byte) color.getBlue(); } @Override public int filterRGB(int x, int y, int rgb) { int alpha = (rgb >> 24) & 0xff; int red = (rgb >> 16) & 0xff; int green = (rgb >> 8) & 0xff; int blue = (rgb) & 0xff; // Filter with the new color red = red & newColorRed; green = green & newColorGreen; blue = blue & newColorBlue; // Set the pixel with the new color return (alpha << 24) | (red << 16) | (green << 8) | blue; } } private class InstanceVisual extends PieceVisual<Instance> { private BufferedImage image; private final InstancePropertyListener ipl = new InstancePropertyListener(); // When rotating an instance, used to set the new position private int offsetx = 0, offsety = 0; // Sprite's origin. Used for rotation private int originx = 0, originy = 0; public InstanceVisual(Instance i) { super(i); i.updateSource.addListener(rul); i.properties.updateSource.addListener(ipl); validate(); } @Override protected void validate() { ResourceReference<GmObject> ro = piece.properties.get(PInstance.OBJECT); GmObject o = ro == null ? null : ro.get(); ResourceReference<Sprite> rs = null; if (o != null) rs = o.get(PGmObject.SPRITE); Sprite s = rs == null ? null : rs.get(); image = s == null ? null : s.getDisplayImage(); if (image == null) image = EMPTY_IMAGE; // Get sprite's origin if (s != null) { originx = (Integer) s.get(PSprite.ORIGIN_X); originy = (Integer) s.get(PSprite.ORIGIN_Y); } else { originx = 0; originy = 0; } Point2D scale = piece.getScale(); Point position = piece.getPosition(); if (s != null) position.translate(-(int) (originx * scale.getX()),-(int) (originy * scale.getY())); // Get instance's properties double angle = piece.getRotation(); int newWidth = image.getWidth(); int newHeight = image.getHeight(); int borderOffsetx = 0; int borderOffsety = 0; // If the instance is selected use bigger bounds for border, and make sure the instance is visible if (piece.isSelected()) { binVisual.setDepth(this,o == null ? 0 : Integer.MIN_VALUE,true); newWidth += 4; newHeight += 4; borderOffsetx = (int) (2 * scale.getX()); borderOffsety = (int) (2 * scale.getY()); } else { binVisual.setDepth(this,o == null ? 0 : (Integer) o.get(PGmObject.DEPTH),false); } // Apply scaling if (scale.getX() != 1.0 || scale.getY() != 1.0) { newWidth *= scale.getX(); newHeight *= scale.getY(); } // Calculate the new bounds when there is a rotation if (angle != 0) { AffineTransform at = new AffineTransform(); // Create a rectangle with image's size Rectangle myRect = new Rectangle(position.x,position.y,newWidth,newHeight); // Apply the rotation at = AffineTransform.getRotateInstance(Math.toRadians(-angle), position.x + originx * scale.getX(),position.y + originy * scale.getY()); Shape rotatedRect = at.createTransformedShape(myRect); // Use a rectangle2D and round manually values with Math.round. getBounds doesn't give correct rounded values. Rectangle2D newBounds2D = rotatedRect.getBounds2D(); newWidth = (int) Math.round(newBounds2D.getWidth()); newHeight = (int) Math.round(newBounds2D.getHeight()); offsetx = (int) Math.round(newBounds2D.getX()) - position.x; offsety = (int) Math.round(newBounds2D.getY()) - position.y; } else { offsetx = 0; offsety = 0; } setBounds(new Rectangle(position.x + offsetx - borderOffsetx,position.y + offsety - borderOffsety,newWidth,newHeight)); } public void paint(Graphics g) { if (show.contains(Show.INSTANCES)) { Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); boolean rotationOrScaling = false; // Get instance's properties Point2D scale = piece.getScale(); double rotation = piece.getRotation(); int alpha = piece.getAlpha(); // If there is a rotation or a scaling, the border size is different if (rotation != 0 || scale.getX() != 1.0 || scale.getY() != 1.0) rotationOrScaling = true; // Apply scaling, rotation and translation if (offsetx != 0 || offsety != 0) g2.translate(-offsetx,-offsety); if (rotation != 0) g2.rotate(Math.toRadians(-rotation),originx * scale.getX(),originy * scale.getY()); if (scale.getX() != 1.0 || scale.getY() != 1.0) g2.scale(scale.getX(),scale.getY()); Image newImage; Color selectedColor = piece.getAWTColor(); // If a color has been selected, apply color blending if (!Color.WHITE.equals(selectedColor)) { ImageFilter filter = new ColorFilter(selectedColor); FilteredImageSource filteredSrc = new FilteredImageSource(image.getSource(),filter); newImage = Toolkit.getDefaultToolkit().createImage(filteredSrc); } else { newImage = image; } // Original composite Composite oc = null; // Apply alpha if (alpha > 0 && alpha < 255) { // Save the original composite oc = g2.getComposite(); AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) (alpha / 255.0)); g2.setComposite(ac); } // Draw the instance if (piece.isSelected()) g2.drawImage((image == EMPTY_IMAGE || alpha == 0) ? EMPTY_SPRITE.getImage() : newImage,2, 2,null); else g2.drawImage((image == EMPTY_IMAGE || alpha == 0) ? EMPTY_SPRITE.getImage() : newImage,0, 0,null); // If the instance is selected, display a border around it if (piece.isSelected()) { // If there was an alpha filtering, remove it if (oc != null) g2.setComposite(oc); // If the option 'Invert colors' is set if (Prefs.useInvertedColorForSelection) g2.setXORMode(Util.convertGmColorWithAlpha(Prefs.selectionInsideColor)); else g2.setColor(Util.convertGmColorWithAlpha(Prefs.selectionInsideColor)); // If the option 'Fill rectangle' is set if (Prefs.useFilledRectangleForSelection) { g2.fillRect(1,1,image.getWidth() + 2,image.getHeight() + 2); } else { if (rotationOrScaling == false) g2.drawRect(1,1,image.getWidth() + 1,image.getHeight() + 1); else g2.drawRect(1,1,image.getWidth() + 2,image.getHeight() + 2); } // If the option 'Invert colors' is set if (Prefs.useInvertedColorForSelection) g2.setXORMode(Util.convertGmColorWithAlpha(Prefs.selectionOutsideColor)); else g2.setColor(Util.convertGmColorWithAlpha(Prefs.selectionOutsideColor)); // Draw the outside border if (rotationOrScaling == false) g2.drawRect(0,0,image.getWidth() + 3,image.getHeight() + 3); else g2.drawRect(0,0,image.getWidth() + 4,image.getHeight() + 4); } } } @Override public void remove() { piece.updateSource.removeListener(rul); piece.properties.updateSource.removeListener(ipl); image = null; super.remove(); } class InstancePropertyListener extends PropertyUpdateListener<PInstance> { @Override public void updated(PropertyUpdateEvent<PInstance> e) { switch (e.key) { case X: case Y: case OBJECT: invalidate(); break; default: break; } } } } private class TileVisual extends PieceVisual<Tile> { private BufferedImage image; private final TilePropertyListener tpl = new TilePropertyListener(); public TileVisual(Tile t) { super(t); t.updateSource.addListener(rul); t.properties.updateSource.addListener(tpl); validate(); } @Override protected void validate() { ResourceReference<Background> rb = piece.properties.get(PTile.BACKGROUND); Background b = rb == null ? null : rb.get(); BufferedImage bi = b == null ? null : b.getDisplayImage(); if (bi == null) image = EMPTY_IMAGE; else { Point p = piece.getBackgroundPosition(); Dimension d = piece.getSize(); try { image = bi.getSubimage(p.x,p.y,d.width,d.height); } catch (RasterFormatException e) { image = EMPTY_IMAGE; } } // If the tile is selected use bigger bounds for border if (piece.isSelected()) { binVisual.setDepth(this,piece.getDepth(),true); Point piecePosition = piece.getPosition(); Dimension pieceSize = piece.getSize(); setBounds(new Rectangle(piecePosition.x - 2,piecePosition.y - 2,pieceSize.width + 4, pieceSize.height + 4)); } else { binVisual.setDepth(this,piece.getDepth(),false); Rectangle r = new Rectangle(piece.getPosition(),piece.getSize()); setBounds(r); } } public void paint(Graphics g) { if (show.contains(Show.TILES)) { Graphics2D g2 = (Graphics2D) g; // If we display only the visible layer, test if the current tile is in the visible layer if (visibleLayer != null && piece.getDepth() != visibleLayer) return; // If the tile is selected, display a border around it if (piece.isSelected()) { g2.drawImage(image,2,2,null); // If the option 'Invert colors' is set if (Prefs.useInvertedColorForSelection) g2.setXORMode(Util.convertGmColorWithAlpha(Prefs.selectionInsideColor)); else g2.setColor(Util.convertGmColorWithAlpha(Prefs.selectionInsideColor)); // If the option 'Fill rectangle' is set if (Prefs.useFilledRectangleForSelection) g2.fillRect(1,1,image.getWidth() + 2,image.getHeight() + 2); else g2.drawRect(1,1,image.getWidth() + 1,image.getHeight() + 1); // If the option 'Invert colors' is set if (Prefs.useInvertedColorForSelection) g2.setXORMode(Util.convertGmColorWithAlpha(Prefs.selectionOutsideColor)); else g2.setColor(Util.convertGmColorWithAlpha(Prefs.selectionOutsideColor)); // Draw the outside border g2.drawRect(0,0,image.getWidth() + 3,image.getHeight() + 3); } else { g.drawImage(image,0,0,null); } } } @Override public void remove() { piece.updateSource.removeListener(rul); piece.properties.updateSource.removeListener(tpl); image = null; super.remove(); } class TilePropertyListener extends PropertyUpdateListener<PTile> { @Override public void updated(PropertyUpdateEvent<PTile> e) { switch (e.key) { case DEPTH: case ROOM_X: case ROOM_Y: invalidate(); break; default: break; } } } } private class InstanceVisualListManager extends VisualListManager<Instance,InstanceVisual> { public InstanceVisualListManager() { super(room.instances); } @Override protected InstanceVisual createVisual(Instance t) { return new InstanceVisual(t); } @Override protected Instance getT(InstanceVisual v) { return v.piece; } } private class TileVisualListManager extends VisualListManager<Tile,TileVisual> { public TileVisualListManager() { super(room.tiles); } @Override protected TileVisual createVisual(Tile t) { return new TileVisual(t); } @Override protected Tile getT(TileVisual v) { return v.piece; } } private abstract static class VisualListManager<T, V extends VisualBox> implements UpdateListener { public final ActiveArrayList<T> tList; private final ArrayList<V> vList; public VisualListManager(ActiveArrayList<T> tl) { tList = tl; vList = new ArrayList<V>(tl.size()); for (T t : tl) vList.add(createVisual(t)); tl.updateSource.addListener(this); } protected abstract V createVisual(T t); protected abstract T getT(V v); public void updated(UpdateEvent e) { ListUpdateEvent lue = (ListUpdateEvent) e; switch (lue.type) { case ADDED: for (int i = lue.fromIndex; i <= lue.toIndex; i++) { T t = tList.get(i); V v = createVisual(t); vList.add(i,v); } break; case REMOVED: for (int i = lue.toIndex; i >= lue.fromIndex; i--) vList.remove(i).remove(); break; case CHANGED: HashSet<T> ts = new HashSet<T>(tList); HashMap<T,V> tm = new HashMap<T,V>(Math.min(vList.size(),tList.size())); for (V v : vList) { T t = getT(v); if (ts.contains(t)) tm.put(t,v); else v.remove(); } vList.clear(); for (T t : tList) { V v = tm.get(t); vList.add(v == null ? createVisual(t) : v); } } assert tList.size() == vList.size(); } } private class RoomPropertyListener extends PropertyUpdateListener<PRoom> { @Override public void updated(PropertyUpdateEvent<PRoom> e) { switch (e.key) { case BACKGROUND_COLOR: if (room.get(PRoom.DRAW_BACKGROUND_COLOR)) repaint(null); break; case DRAW_BACKGROUND_COLOR: repaint(null); break; case VIEWS_ENABLED: if (show.contains(Show.VIEWS) || viewsVisible) repaint(null); break; case ISOMETRIC: gridVisual.setRhombic((Boolean) room.get(PRoom.ISOMETRIC)); if (show.contains(Show.GRID)) repaint(null); break; case SNAP_X: gridVisual.setWidth(gridFactor * (Integer) room.get(PRoom.SNAP_X)); if (show.contains(Show.GRID)) repaint(null); break; case SNAP_Y: gridVisual.setHeight(gridFactor * (Integer) room.get(PRoom.SNAP_Y)); if (show.contains(Show.GRID)) repaint(null); break; case WIDTH: case HEIGHT: parent.updateBounds(); break; default: break; } } } // Class which manages the update of view's properties private class ViewPropertyListener extends PropertyUpdateListener<PView> { @Override public void updated(PropertyUpdateEvent<PView> e) { // Update the display of the view only when updating the position or the size of the view switch (e.key) { case VISIBLE: case VIEW_X: case VIEW_Y: case VIEW_W: case VIEW_H: case BORDER_H: case BORDER_V: repaint(null); default: break; } } } private class BgDefPropertyListener extends PropertyUpdateListener<PBackgroundDef> { @Override public void updated(PropertyUpdateEvent<PBackgroundDef> e) { boolean bg = show.contains(Show.BACKGROUNDS); boolean fg = show.contains(Show.FOREGROUNDS); if (!bg && !fg) return; switch (e.key) { case FOREGROUND: if (!(Boolean) e.map.get(PBackgroundDef.VISIBLE)) return; case VISIBLE: repaint(null); case H_SPEED: case V_SPEED: return; default: break; } if (e.map.get(PBackgroundDef.VISIBLE)) if ((bg && fg) || (e.map.get(PBackgroundDef.FOREGROUND) ? fg : bg)) repaint(null); } } public void updated(UpdateEvent e) { if (e.source.owner instanceof BackgroundDef) { boolean bg = show.contains(Show.BACKGROUNDS); boolean fg = show.contains(Show.FOREGROUNDS); if (!bg && !fg) return; BackgroundDef bd = (BackgroundDef) e.source.owner; if (bd.properties.get(PBackgroundDef.VISIBLE)) if ((bg && fg) || (bd.properties.get(PBackgroundDef.FOREGROUND) ? fg : bg)) repaint(null); } } }