Cherry pick changes from wip-scritchui which should be mainline.
[SquirrelJME.git] / modules / lcdui-demo / src / main / java / net / multiphasicapps / lcduidemo / Mystify.java
blob30a3005a14a0b66efbe10817caec784b2ddfd652
1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the Mozilla Public License Version 2.0.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package net.multiphasicapps.lcduidemo;
12 import java.util.Random;
13 import javax.microedition.lcdui.Canvas;
14 import javax.microedition.lcdui.Display;
15 import javax.microedition.lcdui.Graphics;
16 import javax.microedition.midlet.MIDlet;
17 import javax.microedition.midlet.MIDletStateChangeException;
19 /**
20 * This is a MIDlet which simulates the old-style Mystify Your Mind
21 * screensaver from Windows 3.1.
23 * @since 2018/11/22
25 public class Mystify
26 extends MIDlet
28 /** The number of polygon points. */
29 public static final int NUM_POINTS =
32 /** The number of shadows to draw. */
33 public static final int NUM_SHADOWS =
36 /** The maximum line speed. */
37 public static final int MAX_SPEED =
40 /** Use to detect way off coordinates. */
41 public static final int WAY_OFF =
42 50;
44 /** How oftens the colors shift. */
45 public static final int COLOR_SHIFT =
48 /** The delay time. */
49 public static final int DELAY_TIME =
50 250;
52 /** Delay time in nanoseconds. */
53 public static final long DELAY_TIME_NS = Mystify.DELAY_TIME * 1_000_000L;
55 /**
56 * {@inheritDoc}
57 * @since 2018/11/22
59 @Override
60 protected void destroyApp(boolean __uc)
61 throws MIDletStateChangeException
65 /**
66 * {@inheritDoc}
67 * @since 2018/11/22
69 @Override
70 protected void startApp()
71 throws MIDletStateChangeException
73 // Setup canvas
74 DemoCanvas cv = new DemoCanvas();
76 // Exit command
77 cv.addCommand(Exit.command);
78 cv.setCommandListener(new Exit());
80 // Set display to the canvas
81 Display.getDisplay(this).setCurrent(cv);
84 /**
85 * The demo canvas which does the animation.
87 * @since 2018/11/22
89 public static final class DemoCanvas
90 extends Canvas
92 /** Random number generator for bounces and such. */
93 protected final Random random =
94 new Random();
96 /** Points and their shadows. */
97 protected final Point[][] points =
98 new Point[Mystify.NUM_SHADOWS][Mystify.NUM_POINTS];
100 /** Colors. */
101 protected final int[] colors =
102 new int[Mystify.NUM_SHADOWS];
104 /** The direction of the points. */
105 protected final Point[] direction =
106 new Point[Mystify.NUM_POINTS];
108 /** Update lock to prevent multiple threads updating at once. */
109 private volatile boolean _lockflag;
111 /** The last time an update happened. */
112 private volatile long _nextnano =
113 Long.MIN_VALUE;
116 * Initializes the canvas state.
118 * @since 2018/11/22
121 // Setup title
122 this.setTitle("Mystify Your Squirrels");
124 // Setup points
125 Point[][] points = this.points;
127 // Generate random start points
128 Random random = this.random;
129 Point[] start = points[0];
130 for (int i = 0; i < Mystify.NUM_POINTS; i++)
131 start[i] = new Point(random.nextInt(), random.nextInt());
133 // Copy all the points to the shadow
134 for (int i = 1; i < Mystify.NUM_SHADOWS; i++)
135 for (int j = 0; j < Mystify.NUM_POINTS; j++)
136 points[i][j] = new Point(start[j]);
138 // Initialize the color cycle
139 int[] colors = this.colors;
140 for (int i = 0; i < Mystify.NUM_SHADOWS; i++)
141 colors[i] = random.nextInt();
143 // Determine new directions for all the points
144 Point[] direction = this.direction;
145 for (int i = 0; i < Mystify.NUM_POINTS; i++)
146 direction[i] = this.__newDirection(new Point(),
147 random.nextBoolean(), random.nextBoolean());
149 // We do not draw over every pixel
150 this.setPaintMode(false);
154 * {@inheritDoc}
155 * @since 2018/11/22
157 @Override
158 public void paint(Graphics __g)
160 // Get widget bounds
161 int w = this.getWidth(),
162 h = this.getHeight();
164 // Needed for cycling
165 Random random = this.random;
167 // Needed for drawing
168 Point[][] points = this.points;
169 Point[] direction = this.direction;
170 int[] colors = this.colors;
172 // Used to limit update rate
173 long now = System.nanoTime(),
174 nextnano = this._nextnano;
176 // Draw every point, from older shadows to the newer shape
177 for (int i = Mystify.NUM_SHADOWS - 1; i >= 0; i--)
179 Point[] draw = points[i];
181 // Update the state of the demo, but keep it consistent so
182 // that it is not always updating
183 if (i == 0 && now >= nextnano)
185 // Only allow a single run to update this
186 boolean lockflag;
187 synchronized (this)
189 // Set the lock flag
190 lockflag = this._lockflag;
191 if (!lockflag)
192 this._lockflag = true;
195 // If we did hit a false, we can update
196 if (!lockflag)
199 // Update the state
200 this.__updateState(w, h);
202 finally
204 // Always clear the flag so another run can
205 // have a go
206 this._lockflag = false;
208 // Update some other time in the future
209 this._nextnano = System.nanoTime() +
210 Mystify.DELAY_TIME_NS;
214 // Set the color for this shadow
215 __g.setColor(colors[i]);
217 // Draw all the points
218 for (int j = 0; j < Mystify.NUM_POINTS; j++)
220 // Get A and B points
221 Point a = draw[j],
222 b = draw[(j + 1) % Mystify.NUM_POINTS];
224 // Draw line
225 __g.drawLine(a.x, a.y, b.x, b.y);
229 // Request repaint to paint as fast as possible
230 this.repaint();
234 * Gives a new direction for the point.
236 * @param __p The input point to get a new direction for.
237 * @param __px Positive X?
238 * @param __py Positive Y?
239 * @return {@code __p}
240 * @throws NullPointerException On null arguments.
241 * @since 2018/11/22
243 private Point __newDirection(Point __p, boolean __px, boolean __py)
244 throws NullPointerException
246 if (__p == null)
247 throw new NullPointerException("NARG");
249 // Generate new speeds
250 Random random = this.random;
251 int x = random.nextInt(Mystify.MAX_SPEED) + 1,
252 y = random.nextInt(Mystify.MAX_SPEED) + 1;
254 // Flip signs?
255 if (!__px)
256 x = -x;
257 if (!__py)
258 y = -y;
260 // set
261 __p.x = x;
262 __p.y = y;
264 // Use that point
265 return __p;
269 * Updates the state of the demo.
271 * @param __w The width.
272 * @param __h The height.
273 * @since 2018/11/22
275 private void __updateState(int __w, int __h)
277 // Needed for cycling
278 Random random = this.random;
280 // Base points to modify
281 Point[] draw = this.points[0];
283 // Needed for drawing
284 Point[][] points = this.points;
285 Point[] direction = this.direction;
286 int[] colors = this.colors;
288 // Move all the old points and colors down
289 for (int j = Mystify.NUM_SHADOWS - 2; j >= 0; j--)
291 points[j + 1] = points[j];
292 colors[j + 1] = colors[j];
295 // Allocate a new set of points offset in the directions
296 Point[] place = new Point[Mystify.NUM_POINTS];
297 for (int j = 0; j < Mystify.NUM_POINTS; j++)
299 // Get base coordinates
300 int newx = draw[j].x,
301 newy = draw[j].y;
303 // Limit to the bounds of the screen. If a point is
304 // way off, just choose a random point on the
305 // screen instead
306 if (newx < 0 || newx >= __w)
308 if (newx < -Mystify.WAY_OFF || newy > __w + Mystify.WAY_OFF)
309 newx = random.nextInt((__w > 0 ? __w : 1));
310 else
312 if (newx < 0)
313 newx = 0;
314 else if (newx >= __w)
315 newx = __w;
319 if (newy < 0 || newy >= __h)
321 if (newy < -Mystify.WAY_OFF || newy > __h + Mystify.WAY_OFF)
322 newy = random.nextInt((__h > 0 ? __h : 1));
323 else
325 if (newy < 0)
326 newy = 0;
327 else if (newy >= __h)
328 newy = __h;
332 // Move point in the target direction
333 newx += direction[j].x;
334 newy += direction[j].y;
336 // Previous positive position?
337 boolean ppx = (direction[j].x >= 0),
338 ppy = (direction[j].y >= 0);
340 // Deflect points?
341 boolean defx = (newx <= 0 || newx >= __w),
342 defy = (newy <= 0 || newy >= __h);
344 // Deflect direction
345 this.__newDirection(direction[j],
346 ppx ^ defx, ppy ^ defy);
348 // Make sure the points are not on a bound!
349 if (defx)
350 if (ppx)
351 newx--;
352 else
353 newx++;
354 if (defy)
355 if (ppy)
356 newx--;
357 else
358 newy++;
360 // Extract the color
361 int color = colors[j];
362 byte r = (byte)((color & 0xFF0000) >>> 16),
363 g = (byte)((color & 0x00FF00) >>> 8),
364 b = (byte)((color & 0x0000FF));
366 // Cycle the colors depending on the direction of travel
367 r += (ppx ^ ppy ? +Mystify.COLOR_SHIFT : -Mystify.COLOR_SHIFT);
368 g += (ppy | ppy ? -Mystify.COLOR_SHIFT : +Mystify.COLOR_SHIFT);
369 b += (ppx ^ ppy ? -Mystify.COLOR_SHIFT : +Mystify.COLOR_SHIFT);
371 // Recombine the color
372 colors[j] = ((r & 0xFF) << 16) |
373 ((g & 0xFF) << 8) |
374 (b & 0xFF);
376 // Store the point
377 place[j] = new Point(newx, newy);
380 // Use all these points
381 points[0] = place;
386 * Volatile point information.
388 * @since 2018/11/22
390 public static final class Point
392 /** X position. */
393 public int x;
395 /** Y position. */
396 public int y;
399 * Initializes the point.
401 * @since 2018/11/22
403 public Point()
408 * Initializes the point from another point.
410 * @param __p The point to copy.
411 * @throws NullPointerException On null arguments.
412 * @since 2018/11/22
414 public Point(Point __p)
415 throws NullPointerException
417 if (__p == null)
418 throw new NullPointerException("NARG");
420 this.x = __p.x;
421 this.y = __p.y;
425 * Initializes the point from the given coordinates.
427 * @param __x The X coordinate.
428 * @param __y The Y coordinate.
429 * @since 2018/11/22
431 public Point(int __x, int __y)
433 this.x = __x;
434 this.y = __y;