Skip to content

Commit 896ea3a

Browse files
shai-almogclaude
andcommitted
Migrate a wizard session saved before the safe-area unit changed
Two more review findings. The persisted wizard state survives a reload, and SkinModel.load() read safeTop/safeBottom back verbatim. Those numbers were entered as preview viewbox units and are now read as points or dp, so a session left by the older build silently regenerates a different skin: a hand-tuned 40 on a 1080-wide 400ppi Android device meant 135px then and would mean 100px now. The persisted model carries a schema version, and a session below the current one takes its insets fresh from the device catalog. There is nothing to convert back to -- a value tuned by eye was tuned against the scaling this branch fixes -- and an untouched session got the same numbers from the catalog anyway. A session with no device selected goes back to the device step, where resetForDevice refills both values on the way through. The migration is written back once rather than repeated on every reload. The documented property example omitted the `cutouts` key the wizard now writes. A skin rebuilt from that example would paint the opaque island and give JavaSEPort.parseCutouts() nothing, leaving pointer events free to reach the content underneath -- exactly the bug the key exists to close. The example carries the real value for its device (377,53,452,132, checked against the generator), and the guide's property list and cutouts section mention it. Also drops this branch's own fix for the signup-funnel test: master fixed it in #5628 with an attribute parser rather than a looser regex, which is the better of the two, so the rebase takes master's version. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 7345c65 commit 896ea3a

4 files changed

Lines changed: 67 additions & 2 deletions

File tree

‎docs/demos/common/src/main/snippets/developer-guide/skin-designer.properties‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ displayY=151
1717
displayWidth=1206
1818
displayHeight=2622
1919

20+
# The cutouts the skin paints on the screen, as display-relative
21+
# x,y,w,h rectangles separated by ";". The simulator drops pointer events
22+
# inside them, so a click on the opaque island cannot reach app content
23+
# hidden underneath. A skin that omits this key keeps every cutout
24+
# clickable.
25+
cutouts=377,53,452,132
26+
2027
# Safe area in display-relative coordinates (origin = screen top-left),
2128
# in PIXELS. The Info tab's Top/Bottom fields are points (iOS) or dp
2229
# (Android); the wizard multiplies them by the device's density scale.

‎docs/developer-guide/Skin-Designer.asciidoc‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ pixels. For islands and holes, `Offset Y` is the gap between the
123123
cutout's top edge and the screen top. For notches, `Offset Y` is
124124
ignored, because a notch is anchored to the screen's top edge.
125125

126+
The wizard also writes each cutout's rectangle into the `cutouts`
127+
property. The simulator drops pointer events that land inside one, so a
128+
click on an opaque notch or island can't reach app content hidden
129+
underneath it, the way the hardware cutout can't be touched at all.
130+
126131
WARNING: When the wizard generates the `.skin` file, it
127132
automatically pushes `safePortraitY` down to the lowest cutout edge
128133
whenever that sits below the platform's own status-bar inset, so app
@@ -170,7 +175,7 @@ Clicking *Finish* in the editor's footer:
170175
`android_holo_light.res`, or `winTheme.res`) inside the skin zip.
171176
. Writes `skin.properties` with `roundScreen=true`,
172177
`displayX/Y/Width/Height`, `safePortrait*` / `safeLandscape*`,
173-
the platform name, override names, fonts, and PPI.
178+
`cutouts`, the platform name, override names, fonts, and PPI.
174179

175180
Clicking *Download skin* on the done page hands the file to the
176181
browser's download dialog. The button is the only reliable trigger

