44import com .codename1 .io .Util ;
55import com .codename1 .testing .AbstractTest ;
66import com .codename1 .testing .TestUtils ;
7+ import com .codename1 .ui .AnimationManager ;
8+ import com .codename1 .ui .Button ;
9+ import com .codename1 .ui .Component ;
10+ import com .codename1 .ui .Container ;
711import com .codename1 .ui .Display ;
812import com .codename1 .ui .Form ;
913import com .codename1 .ui .Image ;
14+ import com .codename1 .ui .animations .Motion ;
1015import com .codename1 .ui .util .ImageIO ;
1116import com .codenameone .developerguide .Demo ;
17+ import com .codenameone .developerguide .DemoBrowserForm ;
1218import com .codenameone .developerguide .DemoRegistry ;
1319
1420import java .io .IOException ;
1521import java .io .OutputStream ;
22+ import java .util .ArrayList ;
1623import java .util .HashMap ;
1724import java .util .HashSet ;
25+ import java .util .List ;
1826import java .util .Map ;
1927import java .util .Set ;
2028
2331 * that external tooling can compare them against the developer guide imagery.
2432 */
2533public class AnimationDemosScreenshotTest extends AbstractTest {
26- private static final String HOST_TITLE = "Demo Test Host " ;
34+ private static final String HOST_TITLE = "Developer Guide Demos " ;
2735 private static final long FORM_TIMEOUT_MS = 10000L ;
2836 private static final String STORAGE_PREFIX = "developer-guide.animations." ;
37+ private static final int FRAMES_PER_ANIMATION = 6 ;
38+ private static final long ANIMATION_CAPTURE_TIMEOUT_MS = 2000L ;
39+ private static final long ANIMATION_SETTLE_TIMEOUT_MS = 1500L ;
40+ private static final int ANIMATION_FRAME_DELAY_MS = 180 ;
41+ private static final String FRAME_MANIFEST_SUFFIX = "-frames.manifest" ;
2942
3043 private static final Map <String , String > SCREENSHOT_NAME_OVERRIDES = createScreenshotNameOverrides ();
3144 private static final Set <String > OVERRIDE_FILE_NAMES = new HashSet <>(SCREENSHOT_NAME_OVERRIDES .values ());
@@ -36,24 +49,39 @@ public class AnimationDemosScreenshotTest extends AbstractTest {
3649 public boolean runTest () throws Exception {
3750 clearPreviousScreenshots ();
3851
39- Form host = new Form (HOST_TITLE );
40- host .show ();
41- TestUtils .waitForFormTitle (HOST_TITLE , FORM_TIMEOUT_MS );
52+ boolean previousSlowMotion = Motion .isSlowMotion ();
53+ Motion .setSlowMotion (true );
54+ try {
55+ Form host = new DemoBrowserForm ();
56+ host .show ();
57+ TestUtils .waitForFormTitle (HOST_TITLE , FORM_TIMEOUT_MS );
4258
43- for (Demo demo : DemoRegistry .getDemos ()) {
44- Form previous = Display .getInstance ().getCurrent ();
45- demo .show (host );
46- Form demoForm = waitForFormChange (previous );
47- waitForFormReady (demoForm );
59+ for (Demo demo : DemoRegistry .getDemos ()) {
60+ Form previous = Display .getInstance ().getCurrent ();
61+ demo .show (host );
62+ Form demoForm = waitForFormChange (previous );
63+ waitForFormReady (demoForm );
4864
49- Image screenshot = capture (demoForm );
50- saveScreenshot (storageKeyFor (demo .getTitle ()), screenshot );
65+ waitForAnimationsToFinish (demoForm );
5166
52- host .show ();
53- waitForHost (host );
54- }
67+ triggerAnimationIfNeeded (demo , demoForm );
68+ Form activeForm = ensureCurrentFormReady (demoForm );
5569
56- return true ;
70+ if (waitForAnimationStart (activeForm , ANIMATION_CAPTURE_TIMEOUT_MS )) {
71+ captureAnimationFrames (demo , activeForm );
72+ finalizeAnimations (activeForm );
73+ } else {
74+ Image screenshot = capture (activeForm );
75+ saveScreenshot (storageKeyFor (demo .getTitle ()), screenshot );
76+ }
77+
78+ returnToHost (activeForm , host );
79+ }
80+
81+ return true ;
82+ } finally {
83+ Motion .setSlowMotion (previousSlowMotion );
84+ }
5785 }
5886
5987 private void clearPreviousScreenshots () {
@@ -125,6 +153,7 @@ private void waitForFormReady(Form form) {
125153 private void waitForHost (Form host ) {
126154 long deadline = System .currentTimeMillis () + FORM_TIMEOUT_MS ;
127155 while (Display .getInstance ().getCurrent () != host ) {
156+ Display .getInstance ().animate ();
128157 TestUtils .waitFor (50 );
129158 if (System .currentTimeMillis () > deadline ) {
130159 fail ("Timed out waiting to return to host form." );
@@ -134,6 +163,207 @@ private void waitForHost(Form host) {
134163 TestUtils .waitFor (200 );
135164 }
136165
166+ private void returnToHost (Form demoForm , Form host ) {
167+ if (host == null ) {
168+ return ;
169+ }
170+
171+ long deadline = System .currentTimeMillis () + FORM_TIMEOUT_MS ;
172+
173+ if (demoForm != null && demoForm != currentForm ()) {
174+ demoForm .showBack ();
175+ }
176+
177+ while (System .currentTimeMillis () <= deadline ) {
178+ Form active = currentForm ();
179+ if (active == host ) {
180+ break ;
181+ }
182+
183+ if (active == null ) {
184+ host .show ();
185+ } else {
186+ active .showBack ();
187+ }
188+
189+ TestUtils .waitFor (150 );
190+ }
191+
192+ if (currentForm () != host ) {
193+ host .show ();
194+ }
195+
196+ waitForHost (host );
197+ }
198+
199+ private Form currentForm () {
200+ Component current = Display .getInstance ().getCurrent ();
201+ return (current instanceof Form ) ? (Form ) current : null ;
202+ }
203+
204+ private void triggerAnimationIfNeeded (Demo demo , Form form ) {
205+ if (demo == null || form == null ) {
206+ return ;
207+ }
208+
209+ if (demo instanceof LayoutAnimationsDemo ) {
210+ clickButton (form , "Fall" );
211+ } else if (demo instanceof UnlayoutAnimationsDemo ) {
212+ clickButton (form , "Fall" );
213+ } else if (demo instanceof HiddenComponentDemo ) {
214+ clickButton (form , "Hide It" );
215+ } else if (demo instanceof AnimationSynchronicityDemo ) {
216+ clickButton (form , "Run Sequence" );
217+ } else if (demo instanceof ReplaceTransitionDemo ) {
218+ clickButton (form , "Replace Pending" );
219+ } else if (demo instanceof SlideTransitionsDemo ) {
220+ clickButton (form , "Show" );
221+ } else if (demo instanceof BubbleTransitionDemo ) {
222+ clickButton (form , "+" );
223+ } else if (demo instanceof SwipeBackSupportDemo ) {
224+ clickButton (form , "Open Destination" );
225+ }
226+ }
227+
228+ private void clickButton (Component root , String text ) {
229+ Button button = findButton (root , text );
230+ if (button != null ) {
231+ button .pressed ();
232+ button .released ();
233+ TestUtils .waitFor (200 );
234+ }
235+ }
236+
237+ private Button findButton (Component component , String text ) {
238+ if (component instanceof Button ) {
239+ Button button = (Button ) component ;
240+ if (text .equals (button .getText ())) {
241+ return button ;
242+ }
243+ }
244+
245+ if (component instanceof Form ) {
246+ return findButton (((Form ) component ).getContentPane (), text );
247+ }
248+
249+ if (component instanceof Container ) {
250+ Container container = (Container ) component ;
251+ int childCount = container .getComponentCount ();
252+ for (int i = 0 ; i < childCount ; i ++) {
253+ Button match = findButton (container .getComponentAt (i ), text );
254+ if (match != null ) {
255+ return match ;
256+ }
257+ }
258+ }
259+
260+ return null ;
261+ }
262+
263+ private Form ensureCurrentFormReady (Form fallback ) {
264+ Component current = Display .getInstance ().getCurrent ();
265+ if (current instanceof Form ) {
266+ Form form = (Form ) current ;
267+ waitForFormReady (form );
268+ return form ;
269+ }
270+ return fallback ;
271+ }
272+
273+ private void waitForAnimationsToFinish (Form form ) {
274+ if (form == null ) {
275+ return ;
276+ }
277+ long deadline = System .currentTimeMillis () + ANIMATION_SETTLE_TIMEOUT_MS ;
278+ while (System .currentTimeMillis () <= deadline ) {
279+ AnimationManager manager = form .getAnimationManager ();
280+ if (manager == null || !manager .isAnimating ()) {
281+ break ;
282+ }
283+ form .animate ();
284+ TestUtils .waitFor (50 );
285+ }
286+ }
287+
288+ private void captureAnimationFrames (Demo demo , Form form ) throws IOException {
289+ String sanitized = sanitizeFileName (demo .getTitle ());
290+ String baseKey = storageKeyFor (demo .getTitle ());
291+ boolean baseSaved = false ;
292+ List <String > frameKeys = new ArrayList <>(FRAMES_PER_ANIMATION );
293+ Image finalFrameImage = null ;
294+
295+ for (int frameIndex = 0 ; frameIndex < FRAMES_PER_ANIMATION ; frameIndex ++) {
296+ if (!isAnimating (form ) && frameIndex == 0 ) {
297+ break ;
298+ }
299+
300+ Image frameImage = capture (form );
301+ if (!baseSaved ) {
302+ saveScreenshot (baseKey , frameImage );
303+ baseSaved = true ;
304+ }
305+
306+ String frameKey = stageStorageKeyFor (sanitized , frameIndex );
307+ saveScreenshot (frameKey , frameImage );
308+ frameKeys .add (frameKey );
309+ finalFrameImage = frameImage ;
310+
311+ if (frameIndex >= FRAMES_PER_ANIMATION - 1 ) {
312+ break ;
313+ }
314+
315+ if (!advanceAnimation (form )) {
316+ finalFrameImage = capture (form );
317+ break ;
318+ }
319+ }
320+
321+ if (!baseSaved ) {
322+ Image screenshot = capture (form );
323+ saveScreenshot (baseKey , screenshot );
324+ finalFrameImage = screenshot ;
325+ }
326+
327+ if (finalFrameImage == null ) {
328+ finalFrameImage = capture (form );
329+ }
330+
331+ while (frameKeys .size () < FRAMES_PER_ANIMATION ) {
332+ String frameKey = stageStorageKeyFor (sanitized , frameKeys .size ());
333+ saveScreenshot (frameKey , finalFrameImage );
334+ frameKeys .add (frameKey );
335+ }
336+
337+ if (!frameKeys .isEmpty ()) {
338+ recordFrameManifest (sanitized , frameKeys );
339+ }
340+ }
341+
342+ private String stageStorageKeyFor (String sanitizedTitle , int frame ) {
343+ return STORAGE_PREFIX + sanitizedTitle + "-frame-" + (frame + 1 ) + ".png" ;
344+ }
345+
346+ private void recordFrameManifest (String sanitizedTitle , List <String > frameKeys ) {
347+ if (sanitizedTitle == null || frameKeys == null || frameKeys .isEmpty ()) {
348+ return ;
349+ }
350+
351+ String manifestKey = STORAGE_PREFIX + sanitizedTitle + FRAME_MANIFEST_SUFFIX ;
352+ storage .deleteStorageFile (manifestKey );
353+
354+ Map <String , Object > manifest = new HashMap <>();
355+ manifest .put ("frames" , new ArrayList <>(frameKeys ));
356+
357+ List <Integer > comparableFrames = new ArrayList <>(2 );
358+ comparableFrames .add (Integer .valueOf (0 ));
359+ if (frameKeys .size () > 1 ) {
360+ comparableFrames .add (Integer .valueOf (frameKeys .size () - 1 ));
361+ }
362+ manifest .put ("compareFrames" , comparableFrames );
363+
364+ storage .writeObject (manifestKey , manifest );
365+ }
366+
137367 private static Map <String , String > createScreenshotNameOverrides () {
138368 Map <String , String > map = new HashMap <>();
139369 map .put ("Layout Animations" , "layout-animation-1.png" );
@@ -148,6 +378,51 @@ private String sanitizeFileName(String value) {
148378 return sanitized .isEmpty () ? "demo-screenshot" : sanitized ;
149379 }
150380
381+ private void finalizeAnimations (Form form ) {
382+ if (form == null ) {
383+ return ;
384+ }
385+ form .animate ();
386+ waitForAnimationsToFinish (form );
387+ }
388+
389+ private boolean waitForAnimationStart (Form form , long timeoutMs ) {
390+ if (form == null ) {
391+ return false ;
392+ }
393+ long deadline = System .currentTimeMillis () + timeoutMs ;
394+ while (System .currentTimeMillis () <= deadline ) {
395+ if (isAnimating (form )) {
396+ return true ;
397+ }
398+ form .animate ();
399+ TestUtils .waitFor (50 );
400+ }
401+ return isAnimating (form );
402+ }
403+
404+ private boolean isAnimating (Form form ) {
405+ if (form == null ) {
406+ return false ;
407+ }
408+ AnimationManager manager = form .getAnimationManager ();
409+ return manager != null && manager .isAnimating ();
410+ }
411+
412+ private boolean advanceAnimation (Form form ) {
413+ if (form == null ) {
414+ return false ;
415+ }
416+ long deadline = System .currentTimeMillis () + ANIMATION_FRAME_DELAY_MS ;
417+ boolean animating = isAnimating (form );
418+ while (System .currentTimeMillis () <= deadline && animating ) {
419+ form .animate ();
420+ TestUtils .waitFor (20 );
421+ animating = isAnimating (form );
422+ }
423+ return animating ;
424+ }
425+
151426 @ Override
152427 public boolean shouldExecuteOnEDT () {
153428 return true ;
0 commit comments