1 /*
2 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package life;
26
27 import hat.ifacemapper.MappableIface;
28 import hat.util.ui.SevenSegmentDisplay;
29
30 import javax.swing.Box;
31 import javax.swing.BoxLayout;
32 import javax.swing.JButton;
33 import javax.swing.JComponent;
34 import javax.swing.JFrame;
35 import javax.swing.JLabel;
36 import javax.swing.JMenuBar;
37 import javax.swing.JPanel;
38 import javax.swing.JToggleButton;
39 import javax.swing.WindowConstants;
40 import java.awt.Color;
41 import java.awt.Dimension;
42 import java.awt.Graphics;
43 import java.awt.Graphics2D;
44 import java.awt.GraphicsEnvironment;
45 import java.awt.MouseInfo;
46 import java.awt.Point;
47 import java.awt.Rectangle;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.awt.event.MouseMotionAdapter;
51 import java.awt.geom.AffineTransform;
52 import java.awt.image.BufferedImage;
53 import java.awt.image.DataBufferByte;
54 import java.awt.image.ImageObserver;
55
56 public class Viewer extends JFrame {
57
58 public boolean isReadyForUpdate(long now) {
59 incGenerations();
60 return (state.lastUIUpdateCompleted && ((now - state.timeOfLastUIUpdate) >= state.msPerFrame));
61 }
62
63 public boolean stillRunning() {
64 return state.generations < state.maxGenerations;
65 }
66
67 public void incGenerations() {
68 state.generations++;
69 state.generationsSinceLastChange++;
70 }
71
72 void update(long now, @MappableIface.RO Main.CellGrid cellGrid, int from) {
73
74 if (state.deviceOrModeModified) {
75 state.generationsSinceLastChange = 0;
76 state.timeOfLastChange = now;
77 state.deviceOrModeModified = false;
78 }
79 controls.updateCounters(now);
80 cellGrid.copySliceTo(mainPanel.rasterData, from);
81 state.lastUIUpdateCompleted =false;
82 mainPanel.repaint();
83 state.timeOfLastUIUpdate = now;
84 }
85
86 public static class State {
87 public final long requiredFrameRate = 5;
88 public final long msPerFrame = 1000/requiredFrameRate;
89 public final long maxGenerations = 1000000;
90 private final Object doorBell = new Object();
91 public long generations = 0;
92 public volatile boolean minimizingCopies = false;
93 public volatile boolean usingGPU = false;
94 volatile private boolean started = false;
95 public long generationsSinceLastChange = 0;
96 public long timeOfLastChange = 0;
97 public long timeOfLastUIUpdate;
98 public volatile boolean lastUIUpdateCompleted = false;
99
100 public volatile boolean deviceOrModeModified = false;
101
102 State() {
103
104 }
105 }
106 final Controls controls;
107 final MainPanel mainPanel;
108 public final State state;
109 static final public class MainPanel extends JComponent implements ImageObserver {
110 final double IN = 1.1;
111 final double OUT = 1 / IN;
112 private final BufferedImage image;
113 final byte[] rasterData;
114 private final double initialZoomFactor;
115 private double zoomFactor;
116 private double prevZoomFactor;
117 private boolean zooming;
118 private boolean mouseReleased;
119 private double xOffset = 0;
120 private double yOffset = 0;
121 private Point startPoint;
122 final private State state;
123
124 record Drag(int xDiff, int yDiff) { }
125
126 Drag drag = null;
127
128 @Override
129 public Dimension getPreferredSize() {
130 return new Dimension((int) (image.getWidth() * zoomFactor), (int) (image.getHeight() * zoomFactor));
131 }
132
133 public MainPanel(BufferedImage image, State state) {
134 this.state = state;
135 this.image = image;
136
137 Rectangle bounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
138 this.initialZoomFactor = Math.min((bounds.width - 20) / (float) image.getWidth(),
139 (bounds.height - 20) / (float) image.getHeight());
140 this.rasterData = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
141 this.prevZoomFactor = initialZoomFactor;
142 this.zoomFactor = initialZoomFactor;
143 addMouseWheelListener(e -> {
144 zooming = true;
145 zoomFactor = zoomFactor * ((e.getWheelRotation() < 0) ? IN : OUT);
146 if (zoomFactor < initialZoomFactor) {
147 zoomFactor = initialZoomFactor;
148 prevZoomFactor = zoomFactor;
149 }
150 repaint();
151 });
152 addMouseMotionListener(new MouseMotionAdapter() {
153 @Override
154 public void mouseDragged(MouseEvent e) {
155 Point curPoint = e.getLocationOnScreen();
156 drag = new Drag(curPoint.x - startPoint.x, curPoint.y - startPoint.y);
157 repaint();
158 }
159 });
160 addMouseListener(new MouseAdapter() {
161 @Override
162 public void mousePressed(MouseEvent e) {
163 mouseReleased = false;
164 startPoint = MouseInfo.getPointerInfo().getLocation();
165 repaint();
166 }
167
168 @Override
169 public void mouseReleased(MouseEvent e) {
170 mouseReleased = true;
171 repaint();
172 }
173 });
174 }
175
176 @Override
177 public void paintComponent(Graphics g) {
178 super.paintComponent(g);
179 state.lastUIUpdateCompleted = true;
180 }
181
182 @Override
183 public void paint(Graphics g) {
184 super.paint(g);
185 Graphics2D g2 = (Graphics2D) g;
186 AffineTransform affineTransform = new AffineTransform();
187 if (zooming) {
188 double xRel = MouseInfo.getPointerInfo().getLocation().getX() - getLocationOnScreen().getX();
189 double yRel = MouseInfo.getPointerInfo().getLocation().getY() - getLocationOnScreen().getY();
190 double zoomDiv = zoomFactor / prevZoomFactor;
191 xOffset = (zoomDiv) * (xOffset) + (1 - zoomDiv) * xRel;
192 yOffset = (zoomDiv) * (yOffset) + (1 - zoomDiv) * yRel;
193 affineTransform.translate(xOffset, yOffset);
194 prevZoomFactor = zoomFactor;
195 zooming = false;
196 } else if (drag != null) {
197 affineTransform.translate(xOffset + drag.xDiff, yOffset + drag.yDiff);
198 if (mouseReleased) {
199 xOffset += drag.xDiff;
200 yOffset += drag.yDiff;
201 drag = null;
202 }
203 } else {
204 affineTransform.translate(xOffset, yOffset);
205 }
206 affineTransform.scale(zoomFactor, zoomFactor);
207 g2.transform(affineTransform);
208 g2.setColor(Color.DARK_GRAY);
209 g2.fillRect(-image.getWidth(), -image.getHeight(), image.getWidth() * 3, image.getHeight() * 3);
210 g2.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), 0, 0, image.getWidth(), image.getHeight(), null);
211 }
212
213 }
214
215 public static class Controls {
216 public final JMenuBar menuBar;
217 private final JButton startButton;
218 private JToggleButton useGPUToggleButton;
219 private JToggleButton minimizeCopiesToggleButton;
220 private SevenSegmentDisplay generationsPerSecondSevenSegment;
221 private final SevenSegmentDisplay generationSevenSegment;
222
223 private State state;
224
225 Controls( State state) {
226 this.state = state;
227 this.menuBar = new JMenuBar();
228 JPanel panel = new JPanel();
229 panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
230
231 ((JButton) panel.add(new JButton("Exit"))).addActionListener(_ -> System.exit(0));
232 this.startButton = (JButton) panel.add(new JButton("Start"));
233
234 panel.add(new JLabel("Generation"));
235 this.generationSevenSegment = (SevenSegmentDisplay)
236 panel.add(new SevenSegmentDisplay(6,30,panel.getForeground(),panel.getBackground()));
237
238 panel.add(new JLabel("Gen/Sec"));
239
240 this.generationsPerSecondSevenSegment = (SevenSegmentDisplay)
241 panel.add(new SevenSegmentDisplay(6,30,panel.getForeground(),panel.getBackground()));
242 panel.add(Box.createHorizontalStrut(400));
243 menuBar.add(panel);
244
245 }
246
247 JToggleButton addToggle(JComponent component, String def, String alt) {
248 var toggleButton = (JToggleButton) component.add(new JToggleButton(def));
249 toggleButton.addChangeListener(event -> {
250 if (((JToggleButton) event.getSource()).isSelected()) {
251 ((JToggleButton) event.getSource()).setText(alt);
252 } else {
253 ((JToggleButton) event.getSource()).setText(def);
254 }
255 state.deviceOrModeModified = true;
256 });
257 return toggleButton;
258 }
259
260 public void updateCounters(long now) {
261 generationSevenSegment.set((int)state.generationsSinceLastChange);
262 long interval= (now -state.timeOfLastChange);
263 if (state.generationsSinceLastChange > 0 && interval>0) { // no div/0
264 int gps = (int)((1000*state.generationsSinceLastChange)/interval);
265 /* System.out.println("gps "+(int)gps
266 + " interval="+interval
267 + " state.generationsSinceLastChange="+state.generationsSinceLastChange
268 + " state.timeOfLastChange="+state.timeOfLastChange);*/
269
270 generationsPerSecondSevenSegment.set( gps);
271 }
272 }
273 }
274 Viewer(String title, Main.CellGrid cellGrid, State state) {
275 super(title);
276 this.state = state;
277 this.mainPanel = new MainPanel(new BufferedImage(cellGrid.width(), cellGrid.height(), BufferedImage.TYPE_BYTE_GRAY), state);
278 this.controls = new Controls( state);
279 setJMenuBar(this.controls.menuBar);
280 controls.startButton.addActionListener(_ -> {
281 state.started = true;
282 synchronized (state.doorBell) {
283 state.doorBell.notify();
284 }
285 });
286 this.getContentPane().add(this.mainPanel);
287 this.setLocationRelativeTo(null);
288 this.pack();
289 this.setVisible(true);
290 this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
291
292 cellGrid.copySliceTo(mainPanel.rasterData, 0); // We assume that the original data starts in the lo end of the grid
293 }
294
295 public void waitForStart() {
296 while (!state.started) {
297 synchronized (state.doorBell) {
298 try {
299 state.doorBell.wait();
300 } catch (final InterruptedException ie) {
301 ie.getStackTrace();
302 }
303 }
304 }
305 }
306
307 }