‎scripts/skindesigner/common/src/main/java/com/codename1/tools/skindesigner/SkinDesigner.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ private void loadState() {
174174
device = DeviceDatabase.findById(id);
175175
source = Preferences.get(PREF_SOURCE, null);
176176
skin.load();
177+
if (skin.migrateSafeAreaUnits(device)) {
178+
// Write the migrated state straight back, so the conversion
179+
// happens once rather than on every reload.
180+
skin.save();
181+
}
177182
if (device != null && skin.name == null) {
178183
skin.resetForDevice(device);
179184
}

‎scripts/skindesigner/common/src/main/java/com/codename1/tools/skindesigner/SkinModel.java‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,21 @@ public Cutout(String type, int w, int h, int x, int y, String name) {
7575

7676
private static final String P = "wiz.skin.";
7777

78+
/**
79+
* Layout version of the persisted wizard state.
80+
*
81+
* <p>1 stored {@link #safeTop} / {@link #safeBottom} in preview viewbox
82+
* units, which the generator scaled by {@code resolutionW / 320}. 2
83+
* stores them in the density-independent units the device catalog uses
84+
* -- points on iOS, dp on Android -- scaled by the device's density.
85+
* The numbers look identical in storage and mean different pixel counts,
86+
* so a session saved by the older build has to be migrated rather than
87+
* loaded; see {@link #migrateSafeAreaUnits}.</p>
88+
*/
89+
private static final int SCHEMA = 2;
90+
91+
private int schema = SCHEMA;
92+
7893
public void resetForDevice(DeviceDatabase.Device d) {
7994
presetId = "rr";
8095
// Drop the trailing " skin" from the generated name — sanitize() then
@@ -105,7 +120,37 @@ public void resetForDevice(DeviceDatabase.Device d) {
105120
}
106121
}
107122

123+
/**
124+
* Brings a session persisted by an older build up to {@link #SCHEMA},
125+
* and reports whether anything changed so the caller can write it back.
126+
*
127+
* <p>Schema 1 stored the safe-area insets in viewbox units. Reading one
128+
* of those numbers as points would silently regenerate a different skin
129+
* from the one the user left behind -- a hand-tuned 40 on a 1080-wide,
130+
* 400ppi Android device meant 135px then and would mean 100px now. There
131+
* is nothing to convert back to, either: a value the user tuned by eye
132+
* was tuned against the scaling this change fixes. So the insets are
133+
* taken fresh from the device catalog, which is where an untouched
134+
* session got them anyway.</p>
135+
*
136+
* <p>With no device selected there is nothing to take them from, but the
137+
* wizard sends that session back to the device step and
138+
* {@link #resetForDevice} refills both values on the way through.</p>
139+
*/
140+
public boolean migrateSafeAreaUnits(DeviceDatabase.Device d) {
141+
if (schema >= SCHEMA) {
142+
return false;
143+
}
144+
schema = SCHEMA;
145+
if (d != null) {
146+
safeTop = d.safeTop;
147+
safeBottom = d.safeBottom;
148+
}
149+
return true;
150+
}
151+
108152
public void save() {
153+
Preferences.set(P + "schema", SCHEMA);
109154
Preferences.set(P + "presetId", presetId);
110155
Preferences.set(P + "name", name);
111156
Preferences.set(P + "cornerR", cornerR);
@@ -127,6 +172,9 @@ public void save() {
127172
}
128173

129174
public void load() {
175+
// Absent means a session written before the key existed, which is
176+
// schema 1 by definition.
177+
schema = Preferences.get(P + "schema", 1);
130178
presetId = Preferences.get(P + "presetId", presetId);
131179
name = Preferences.get(P + "name", name);
132180
cornerR = Preferences.get(P + "cornerR", cornerR);
@@ -150,7 +198,7 @@ public void load() {
150198
}
151199

152200
public static void clearPersisted() {
153-
for (String key : new String[]{"presetId", "name", "cornerR", "bezel",
201+
for (String key : new String[]{"schema", "presetId", "name", "cornerR", "bezel",
154202
"homeIndicator", "safeTop", "safeBottom", "cutoutCount"}) {
155203
Preferences.delete(P + key);
156204
}

0 commit comments

Comments
 (0)