Skip to content

Commit ddf03de

Browse files
shai-almogclaude
andcommitted
hellocodenameone: stabilize DualAppearanceBaseTest screenshot timing
Two fixes in one: 1. Race in light->dark hand-off (visible in builds 25393887679 and 25396087564 as DialogTheme_dark.png containing the "DialogTheme / light" form, FloatingActionButtonTheme_light.png containing the dark dialog form). The chain `light_screenshot -> next.run() -> setDarkMode(true) -> refreshTheme() -> new Form -> form.show() -> onShowCompleted -> UITimer 1500 ms -> emitCurrentFormScreenshot` completed every step in order, but on iOS Metal the show transition was still drawing the previous form into the back buffer when the timer fired. cn1_captureView in IOSNative.m uses `drawViewHierarchyInRect:afterScreenUpdates:NO` (the YES variant stalls under UIScene), so the CAMetalLayer's front buffer still held the previous form's pixels and the screenshot grabbed those. Two changes guard against this: - `setTransitionInAnimator(CommonTransitions.createEmpty())` on the dual-appearance form (and matching out-animator) makes form.show() switch synchronously, removing the ~300 ms transition window during which the back buffer is still painting the previous form. - Wrap emitCurrentFormScreenshot in three nested Display.callSerially hops so at least three EDT paint cycles (and three Metal display-link presents) land between onShowCompleted's hand-off and the capture call. 2. landscape.png golden was the wrong artifact. The previous golden- refresh commit (427ceaf) picked up the half-baked render from build 25393887679 -- title bar background was still being painted at capture time. Re-pulled from build 25396087564 which has the fully-rendered title bar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 427ceaf commit ddf03de

2 files changed

Lines changed: 28 additions & 1 deletion

File tree

scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DualAppearanceBaseTest.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.codename1.ui.Form;
1010
import com.codename1.ui.Graphics;
1111
import com.codename1.ui.Painter;
12+
import com.codename1.ui.animations.CommonTransitions;
1213
import com.codename1.ui.geom.Rectangle;
1314
import com.codename1.ui.layouts.Layout;
1415
import com.codename1.ui.plaf.Style;
@@ -150,10 +151,36 @@ protected void onShowCompleted() {
150151
// race over the same transitioning buffer and produce
151152
// byte-identical PNGs (classic symptom was
152153
// ButtonTheme_light.png == ButtonTheme_dark.png).
153-
Cn1ssDeviceRunnerHelper.emitCurrentFormScreenshot(imageName, next);
154+
//
155+
// Even with that chain in place, on iOS Metal the
156+
// light->dark show transition was leaving the previous
157+
// frame's pixels in the CAMetalLayer at the moment
158+
// cn1_captureView ran with afterScreenUpdates:NO, so
159+
// the dark-tagged screenshot grabbed light-form pixels
160+
// (visible victims: DialogTheme_dark, FloatingAction-
161+
// ButtonTheme_light). Pump three Display.callSerially
162+
// hops before emit so at least three EDT paint cycles
163+
// (and therefore three Metal frame presents) land
164+
// between the form's own onShowCompleted hand-off and
165+
// the actual capture; combined with the createEmpty()
166+
// transition below this gives the new form's pixels
167+
// time to reach the front buffer.
168+
Display.getInstance().callSerially(() ->
169+
Display.getInstance().callSerially(() ->
170+
Display.getInstance().callSerially(() ->
171+
Cn1ssDeviceRunnerHelper.emitCurrentFormScreenshot(imageName, next))));
154172
});
155173
}
156174
};
175+
// Skip the form-show transition entirely. The default fade/slide
176+
// takes ~300ms during which CN1 is still drawing the *previous*
177+
// form into the back buffer; on iOS Metal that means the screen-
178+
// shot's CAMetalLayer contents linger on the old frame even after
179+
// onShowCompleted fires. createEmpty() makes form.show() switch
180+
// synchronously so onShowCompleted fires with the new form
181+
// already painted, removing the transition window from the race.
182+
form.setTransitionInAnimator(CommonTransitions.createEmpty());
183+
form.setTransitionOutAnimator(CommonTransitions.createEmpty());
157184
populate(form, suffix);
158185
if (textured) {
159186
// The ContentPane sits on top of the Form and paints its own
-5.73 KB
Loading

0 commit comments

Comments
 (0)