/*******************************************************************************
* Rhythos Editor is a game editor and project management tool for making RPGs on top of the Rhythos Game system.
*
* Copyright (C) 2013 David Maletz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package mrpg.editor;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.JPanel;
import javax.swing.Scrollable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import mrpg.editor.resource.Project;
import mrpg.editor.resource.TileResource;
import mrpg.world.BasicTilemap;
import mrpg.world.Direction;
import mrpg.world.Tile;
import mrpg.world.Tilemap;
public class TilesetViewer extends JPanel implements MouseListener, MouseMotionListener, Scrollable, Iterable<Tile> {
private static final long serialVersionUID = -782835855502107652L;
private final Rectangle rect = new Rectangle(); public static final int TILE_SIZE = 32;
private BasicTilemap mainMap = null; private ArrayList<Tilemap> extraTiles = null;
private int selX1 = 0, selY1 = 0, selX2 = 0, selY2 = 0; private boolean show_grid = false;
public static final Color selectColor1 = new Color(250,248,160), selectColor2 = new Color(192,237,254);
public WorldOverlay overlay = null;
public TilesetViewer(){addMouseListener(this); addMouseMotionListener(this);}
private void computeSize(){
Dimension d = new Dimension(TILE_SIZE*8, 0);
if(mainMap != null){
d.width = mainMap.getTilesX()*TILE_SIZE;
d.height += mainMap.getTilesY()*TILE_SIZE;
}
if(extraTiles != null){
d.height += (int)Math.ceil(extraTiles.size()*1.0/(d.width/TILE_SIZE))*TILE_SIZE;
}
if(show_grid){d.width += 1; d.height += 1;}
setMinimumSize(d); setPreferredSize(d);
revalidate(); repaint();
}
public void setShowGrid(boolean b){show_grid = b; computeSize();}
private Project project = null, cur_project = null;
private int getTileSize(){if(cur_project == null) return TILE_SIZE; else return cur_project.tile_size;}
public void setProject(Project p){
if(project != p){
if(project != null && p != null){selX1 = 0; selY1 = 0; selX2 = 0; selY2 = 0; mainMap = null; extraTiles = null; computeSize();}
project = p;
}
}
public void refresh(){
if(project == null) return; try{
if(mainMap != null) mainMap = (BasicTilemap)mainMap.getResource().getTilemap();
if(extraTiles != null) for(int i=0; i<extraTiles.size(); i++) extraTiles.set(i, extraTiles.get(i).getResource().getTilemap());
computeSize();
} catch(Exception e){}
}
public void setTilemap(BasicTilemap map, Project p){
if(mainMap == map || (project != null && project != p)) return; cur_project = p;
selX1 = 0; selY1 = 0; selX2 = 0; selY2 = 0; mainMap = map; computeSize();
}
public boolean removeTilemap(BasicTilemap map){
if(mainMap == map){selX1 = 0; selY1 = 0; selX2 = 0; selY2 = 0; mainMap = null; computeSize(); return true;} else return false;
}
public BasicTilemap getTilemap(){return mainMap;}
public void toggleAutoTile(Tilemap m, Project p){
if(!m.indexNeighbors() || (project != null && project != p)) return; cur_project = p;
if(extraTiles == null) extraTiles = new ArrayList<Tilemap>();
int idx = extraTiles.indexOf(m); if(idx != -1) extraTiles.remove(idx); else extraTiles.add(m); computeSize();
}
public void addAutoTile(Tilemap m, Project p){
if(!m.indexNeighbors() || (project != null && project != p)) return; cur_project = p;
if(extraTiles == null) extraTiles = new ArrayList<Tilemap>();
extraTiles.add(m); computeSize(); computeSize();
}
public boolean removeAutoTile(Tilemap m){
if(extraTiles == null) return false; boolean ret = extraTiles.remove(m); computeSize(); return ret;
}
public void addElements(Document doc, Element element, Project p){
if(project != p) return;
if(mainMap != null){
TileResource r = mainMap.getResource(); Element elem = doc.createElement("tileset"); elem.setAttribute("type", r.getType());
elem.setTextContent(Long.toHexString(r.getId())); element.appendChild(elem);
} if(extraTiles != null) for(Tilemap t : extraTiles){
TileResource r = t.getResource(); Element elem = doc.createElement("autotile"); elem.setAttribute("type", r.getType());
elem.setTextContent(Long.toHexString(r.getId())); element.appendChild(elem);
}
}
public void paint(Graphics g){
int ht = 0, tile_size = getTileSize(); Graphics2D g2 = (Graphics2D)g.create(); double s = ((double)TILE_SIZE)/tile_size;
g2.scale(s,s); g2.getClipBounds(rect); g2.clearRect(rect.x, rect.y, rect.width, rect.height);
if(mainMap != null){
ht = mainMap.getTilesY()*tile_size;
int w = Math.min(rect.x+rect.width, mainMap.getTilesX()*tile_size)-rect.x;
int h = Math.min(rect.y+rect.height, ht)-rect.y;
if(w > 0 && h > 0)
g2.drawImage(mainMap.getTile(0,0).image, rect.x, rect.y, rect.x+w, rect.y+h, rect.x, rect.y, rect.x+w, rect.y+h, this);
}
if(extraTiles != null){
int width = getPreferredSize().width/tile_size;
int stx = Math.max(0, (int)Math.floor(((double)(rect.x))/tile_size)),
sty = Math.max(0, (int)Math.floor(((double)(rect.y-ht))/tile_size)),
endx = Math.min(width-1, (int)Math.floor(((double)(rect.x+rect.width))/tile_size)),
endy = Math.min((int)Math.ceil(extraTiles.size()*1.0/width)-1, (int)Math.floor(((double)(rect.y+rect.height-ht))/tile_size));
for(int y=sty; y<=endy; y++)
for(int x=stx; x<=endx; x++){
int i = y*width+x; if(i >= extraTiles.size()) continue;
int _x = x*tile_size, _y = ht+y*tile_size, _w = tile_size, _h = tile_size;
int sx = 0, sy = 0, rw = rect.x+rect.width, rh = rect.y+rect.height;
if(_x < rect.x){sx += rect.x-_x; _w -= sx; _x = rect.x;}
if(_x+_w > rw) _w -= _x+_w-rw;
if(_y < rect.y){sy += rect.y-_y; _h -= sy; _y = rect.y;}
if(_y+_h > rh) _h -= _y+_h-rh;
extraTiles.get(i).getTile(Direction.NONE).paint(g2, _x, _y, sx, sy, _w, _h, this);
}
}
g.getClipBounds(rect);
if(mainMap != null || extraTiles != null){
g.setColor(Color.black);
if(show_grid){
Dimension d = getPreferredSize();
int rw = Math.min(d.width, rect.x+rect.width), rh = Math.min(d.height, rect.y+rect.height);
for(int y=(int)Math.ceil(rect.y*1.0/TILE_SIZE)*TILE_SIZE; y<rh; y+=TILE_SIZE)
g.drawLine(rect.x, y, rw-1, y);
for(int x=(int)Math.ceil(rect.x*1.0/TILE_SIZE)*TILE_SIZE; x<rw; x+=TILE_SIZE)
g.drawLine(x, rect.y, x, rh-1);
}
int x = Math.min(selX1, selX2), y = Math.min(selY1, selY2), w = Math.abs(selX1-selX2)+1, h = Math.abs(selY1-selY2)+1;
g.drawRect(x*TILE_SIZE+3, y*TILE_SIZE+3, w*TILE_SIZE-6, h*TILE_SIZE-6);
g.setColor(selectColor2);
g.drawRect(x*TILE_SIZE+1, y*TILE_SIZE+1, w*TILE_SIZE-2, h*TILE_SIZE-2);
g.setColor(selectColor1);
g.drawRect(x*TILE_SIZE+2, y*TILE_SIZE+2, w*TILE_SIZE-4, h*TILE_SIZE-4);
}
}
private Tile getTile(int x, int y){
int w = 8, h = 0;
if(mainMap != null){
w = mainMap.getTilesX();
if(x >= w) return Tile.empty;
h = mainMap.getTilesY();
if(y < h) return mainMap.getTile(x, y);
} else if(x >= w) return Tile.empty;
if(extraTiles != null){
int i = (y-h)*w+x;
if(i < extraTiles.size()) return extraTiles.get(i).getTile(Direction.NONE);
}
return Tile.empty;
}
public Tile getSelectedTile(){return getTile(selX1, selY1);}
public Tile getSelectedTile(int dx, int dy){return getTile(selX1+dx, selY1+dy);}
public SelectionIterator getSelectedTiles(){return new SelectionIterator(selX1, selY1, selX2, selY2);}
public SelectionIterator iterator(){return new SelectionIterator(selX1, selY1, selX2, selY2);}
public class SelectionIterator implements Iterator<Tile>{
private int stSelX, curSelX, endSelX, stSelY, curSelY, endSelY;
public SelectionIterator(int selX1, int selY1, int selX2, int selY2){
curSelX = Math.min(selX1, selX2); stSelX = curSelX; endSelX = Math.max(selX1, selX2);
curSelY = Math.min(selY1, selY2); stSelY = curSelY; endSelY = Math.max(selY1, selY2);
}
public boolean hasNext() {return curSelX <= endSelX && curSelY <= endSelY;}
public Tile next() {
Tile ret = getTile(curSelX, curSelY);
curSelX++; if(curSelX > endSelX){curSelX = stSelX; curSelY++;}
return ret;
}
public int deltaX(){return curSelX-stSelX;}
public int deltaY(){return curSelY-stSelY;}
public void remove() {}
}
public int getSelectionWidth(){return Math.abs(selX1-selX2)+1;}
public int getSelectionHeight(){return Math.abs(selY1-selY2)+1;}
public void mouseDragged(MouseEvent e) {
Dimension size = getPreferredSize();
int x = Math.min(size.width-TILE_SIZE, Math.max(0, e.getX()))/TILE_SIZE,
y = Math.min(size.height-TILE_SIZE, Math.max(0, e.getY()))/TILE_SIZE;
if(selX2 != x || selY2 != y){
int ox = selX2, oy = selY2;
selX2 = x; selY2 = y;
if(overlay != null) overlay.updateSelection(Math.abs(selX1-ox)+1, Math.abs(selX2-oy)+1);
repaint();
}
}
public void mouseMoved(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mousePressed(MouseEvent e) {
overlay.getEditor().removeFocus();
int osw = getSelectionWidth(), osh = getSelectionHeight();
Dimension size = getPreferredSize();
int x = Math.min(size.width-TILE_SIZE, Math.max(0, e.getX()))/TILE_SIZE,
y = Math.min(size.height-TILE_SIZE, Math.max(0, e.getY()))/TILE_SIZE;
selX1 = x; selY1 = y; selX2 = x; selY2 = y;
if(overlay != null) overlay.updateSelection(osw, osh);
repaint();
}
public void mouseReleased(MouseEvent e) {}
public int getScrollableUnitIncrement(Rectangle r, int o, int d){return TILE_SIZE;}
public int getScrollableBlockIncrement(Rectangle r, int o, int d){return TILE_SIZE;}
public Dimension getPreferredScrollableViewportSize(){return getPreferredSize();}
public boolean getScrollableTracksViewportWidth(){return false;}
public boolean getScrollableTracksViewportHeight(){return false;}
}