Files
ATCS/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpritesheetEditor.java
Zukero 6ac332834d Progress on WriterMode. Project export package now create
deterministically-ordered JSON. Should make nicer diffs.
2016-12-15 19:16:53 +01:00

432 lines
14 KiB
Java

package com.gpl.rpg.atcontentstudio.ui.sprites;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import com.gpl.rpg.atcontentstudio.ATContentStudio;
import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode;
import com.gpl.rpg.atcontentstudio.model.maps.TMXMap;
import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet;
import com.gpl.rpg.atcontentstudio.ui.Editor;
import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener;
import com.jidesoft.swing.JideBoxLayout;
import com.jidesoft.swing.JideTabbedPane;
public class SpritesheetEditor extends Editor {
private static final long serialVersionUID = 3956109815682889863L;
Map<String, JPanel> editorTabs = new LinkedHashMap<String, JPanel>();
JideTabbedPane editorTabsHolder;
private JSpinner widthField;
private JSpinner heightField;
private JCheckBox animatedBox;
private JComboBox categoryBox;
private JPanel spriteViewPane;
public static JComponent getWarningLabel() {
JLabel label = new JLabel(
"<html><i>" +
"The data presented here is not part of the game.<br/>" +
"What you change here will be changed in your ATCS project.<br/>" +
"None of this is exported to JSON or TMX, although it must be set correctly in order to choose tiles & icons correctly.<br/>" +
"</i></html>");
return label;
}
public SpritesheetEditor(Spritesheet sheet) {
super();
this.icon = new ImageIcon(sheet.getIcon(0));
this.name = sheet.id;
this.target = sheet;
JPanel pane = new JPanel();
final FieldUpdateListener listener = new SpritesheetFieldUpdater();
pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6));
add(getWarningLabel(), JideBoxLayout.FIX);
addLabelField(pane, "Spritesheet ID: ", sheet.id);
addLabelField(pane, "File: ", sheet.spritesheetFile.getAbsolutePath());
widthField = addIntegerField(pane, "Sprite width (px): ", sheet.spriteWidth, false, true, listener);
heightField = addIntegerField(pane, "Sprite height (px): ", sheet.spriteHeight, false, true, listener);
animatedBox = addBooleanBasedCheckBox(pane, "Is an animation", sheet.animated, true, listener);
categoryBox = addEnumValueBox(pane, "Category: ", Spritesheet.Category.values(), sheet.category, true, listener);
spriteViewPane = new JPanel();
updateView(spriteViewPane);
pane.add(spriteViewPane, JideBoxLayout.FIX);
addBacklinksList(pane, sheet);
//Placeholder. Fills the eventual remaining space.
pane.add(new JPanel(), JideBoxLayout.VARY);
setLayout(new BorderLayout());
editorTabsHolder = new JideTabbedPane(JideTabbedPane.BOTTOM);
editorTabsHolder.setTabShape(JideTabbedPane.SHAPE_FLAT);
editorTabsHolder.setUseDefaultShowCloseButtonOnTab(false);
editorTabsHolder.setShowCloseButtonOnTab(false);
add(editorTabsHolder, BorderLayout.CENTER);
JScrollPane sheetScroller = new JScrollPane(pane);
sheetScroller.getVerticalScrollBar().setUnitIncrement(16);
editorTabsHolder.add("Spritesheet",sheetScroller);
JScrollPane rawScroller = new JScrollPane(new JLabel(new ImageIcon(sheet.spritesheet)));
rawScroller.getVerticalScrollBar().setUnitIncrement(16);
editorTabsHolder.add("Raw image", rawScroller);
}
private Thread animator = new Thread();
private boolean animate = true;
private JLabel iconLabel;
private List<ImageIcon> icons = null;
public void updateView(JPanel pane) {
Spritesheet sheet = (Spritesheet)target;
pane.removeAll();
pane.setLayout(new BorderLayout());
if (sheet.animated) {
iconLabel = new JLabel();
iconLabel.setBackground(Color.WHITE);
if (icons == null) {
icons = new ArrayList<ImageIcon>();
} else {
icons.clear();
}
int i = 0;
Image img;
while ((img = sheet.getImage(i++)) != null) {
icons.add(new ImageIcon(img));
}
if (i > 0) {
iconLabel.setIcon(icons.get(0));
}
pane.add(iconLabel, BorderLayout.CENTER);
resetAnimator();
} else {
JTable spritesTable = new JTable(new SpritesheetTableModel(sheet));
spritesTable.setDefaultRenderer(Image.class, new SpritesheetCellRenderer(sheet));
spritesTable.setCellSelectionEnabled(true);
spritesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
spritesTable.getTableHeader().setVisible(false);
Enumeration<TableColumn> columns = spritesTable.getColumnModel().getColumns();
TableColumn col;
while (columns.hasMoreElements()) {
col = columns.nextElement();
col.setMinWidth(sheet.spriteWidth + 4);
col.setMaxWidth(sheet.spriteWidth + 4);
}
spritesTable.setRowHeight(sheet.spriteHeight + 4);
pane.add(new JScrollPane(spritesTable), BorderLayout.CENTER);
}
}
public static JList addBacklinksList(JPanel pane, Spritesheet sheet) {
final JList list = new JList(new SpritesheetsBacklinksListModel(sheet));
list.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
if (list.getSelectedValue() instanceof TMXMap) {
ATContentStudio.frame.openEditor((TMXMap)list.getSelectedValue());
ATContentStudio.frame.selectInTree((TMXMap)list.getSelectedValue());
} else if (list.getSelectedValue() instanceof GameDataElement) {
ATContentStudio.frame.openEditor((GameDataElement)list.getSelectedValue());
ATContentStudio.frame.selectInTree((GameDataElement)list.getSelectedValue());
}
}
}
});
list.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (list.getSelectedValue() instanceof TMXMap) {
ATContentStudio.frame.openEditor((TMXMap)list.getSelectedValue());
ATContentStudio.frame.selectInTree((TMXMap)list.getSelectedValue());
} else if (list.getSelectedValue() instanceof GameDataElement) {
ATContentStudio.frame.openEditor((GameDataElement)list.getSelectedValue());
ATContentStudio.frame.selectInTree((GameDataElement)list.getSelectedValue());
}
}
}
});
list.setCellRenderer(new BacklinkCellRenderer(true));
JScrollPane scroller = new JScrollPane(list);
scroller.setBorder(BorderFactory.createTitledBorder("Elements pointing to this spritesheet."));
pane.add(scroller, JideBoxLayout.FIX);
return list;
}
public static class SpritesheetTableModel implements TableModel {
Spritesheet sheet;
public SpritesheetTableModel(Spritesheet sheet) {
this.sheet = sheet;
}
@Override
public int getRowCount() {
return (sheet.spritesheet.getHeight() / sheet.spriteHeight) + ((sheet.spritesheet.getHeight() % sheet.spriteHeight) == 0 ? 0 : 1);
}
@Override
public int getColumnCount() {
return (sheet.spritesheet.getWidth() / sheet.spriteWidth) + ((sheet.spritesheet.getWidth() % sheet.spriteWidth) == 0 ? 0 : 1);
}
@Override
public String getColumnName(int columnIndex) {
return "";
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return Image.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return sheet.getImage((rowIndex * getColumnCount()) + columnIndex);
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
}
List<TableModelListener> listeners = new ArrayList<TableModelListener>();
@Override
public void addTableModelListener(TableModelListener l) {
listeners.add(l);
}
@Override
public void removeTableModelListener(TableModelListener l) {
listeners.remove(l);
}
}
public static class SpritesheetCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = -4213756343124247612L;
Spritesheet sheet;
public SpritesheetCellRenderer(Spritesheet sheet) {
super();
this.sheet = sheet;
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (c instanceof JLabel) {
((JLabel)c).setText("");
if (value != null) {
((JLabel)c).setIcon(new ImageIcon((Image)value));
((JLabel)c).setToolTipText(sheet.id+":"+((row * table.getColumnCount())+column));
}
}
return c;
}
}
@Override
public void setVisible(boolean aFlag) {
super.setVisible(aFlag);
animate = aFlag;
if (aFlag && animator != null) {
resetAnimator();
}
}
private void resetAnimator() {
new Thread() {
public void run() {
if (animator != null && animator.isAlive()) {
try {
animator.join();
} catch (InterruptedException e) {}
}
animate = true;
animator = new Thread() {
public void run() {
int i = -1;
while (animate) {
if (icons != null) {
synchronized (icons) {
i = (i + 1) % icons.size();
iconLabel.setIcon(icons.get(i));
}
iconLabel.revalidate();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
};
};
animator.start();
}
}.start();
}
public static class SpritesheetsBacklinksListModel implements ListModel {
Spritesheet sheet;
public SpritesheetsBacklinksListModel(Spritesheet sheet) {
this.sheet = sheet;
}
@Override
public int getSize() {
return sheet.getBacklinks().size();
}
@Override
public Object getElementAt(int index) {
for (ProjectTreeNode node : sheet.getBacklinks()) {
if (index == 0) return node;
index --;
}
return null;
}
List<ListDataListener> listeners = new ArrayList<ListDataListener>();
@Override
public void addListDataListener(ListDataListener l) {
listeners.add(l);
}
@Override
public void removeListDataListener(ListDataListener l) {
listeners.remove(l);
}
public void fireListChanged() {
for (ListDataListener l : listeners) {
l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, this.getSize()));
}
}
}
public static class BacklinkCellRenderer extends DefaultListCellRenderer {
private static final long serialVersionUID = 6819681566800482793L;
private boolean includeType = false;
public BacklinkCellRenderer(boolean includeType) {
super();
this.includeType = includeType;
}
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value == null) {
label.setText("none");
} else {
if (includeType && ((ProjectTreeNode)value).getDataType() != null) {
label.setText(((ProjectTreeNode)value).getDataType().toString()+"/"+((ProjectTreeNode)value).getDesc());
} else {
label.setText(((ProjectTreeNode)value).getDesc());
}
if (((ProjectTreeNode)value).getIcon() == null) {
Notification.addError("Unable to find icon for "+((ProjectTreeNode)value).getDesc());
} else {
label.setIcon(new ImageIcon(((ProjectTreeNode)value).getIcon()));
}
}
return label;
}
}
@Override
public void targetUpdated() {
this.icon = new ImageIcon(((Spritesheet)target).getIcon(0));
this.name = ((Spritesheet)target).id;
}
public class SpritesheetFieldUpdater implements FieldUpdateListener {
@Override
public void valueChanged(JComponent source, Object value) {
Spritesheet sheet = (Spritesheet) target;
if (source == widthField) {
sheet.spriteWidth = (Integer) value;
sheet.clearCache();
updateView(spriteViewPane);
spriteViewPane.revalidate();
spriteViewPane.repaint();
} else if (source == heightField) {
sheet.spriteHeight = (Integer) value;
sheet.clearCache();
updateView(spriteViewPane);
spriteViewPane.revalidate();
spriteViewPane.repaint();
} else if (source == animatedBox) {
sheet.animated = (Boolean) value;
if (!sheet.animated) {
animate = false;
}
updateView(spriteViewPane);
spriteViewPane.revalidate();
spriteViewPane.repaint();
} else if (source == categoryBox) {
sheet.category = (Spritesheet.Category) value;
}
}
}
}