JScratch
Loading...
Searching...
No Matches
CostumeEditorPanel.java
Go to the documentation of this file.
1package com.jscratch.ui;
2
3import com.jscratch.WorkspaceManager;
4import javax.swing.*;
5import java.awt.*;
6import java.awt.event.*;
7import java.awt.image.BufferedImage;
8import java.io.File;
9import java.io.IOException;
10import java.nio.file.Files;
11import java.nio.file.StandardCopyOption;
12import java.util.List;
13import javax.imageio.ImageIO;
14
15public class CostumeEditorPanel extends JPanel {
16 private BufferedImage currentImage;
17 private String currentCostumeName;
18 private Color currentColor = Color.BLACK;
19 private int brushSize = 5;
20 private String currentTool = "Brush";
21 private double zoom = 1.0;
22
23 private JPanel canvas;
24 private DefaultListModel<String> listModel = new DefaultListModel<>();
25 private JList<String> costumeList = new JList<>(listModel);
26 private List<String> costumePaths;
27
28 private JSpinner widthSpinner, heightSpinner;
29
30 public CostumeEditorPanel(List<String> costumePaths) {
31 this.costumePaths = costumePaths;
32 setLayout(new BorderLayout());
33
34 // --- Left Sidebar (Costume List) ---
35 JPanel leftPanel = new JPanel(new BorderLayout());
36 leftPanel.setPreferredSize(new Dimension(200, 0));
37 leftPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, new Color(220, 220, 220)));
38 leftPanel.setBackground(new Color(249, 249, 249));
39
41
42 leftPanel.add(new JScrollPane(costumeList), BorderLayout.CENTER);
43
44 JPanel btnPanel = new JPanel(new GridLayout(3, 1, 5, 5));
45 btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
46 btnPanel.setOpaque(false);
47
48 JButton refreshBtn = new JButton("Refresh Assets");
49 refreshBtn.addActionListener(e -> refreshCostumeList());
50
51 JButton addBtn = new JButton("Paint New");
52 addBtn.addActionListener(e -> createNewCostume());
53
54 JButton importBtn = new JButton("Import Image");
55 importBtn.addActionListener(e -> importCostume());
56
57 btnPanel.add(refreshBtn);
58 btnPanel.add(addBtn);
59 btnPanel.add(importBtn);
60 leftPanel.add(btnPanel, BorderLayout.SOUTH);
61
62 // --- Toolbar ---
63 JPanel toolbar = new JPanel(new FlowLayout(FlowLayout.LEFT));
64 toolbar.setBackground(new Color(240, 240, 240));
65
66 // Tools
67 String[] tools = {"Brush", "Eraser", "Fill"};
68 for (String tool : tools) {
69 JButton b = new JButton(tool);
70 b.addActionListener(e -> currentTool = tool);
71 toolbar.add(b);
72 }
73
74 // Brush Size
75 JSpinner sizeSpinner = new JSpinner(new SpinnerNumberModel(5, 1, 100, 1));
76 sizeSpinner.addChangeListener(e -> brushSize = (int) sizeSpinner.getValue());
77 toolbar.add(new JLabel(" Size:")); toolbar.add(sizeSpinner);
78
79 // Color
80 JButton colorBtn = new JButton("Color");
81 colorBtn.addActionListener(e -> {
82 Color c = JColorChooser.showDialog(this, "Select Color", currentColor);
83 if (c != null) currentColor = c;
84 });
85 toolbar.add(colorBtn);
86
87 // Zoom
88 JButton zoomIn = new JButton("+");
89 JButton zoomOut = new JButton("-");
90 zoomIn.addActionListener(e -> { zoom *= 1.2; updateCanvasLayout(); });
91 zoomOut.addActionListener(e -> { zoom /= 1.2; updateCanvasLayout(); });
92 toolbar.add(new JLabel(" Zoom:")); toolbar.add(zoomIn); toolbar.add(zoomOut);
93
94 // Canvas Size
95 widthSpinner = new JSpinner(new SpinnerNumberModel(480, 1, 2000, 10));
96 heightSpinner = new JSpinner(new SpinnerNumberModel(360, 1, 2000, 10));
97 JButton resizeBtn = new JButton("Resize");
98 resizeBtn.addActionListener(e -> resizeImage((int)widthSpinner.getValue(), (int)heightSpinner.getValue()));
99
100 toolbar.add(new JLabel(" W:")); toolbar.add(widthSpinner);
101 toolbar.add(new JLabel(" H:")); toolbar.add(heightSpinner);
102 toolbar.add(resizeBtn);
103
104 // Save
105 JButton saveBtn = new JButton("Save To Assets");
106 saveBtn.addActionListener(e -> saveCurrentImage());
107 toolbar.add(Box.createHorizontalStrut(10));
108 toolbar.add(saveBtn);
109
110 // --- Canvas ---
111 canvas = new JPanel() {
112 @Override
113 protected void paintComponent(Graphics g) {
114 super.paintComponent(g);
115 if (currentImage != null) {
116 Graphics2D g2 = (Graphics2D) g;
117 int drawW = (int)(currentImage.getWidth() * zoom);
118 int drawH = (int)(currentImage.getHeight() * zoom);
119
120 g2.setColor(Color.WHITE);
121 g2.fillRect(0, 0, drawW, drawH);
122 g2.drawImage(currentImage, 0, 0, drawW, drawH, null);
123 g2.setColor(new Color(200, 200, 200));
124 g2.drawRect(0, 0, drawW, drawH);
125 }
126 }
127 };
128 canvas.setBackground(new Color(230, 230, 230));
129
130 MouseAdapter ma = new MouseAdapter() {
131 @Override public void mousePressed(MouseEvent e) { useTool(e.getPoint()); }
132 @Override public void mouseDragged(MouseEvent e) { useTool(e.getPoint()); }
133
134 private void useTool(Point p) {
135 if (currentImage == null) return;
136 int imgX = (int)(p.x / zoom);
137 int imgY = (int)(p.y / zoom);
138 if (imgX < 0 || imgY < 0 || imgX >= currentImage.getWidth() || imgY >= currentImage.getHeight()) return;
139
140 Graphics2D g2 = currentImage.createGraphics();
141 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
142
143 if (currentTool.equals("Brush")) {
144 g2.setComposite(AlphaComposite.SrcOver);
145 g2.setColor(currentColor);
146 g2.fillOval(imgX - brushSize/2, imgY - brushSize/2, brushSize, brushSize);
147 } else if (currentTool.equals("Eraser")) {
148 g2.setComposite(AlphaComposite.Clear);
149 g2.fillOval(imgX - brushSize/2, imgY - brushSize/2, brushSize, brushSize);
150 } else if (currentTool.equals("Fill")) {
151 g2.setComposite(AlphaComposite.SrcOver);
152 g2.setColor(currentColor);
153 g2.fillRect(0, 0, currentImage.getWidth(), currentImage.getHeight());
154 }
155 g2.dispose();
156 canvas.repaint();
157 }
158 };
159 canvas.addMouseListener(ma);
160 canvas.addMouseMotionListener(ma);
161
162 costumeList.addListSelectionListener(e -> {
163 if (!e.getValueIsAdjusting() && costumeList.getSelectedIndex() != -1) {
164 String name = listModel.getElementAt(costumeList.getSelectedIndex());
165 File f = new File(WorkspaceManager.getAssetsDir(), name);
166 loadCostume(f.getAbsolutePath());
167 }
168 });
169
170 add(leftPanel, BorderLayout.WEST);
171 add(toolbar, BorderLayout.NORTH);
172 add(new JScrollPane(canvas), BorderLayout.CENTER);
173
175 }
176
177 private void refreshCostumeList() {
178 listModel.clear();
179 File assetsDir = WorkspaceManager.getAssetsDir();
180 if (assetsDir.exists()) {
181 File[] files = assetsDir.listFiles((dir, name) -> {
182 String lower = name.toLowerCase();
183 return lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg");
184 });
185 if (files != null) {
186 for (File f : files) {
187 if (!listModel.contains(f.getName())) {
188 listModel.addElement(f.getName());
189 if (!costumePaths.contains(f.getAbsolutePath())) {
190 costumePaths.add(f.getAbsolutePath());
191 }
192 }
193 }
194 }
195 }
196 }
197
198 private void updateCanvasLayout() {
199 if (currentImage != null) {
200 canvas.setPreferredSize(new Dimension((int)(currentImage.getWidth() * zoom), (int)(currentImage.getHeight() * zoom)));
201 canvas.revalidate();
202 canvas.repaint();
203 }
204 }
205
206 private void resizeImage(int newW, int newH) {
207 if (currentImage == null) return;
208 BufferedImage newImg = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB);
209 Graphics2D g = newImg.createGraphics();
210 g.drawImage(currentImage, 0, 0, null);
211 g.dispose();
212 currentImage = newImg;
214 }
215
216 private void createNewCostume() {
217 int w = (int)widthSpinner.getValue();
218 int h = (int)heightSpinner.getValue();
219 currentImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
220 currentCostumeName = "costume" + (listModel.size() + 1) + ".png";
222 }
223
224 private void importCostume() {
225 JFileChooser fc = new JFileChooser(WorkspaceManager.getRoot());
226 if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
227 try {
228 File src = fc.getSelectedFile();
229 File dest = new File(WorkspaceManager.getAssetsDir(), src.getName());
230 Files.copy(src.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
231
233 loadCostume(dest.getAbsolutePath());
234 } catch (IOException e) {
235 JOptionPane.showMessageDialog(this, "Error importing: " + e.getMessage());
236 }
237 }
238 }
239
240 private void loadCostume(String path) {
241 try {
242 File f = new File(path);
243 if (f.exists()) {
244 currentImage = ImageIO.read(f);
245 currentCostumeName = f.getName();
246 widthSpinner.setValue(currentImage.getWidth());
247 heightSpinner.setValue(currentImage.getHeight());
249 }
250 } catch (IOException e) {
251 e.printStackTrace();
252 }
253 }
254
255 private void saveCurrentImage() {
256 if (currentImage == null) return;
257 String name = JOptionPane.showInputDialog(this, "Save as (e.g. hero.png):", currentCostumeName);
258 if (name == null || name.trim().isEmpty()) return;
259 if (!name.toLowerCase().endsWith(".png")) name += ".png";
260
261 try {
262 File dest = new File(WorkspaceManager.getAssetsDir(), name);
263 ImageIO.write(currentImage, "PNG", dest);
264 currentCostumeName = name;
266 JOptionPane.showMessageDialog(this, "Saved to assets/" + name);
267 } catch (IOException e) {
268 JOptionPane.showMessageDialog(this, "Error saving: " + e.getMessage());
269 }
270 }
271}
272
CostumeEditorPanel(List< String > costumePaths)
DefaultListModel< String > listModel