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 ;
1217import com .codenameone .developerguide .DemoRegistry ;
1318
1419import java .io .IOException ;
1520import java .io .OutputStream ;
21+ import java .util .ArrayList ;
1622import java .util .HashMap ;
1723import java .util .HashSet ;
24+ import java .util .List ;
1825import java .util .Map ;
1926import java .util .Set ;
2027
@@ -26,6 +33,11 @@ public class AnimationDemosScreenshotTest extends AbstractTest {
2633 private static final String HOST_TITLE = "Demo Test Host" ;
2734 private static final long FORM_TIMEOUT_MS = 10000L ;
2835 private static final String STORAGE_PREFIX = "developer-guide.animations." ;
36+ private static final int FRAMES_PER_ANIMATION = 6 ;
37+ private static final long ANIMATION_CAPTURE_TIMEOUT_MS = 2000L ;
38+ private static final long ANIMATION_SETTLE_TIMEOUT_MS = 1500L ;
39+ private static final int ANIMATION_FRAME_DELAY_MS = 180 ;
40+ private static final String FRAME_MANIFEST_SUFFIX = "-frames.manifest" ;
2941
3042 private static final Map <String , String > SCREENSHOT_NAME_OVERRIDES = createScreenshotNameOverrides ();
3143 private static final Set <String > OVERRIDE_FILE_NAMES = new HashSet <>(SCREENSHOT_NAME_OVERRIDES .values ());
@@ -36,24 +48,40 @@ public class AnimationDemosScreenshotTest extends AbstractTest {
3648 public boolean runTest () throws Exception {
3749 clearPreviousScreenshots ();
3850
39- Form host = new Form (HOST_TITLE );
40- host .show ();
41- TestUtils .waitForFormTitle (HOST_TITLE , FORM_TIMEOUT_MS );
51+ boolean previousSlowMotion = Motion .isSlowMotion ();
52+ Motion .setSlowMotion (true );
53+ try {
54+ Form host = new Form (HOST_TITLE );
55+ host .show ();
56+ TestUtils .waitForFormTitle (HOST_TITLE , FORM_TIMEOUT_MS );
4257
43- for (Demo demo : DemoRegistry .getDemos ()) {
44- Form previous = Display .getInstance ().getCurrent ();
45- demo .show (host );
46- Form demoForm = waitForFormChange (previous );
47- waitForFormReady (demoForm );
58+ for (Demo demo : DemoRegistry .getDemos ()) {
59+ Form previous = Display .getInstance ().getCurrent ();
60+ demo .show (host );
61+ Form demoForm = waitForFormChange (previous );
62+ waitForFormReady (demoForm );
4863
49- Image screenshot = capture (demoForm );
50- saveScreenshot (storageKeyFor (demo .getTitle ()), screenshot );
64+ waitForAnimationsToFinish (demoForm );
5165
52- host .show ();
53- waitForHost (host );
54- }
66+ triggerAnimationIfNeeded (demo , demoForm );
67+ Form activeForm = ensureCurrentFormReady (demoForm );
5568
56- return true ;
69+ if (waitForAnimationStart (activeForm , ANIMATION_CAPTURE_TIMEOUT_MS )) {
70+ captureAnimationFrames (demo , activeForm );
71+ finalizeAnimations (activeForm );
72+ } else {
73+ Image screenshot = capture (activeForm );
74+ saveScreenshot (storageKeyFor (demo .getTitle ()), screenshot );
75+ }
76+
77+ host .show ();
78+ waitForHost (host );
79+ }
80+
81+ return true ;
82+ } finally {
83+ Motion .setSlowMotion (previousSlowMotion );
84+ }
5785 }
5886
5987 private void clearPreviousScreenshots () {
@@ -134,6 +162,169 @@ private void waitForHost(Form host) {
134162 TestUtils .waitFor (200 );
135163 }
136164
165+ private void triggerAnimationIfNeeded (Demo demo , Form form ) {
166+ if (demo == null || form == null ) {
167+ return ;
168+ }
169+
170+ if (demo instanceof LayoutAnimationsDemo ) {
171+ clickButton (form , "Fall" );
172+ } else if (demo instanceof UnlayoutAnimationsDemo ) {
173+ clickButton (form , "Fall" );
174+ } else if (demo instanceof HiddenComponentDemo ) {
175+ clickButton (form , "Hide It" );
176+ } else if (demo instanceof AnimationSynchronicityDemo ) {
177+ clickButton (form , "Run Sequence" );
178+ } else if (demo instanceof ReplaceTransitionDemo ) {
179+ clickButton (form , "Replace Pending" );
180+ } else if (demo instanceof SlideTransitionsDemo ) {
181+ clickButton (form , "Show" );
182+ } else if (demo instanceof BubbleTransitionDemo ) {
183+ clickButton (form , "+" );
184+ } else if (demo instanceof SwipeBackSupportDemo ) {
185+ clickButton (form , "Open Destination" );
186+ }
187+ }
188+
189+ private void clickButton (Component root , String text ) {
190+ Button button = findButton (root , text );
191+ if (button != null ) {
192+ button .pressed ();
193+ button .released ();
194+ TestUtils .waitFor (200 );
195+ }
196+ }
197+
198+ private Button findButton (Component component , String text ) {
199+ if (component instanceof Button ) {
200+ Button button = (Button ) component ;
201+ if (text .equals (button .getText ())) {
202+ return button ;
203+ }
204+ }
205+
206+ if (component instanceof Form ) {
207+ return findButton (((Form ) component ).getContentPane (), text );
208+ }
209+
210+ if (component instanceof Container ) {
211+ Container container = (Container ) component ;
212+ int childCount = container .getComponentCount ();
213+ for (int i = 0 ; i < childCount ; i ++) {
214+ Button match = findButton (container .getComponentAt (i ), text );
215+ if (match != null ) {
216+ return match ;
217+ }
218+ }
219+ }
220+
221+ return null ;
222+ }
223+
224+ private Form ensureCurrentFormReady (Form fallback ) {
225+ Component current = Display .getInstance ().getCurrent ();
226+ if (current instanceof Form ) {
227+ Form form = (Form ) current ;
228+ waitForFormReady (form );
229+ return form ;
230+ }
231+ return fallback ;
232+ }
233+
234+ private void waitForAnimationsToFinish (Form form ) {
235+ if (form == null ) {
236+ return ;
237+ }
238+ long deadline = System .currentTimeMillis () + ANIMATION_SETTLE_TIMEOUT_MS ;
239+ while (System .currentTimeMillis () <= deadline ) {
240+ AnimationManager manager = form .getAnimationManager ();
241+ if (manager == null || !manager .isAnimating ()) {
242+ break ;
243+ }
244+ form .animate ();
245+ TestUtils .waitFor (50 );
246+ }
247+ }
248+
249+ private void captureAnimationFrames (Demo demo , Form form ) throws IOException {
250+ String sanitized = sanitizeFileName (demo .getTitle ());
251+ String baseKey = storageKeyFor (demo .getTitle ());
252+ boolean baseSaved = false ;
253+ List <String > frameKeys = new ArrayList <>(FRAMES_PER_ANIMATION );
254+ Image finalFrameImage = null ;
255+
256+ for (int frameIndex = 0 ; frameIndex < FRAMES_PER_ANIMATION ; frameIndex ++) {
257+ if (!isAnimating (form ) && frameIndex == 0 ) {
258+ break ;
259+ }
260+
261+ Image frameImage = capture (form );
262+ if (!baseSaved ) {
263+ saveScreenshot (baseKey , frameImage );
264+ baseSaved = true ;
265+ }
266+
267+ String frameKey = stageStorageKeyFor (sanitized , frameIndex );
268+ saveScreenshot (frameKey , frameImage );
269+ frameKeys .add (frameKey );
270+ finalFrameImage = frameImage ;
271+
272+ if (frameIndex >= FRAMES_PER_ANIMATION - 1 ) {
273+ break ;
274+ }
275+
276+ if (!advanceAnimation (form )) {
277+ finalFrameImage = capture (form );
278+ break ;
279+ }
280+ }
281+
282+ if (!baseSaved ) {
283+ Image screenshot = capture (form );
284+ saveScreenshot (baseKey , screenshot );
285+ finalFrameImage = screenshot ;
286+ }
287+
288+ if (finalFrameImage == null ) {
289+ finalFrameImage = capture (form );
290+ }
291+
292+ while (frameKeys .size () < FRAMES_PER_ANIMATION ) {
293+ String frameKey = stageStorageKeyFor (sanitized , frameKeys .size ());
294+ saveScreenshot (frameKey , finalFrameImage );
295+ frameKeys .add (frameKey );
296+ }
297+
298+ if (!frameKeys .isEmpty ()) {
299+ recordFrameManifest (sanitized , frameKeys );
300+ }
301+ }
302+
303+ private String stageStorageKeyFor (String sanitizedTitle , int frame ) {
304+ return STORAGE_PREFIX + sanitizedTitle + "-frame-" + (frame + 1 ) + ".png" ;
305+ }
306+
307+ private void recordFrameManifest (String sanitizedTitle , List <String > frameKeys ) {
308+ if (sanitizedTitle == null || frameKeys == null || frameKeys .isEmpty ()) {
309+ return ;
310+ }
311+
312+ String manifestKey = STORAGE_PREFIX + sanitizedTitle + FRAME_MANIFEST_SUFFIX ;
313+ storage .deleteStorageFile (manifestKey );
314+
315+ Map <String , Object > manifest = new HashMap <>();
316+ manifest .put ("frames" , new ArrayList <>(frameKeys ));
317+
318+ List <Integer > comparableFrames = new ArrayList <>(2 );
319+ comparableFrames .add (Integer .valueOf (0 ));
320+ if (frameKeys .size () > 1 ) {
321+ comparableFrames .add (Integer .valueOf (frameKeys .size () - 1 ));
322+ }
323+ manifest .put ("compareFrames" , comparableFrames );
324+
325+ storage .writeObject (manifestKey , manifest );
326+ }
327+
137328 private static Map <String , String > createScreenshotNameOverrides () {
138329 Map <String , String > map = new HashMap <>();
139330 map .put ("Layout Animations" , "layout-animation-1.png" );
@@ -148,6 +339,51 @@ private String sanitizeFileName(String value) {
148339 return sanitized .isEmpty () ? "demo-screenshot" : sanitized ;
149340 }
150341
342+ private void finalizeAnimations (Form form ) {
343+ if (form == null ) {
344+ return ;
345+ }
346+ form .animate ();
347+ waitForAnimationsToFinish (form );
348+ }
349+
350+ private boolean waitForAnimationStart (Form form , long timeoutMs ) {
351+ if (form == null ) {
352+ return false ;
353+ }
354+ long deadline = System .currentTimeMillis () + timeoutMs ;
355+ while (System .currentTimeMillis () <= deadline ) {
356+ if (isAnimating (form )) {
357+ return true ;
358+ }
359+ form .animate ();
360+ TestUtils .waitFor (50 );
361+ }
362+ return isAnimating (form );
363+ }
364+
365+ private boolean isAnimating (Form form ) {
366+ if (form == null ) {
367+ return false ;
368+ }
369+ AnimationManager manager = form .getAnimationManager ();
370+ return manager != null && manager .isAnimating ();
371+ }
372+
373+ private boolean advanceAnimation (Form form ) {
374+ if (form == null ) {
375+ return false ;
376+ }
377+ long deadline = System .currentTimeMillis () + ANIMATION_FRAME_DELAY_MS ;
378+ boolean animating = isAnimating (form );
379+ while (System .currentTimeMillis () <= deadline && animating ) {
380+ form .animate ();
381+ TestUtils .waitFor (20 );
382+ animating = isAnimating (form );
383+ }
384+ return animating ;
385+ }
386+
151387 @ Override
152388 public boolean shouldExecuteOnEDT () {
153389 return true ;
0 commit comments