JScratch
Loading...
Searching...
No Matches
Stage.java
Go to the documentation of this file.
1package com.jscratch;
2
3import java.awt.AlphaComposite;
4import java.awt.Color;
5import java.awt.Dimension;
6import java.awt.Graphics;
7import java.awt.Graphics2D;
8import java.awt.RenderingHints;
9import java.awt.event.KeyEvent;
10import java.awt.event.KeyListener;
11import java.awt.event.MouseEvent;
12import java.awt.event.MouseListener;
13import java.awt.event.MouseMotionListener;
14import java.awt.image.BufferedImage;
15import java.util.ArrayList;
16import java.util.List;
17import java.util.concurrent.CopyOnWriteArrayList;
18import javax.swing.JFrame;
19import javax.swing.JPanel;
20import javax.swing.JToolBar;
21import javax.swing.JButton;
22import javax.swing.SwingUtilities;
23import javax.swing.Timer;
24import java.awt.BorderLayout;
25
26public class Stage extends JPanel implements KeyListener, MouseListener, MouseMotionListener {
27 public static Stage instance;
28 private JFrame frame;
29 private Timer renderTimer;
30
31 // Direct translations for Scratch concepts
32 private List<Sprite> sprites = new CopyOnWriteArrayList<>();
33 private List<Costume> backdrops = new ArrayList<>();
34 private int backdropIndex = 0;
35
36 // Stage properties
37 private String currentBackdropName = "";
38 private double tempo = 60;
39 private double timerValue = 0;
40 private long startTime;
41
42 // Input state
43 private int mouseX = 0;
44 private int mouseY = 0;
45 private boolean mouseDown = false;
46 private final boolean[] keysDown = new boolean[65536];
47
48 // Frame Sync
49 private java.util.concurrent.CountDownLatch frameLatch = new java.util.concurrent.CountDownLatch(1);
50
51 // Event Listeners
52 private java.util.Map<String, List<Runnable>> broadcastListeners = new java.util.concurrent.ConcurrentHashMap<>();
53 private java.util.Map<Integer, List<Runnable>> keyListeners = new java.util.concurrent.ConcurrentHashMap<>();
54 private List<java.util.function.BiConsumer<Double, Double>> scaleListeners = new java.util.concurrent.CopyOnWriteArrayList<>();
55
56 // Backing ScratchObject for global vars/lists/sounds
58 private double currentScale = 1.0;
59
60 public Stage() {
61 this.stageObject = new ScratchObject("Stage") {};
62
63 this.setPreferredSize(new Dimension(480, 360));
64 this.setBackground(Color.WHITE);
65 this.setFocusable(true);
66 this.addKeyListener(this);
67 this.addMouseListener(this);
68 this.addMouseMotionListener(this);
69
70 renderTimer = new Timer(1000 / 60, e -> {
71 update();
72 repaint();
73 java.util.concurrent.CountDownLatch oldLatch = frameLatch;
74 frameLatch = new java.util.concurrent.CountDownLatch(1);
75 oldLatch.countDown();
76 });
77 renderTimer.start();
78
79 startTime = System.currentTimeMillis();
80 }
81
82 // --- Delegate ScratchObject Methods ---
83 public Object getVariable(String name) { return stageObject.getVariable(name); }
84 public double getVariableAsNumber(String name) { return stageObject.getVariableAsNumber(name); }
85 public String getVariableAsString(String name) { return stageObject.getVariableAsString(name); }
86 public String answer() { return stageObject.answer(); }
87 public void setVariable(String name, Object value) { stageObject.setVariable(name, value); }
88 public void changeVariableBy(String name, double amount) { stageObject.changeVariableBy(name, amount); }
89 public void addToList(String listName, Object item) { stageObject.addToList(listName, item); }
90 public String join(Object a, Object b) { return stageObject.join(a, b); }
91 public void setVolume(double vol) { stageObject.setVolume(vol); }
92 public void playSound(String path) { stageObject.playSound(path); }
93 public void startScript(Runnable script) { stageObject.startScript(script); }
94
95 // --- Broadcasts & Keys ---
96 public void broadcast(String message) {
97 List<Runnable> listeners = broadcastListeners.get(message);
98 if (listeners != null) {
99 for (Runnable r : listeners) {
100 stageObject.startScript(r);
101 }
102 }
103 }
104
105 public void whenIReceive(String message, Runnable action) {
106 broadcastListeners.computeIfAbsent(message, k -> new CopyOnWriteArrayList<>()).add(action);
107 }
108
109 public void whenKeyPressed(int keyCode, Runnable action) {
110 keyListeners.computeIfAbsent(keyCode, k -> new java.util.concurrent.CopyOnWriteArrayList<>()).add(action);
111 }
112
113 public void whenScaleChanged(java.util.function.BiConsumer<Double, Double> action) {
114 scaleListeners.add(action);
115 }
116
117 public boolean isKeyPressed(String keyName) {
118 switch(keyName.toLowerCase()) {
119 case "space": return isKeyPressed(KeyEvent.VK_SPACE);
120 case "up arrow": return isKeyPressed(KeyEvent.VK_UP);
121 case "down arrow": return isKeyPressed(KeyEvent.VK_DOWN);
122 case "right arrow": return isKeyPressed(KeyEvent.VK_RIGHT);
123 case "left arrow": return isKeyPressed(KeyEvent.VK_LEFT);
124 case "any":
125 for(boolean b : keysDown) if(b) return true;
126 return false;
127 default:
128 if(keyName.length() == 1) {
129 char c = keyName.toUpperCase().charAt(0);
130 return isKeyPressed((int)c);
131 }
132 return false;
133 }
134 }
135
136 public void waitNextFrame() {
137 try { frameLatch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
138 }
139
140 public void waitTillNextFrame() {
142 }
143
144 // --- Setup ---
145 public void addSprite(Sprite s) {
146 sprites.add(s);
147 s.setStage(this);
148 }
149
150 public void addBackdrop(Costume c) {
151 backdrops.add(c);
152 if (backdrops.size() == 1) {
154 }
155 }
156
158 if (!backdrops.isEmpty() && backdropIndex < backdrops.size()) {
159 return backdrops.get(backdropIndex);
160 }
161 return null;
162 }
163
164 private void update() {
165 timerValue = (System.currentTimeMillis() - startTime) / 1000.0;
166 }
167
168 @Override
169 protected void paintComponent(Graphics g) {
170 super.paintComponent(g);
171 Graphics2D g2d = (Graphics2D) g;
172 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
173
174 if (!backdrops.isEmpty() && backdropIndex < backdrops.size()) {
175 BufferedImage bg = backdrops.get(backdropIndex).getImage();
176 g2d.drawImage(bg, 0, 0, getWidth(), getHeight(), null);
177 }
178
179 for (Sprite s : sprites) {
180 if (s.isVisible()) {
181 s.draw(g2d);
182 }
183 }
184
185 // Watchers
186 g2d.setColor(new Color(200, 200, 200, 200));
187 int varY = 20;
188 for (java.util.Map.Entry<String, Object> entry : stageObject.variables.entrySet()) {
189 String text = entry.getKey() + ": " + entry.getValue();
190 g2d.fillRoundRect(10, varY - 15, 120, 20, 5, 5);
191 g2d.setColor(Color.BLACK);
192 g2d.drawString(text, 15, varY);
193 g2d.setColor(new Color(200, 200, 200, 200));
194 varY += 25;
195 }
196 }
197
198 public void switchBackdropTo(String name) {
199 for (int i = 0; i < backdrops.size(); i++) {
200 if (backdrops.get(i).getName().equals(name)) {
201 backdropIndex = i;
202 currentBackdropName = name;
203 break;
204 }
205 }
206 repaint();
207 }
208
209 public void nextBackdrop() {
210 backdropIndex = (backdropIndex + 1) % backdrops.size();
212 repaint();
213 }
214
215 public int getMouseX() { return mouseX - getWidth() / 2; }
216 public int getMouseY() { return -(mouseY - getHeight() / 2); }
217 public int getStageWidth() { return getWidth(); }
218 public int getStageHeight() { return getHeight(); }
219 public double getScaleFactor() { return getWidth() / 480.0; }
220 public boolean isMouseDown() { return mouseDown; }
221 public boolean isKeyPressed(int keyCode) {
222 return (keyCode >= 0 && keyCode < keysDown.length) ? keysDown[keyCode] : false;
223 }
224 public double getTimer() { return timerValue; }
225 public void resetTimer() { startTime = System.currentTimeMillis(); }
226
227 public void setStageScale(int scale, JFrame frame) {
228 if (currentScale == scale) return;
229 double oldScale = currentScale;
230 currentScale = (double)scale;
231 this.setPreferredSize(new Dimension(480 * scale, 360 * scale));
232 SwingUtilities.invokeLater(() -> {
233 if (frame != null) {
234 frame.pack();
235 }
236 this.requestFocusInWindow();
237 });
238 for (java.util.function.BiConsumer<Double, Double> listener : scaleListeners) {
239 listener.accept(oldScale, currentScale);
240 }
241 for (Sprite s : sprites) {
242 s.handleScaleChange(oldScale, currentScale);
243 }
244 }
245
246 @Override public void keyTyped(KeyEvent e) {}
247 @Override public void keyPressed(KeyEvent e) {
248 int code = e.getKeyCode();
249 if (code < keysDown.length) keysDown[code] = true;
250 List<Runnable> listeners = keyListeners.get(code);
251 if (listeners != null) for (Runnable r : listeners) stageObject.startScript(r);
252 }
253 @Override public void keyReleased(KeyEvent e) { if (e.getKeyCode() < keysDown.length) keysDown[e.getKeyCode()] = false; }
254 @Override public void mouseClicked(MouseEvent e) {
255 for (int i = sprites.size() - 1; i >= 0; i--) {
256 Sprite s = sprites.get(i);
257 if (s.containsPoint(e.getX(), e.getY())) {
258 s.handleMouseClick(e.getX(), e.getY());
259 break;
260 }
261 }
262 }
263 @Override public void mousePressed(MouseEvent e) { mouseDown = true; }
264 @Override public void mouseReleased(MouseEvent e) { mouseDown = false; }
265 @Override public void mouseEntered(MouseEvent e) {}
266 @Override public void mouseExited(MouseEvent e) {}
267 @Override public void mouseDragged(MouseEvent e) { mouseX = e.getX(); mouseY = e.getY(); }
268 @Override public void mouseMoved(MouseEvent e) { mouseX = e.getX(); mouseY = e.getY(); }
269
270 public static void run(Runnable setup) {
271 run(new Stage(), setup);
272 }
273
274 public static void run(Stage stage, Runnable setup) {
275 instance = stage;
276 SwingUtilities.invokeLater(() -> {
277 JFrame frame = new JFrame("JScratch Stage");
278 stage.frame = frame;
279 frame.setLayout(new BorderLayout());
280
281 JToolBar toolBar = new JToolBar();
282 toolBar.setFloatable(false);
283
284 JButton btn1x = new JButton("480x360");
285 btn1x.addActionListener(e -> stage.setStageScale(1, frame));
286 toolBar.add(btn1x);
287
288 JButton btn2x = new JButton("960x720");
289 btn2x.addActionListener(e -> stage.setStageScale(2, frame));
290 toolBar.add(btn2x);
291
292 JButton btn3x = new JButton("1440x1080");
293 btn3x.addActionListener(e -> stage.setStageScale(3, frame));
294 toolBar.add(btn3x);
295
296 frame.add(toolBar, BorderLayout.NORTH);
297 frame.add(stage, BorderLayout.CENTER);
298
299 frame.pack();
300 frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
301 frame.setVisible(true);
302 stage.requestFocusInWindow();
303 setup.run();
304 });
305 }
306
307 public void clearSprites() {
308 sprites.clear();
309 broadcastListeners.clear();
310 keyListeners.clear();
311 stageObject.activeScripts.clear();
312 stageObject.variables.clear();
313 stageObject.lists.clear();
314 }
315
316 public void disposeStage() {
317 if (renderTimer != null) renderTimer.stop();
318 if (frame != null) {
319 frame.setVisible(false);
320 frame.dispose();
321 }
322 if (instance == this) instance = null;
323 }
324
325 public static Stage getInstance() {
326 if (instance == null) {
327 instance = new Stage();
328 }
329 return instance;
330 }
331}
void handleMouseClick(int mx, int my)
Definition Sprite.java:106
boolean containsPoint(int mx, int my)
Definition Sprite.java:69
void setStage(Stage stage)
Definition Sprite.java:50
final boolean[] keysDown
Definition Stage.java:46
void addSprite(Sprite s)
Definition Stage.java:145
static Stage instance
Definition Stage.java:27
void mouseClicked(MouseEvent e)
Definition Stage.java:254
double getScaleFactor()
Definition Stage.java:219
void switchBackdropTo(String name)
Definition Stage.java:198
void paintComponent(Graphics g)
Definition Stage.java:169
void waitNextFrame()
Definition Stage.java:136
void broadcast(String message)
Definition Stage.java:96
String join(Object a, Object b)
Definition Stage.java:90
String currentBackdropName
Definition Stage.java:37
boolean isMouseDown()
Definition Stage.java:220
List< Sprite > sprites
Definition Stage.java:32
final ScratchObject stageObject
Definition Stage.java:57
void keyReleased(KeyEvent e)
Definition Stage.java:253
List< Costume > backdrops
Definition Stage.java:33
java.util.concurrent.CountDownLatch frameLatch
Definition Stage.java:49
void addToList(String listName, Object item)
Definition Stage.java:89
void nextBackdrop()
Definition Stage.java:209
void mouseReleased(MouseEvent e)
Definition Stage.java:264
double getVariableAsNumber(String name)
Definition Stage.java:84
List< java.util.function.BiConsumer< Double, Double > > scaleListeners
Definition Stage.java:54
static void run(Runnable setup)
Definition Stage.java:270
String answer()
Definition Stage.java:86
static Stage getInstance()
Definition Stage.java:325
void whenScaleChanged(java.util.function.BiConsumer< Double, Double > action)
Definition Stage.java:113
void whenIReceive(String message, Runnable action)
Definition Stage.java:105
double currentScale
Definition Stage.java:58
void waitTillNextFrame()
Definition Stage.java:140
void setVariable(String name, Object value)
Definition Stage.java:87
static void run(Stage stage, Runnable setup)
Definition Stage.java:274
void keyTyped(KeyEvent e)
Definition Stage.java:246
void mouseEntered(MouseEvent e)
Definition Stage.java:265
boolean isKeyPressed(String keyName)
Definition Stage.java:117
void mouseDragged(MouseEvent e)
Definition Stage.java:267
java.util.Map< Integer, List< Runnable > > keyListeners
Definition Stage.java:53
double timerValue
Definition Stage.java:39
Costume getBackdrop()
Definition Stage.java:157
void keyPressed(KeyEvent e)
Definition Stage.java:247
double getTimer()
Definition Stage.java:224
void startScript(Runnable script)
Definition Stage.java:93
void playSound(String path)
Definition Stage.java:92
void whenKeyPressed(int keyCode, Runnable action)
Definition Stage.java:109
void mousePressed(MouseEvent e)
Definition Stage.java:263
java.util.Map< String, List< Runnable > > broadcastListeners
Definition Stage.java:52
Object getVariable(String name)
Definition Stage.java:83
void disposeStage()
Definition Stage.java:316
String getVariableAsString(String name)
Definition Stage.java:85
void mouseExited(MouseEvent e)
Definition Stage.java:266
void setStageScale(int scale, JFrame frame)
Definition Stage.java:227
boolean mouseDown
Definition Stage.java:45
void setVolume(double vol)
Definition Stage.java:91
void changeVariableBy(String name, double amount)
Definition Stage.java:88
void mouseMoved(MouseEvent e)
Definition Stage.java:268
boolean isKeyPressed(int keyCode)
Definition Stage.java:221
void addBackdrop(Costume c)
Definition Stage.java:150
void clearSprites()
Definition Stage.java:307