Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4929,16 +4929,27 @@ public void installNativeTheme() {
return;
}
try {
// Resolve desired theme flavor. cn1.androidTheme is the new per-CN1
// hint (material | hololight | legacy). The Material 3 modern theme
// is opt-in via cn1.androidTheme=material / modern. Default is
// android_holo_light - what master shipped and what existing
// screenshot goldens are anchored against. The ancient pre-Holo
// androidTheme.res is only reached via explicit and.hololight=true
// (historical back-compat) or cn1.androidTheme=legacy.
String mode = Display.getInstance().getProperty("cn1.androidTheme", null);
// Resolve desired theme flavor. and.themeMode is the per-platform
// hint (auto | modern | material | hololight | legacy); the legacy
// name cn1.androidTheme is still honored for back-compat. The
// cross-platform shortcut nativeTheme=modern/legacy (deprecated
// alias: cn1.nativeTheme) feeds in when no platform-specific hint
// is set. Default stays on android_holo_light - what master
// shipped and what existing screenshot goldens are anchored
// against. The ancient pre-Holo androidTheme.res is only reached
// via explicit and.hololight=true (historical back-compat) or
// and.themeMode=legacy.
Display d = Display.getInstance();
String mode = d.getProperty("and.themeMode",
d.getProperty("cn1.androidTheme", null));
if (mode == null) {
if ("true".equalsIgnoreCase(Display.getInstance().getProperty("and.hololight", "false"))) {
String shared = d.getProperty("nativeTheme",
d.getProperty("cn1.nativeTheme", null));
if ("modern".equalsIgnoreCase(shared)) {
mode = "material";
} else if ("legacy".equalsIgnoreCase(shared)) {
mode = "hololight";
} else if ("true".equalsIgnoreCase(d.getProperty("and.hololight", "false"))) {
mode = "legacy";
} else {
mode = "hololight";
Expand All @@ -4948,7 +4959,7 @@ public void installNativeTheme() {
}

String resPath;
if ("material".equals(mode) || "modern".equals(mode)) {
if ("material".equals(mode) || "modern".equals(mode) || "auto".equals(mode)) {
resPath = "/AndroidMaterialTheme.res";
} else if ("hololight".equals(mode) || "holo".equals(mode)) {
resPath = "/android_holo_light.res";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@

/**
* Registers schema metadata for the native-theme build hints
* (ios.themeMode, cn1.androidTheme, cn1.nativeTheme) so that the
* (ios.themeMode, and.themeMode, nativeTheme) so that the
* Build Hints UI inside the Codename One Simulator can show them as
* labelled Select dropdowns instead of opaque key/value entries.
*
* <p>The deprecated keys {@code cn1.nativeTheme} and
* {@code cn1.androidTheme} are still honored at runtime but are no
* longer surfaced in the schema - new projects should use
* {@code nativeTheme} / {@code and.themeMode} (matching the
* {@code ios.themeMode} pattern).
*
* <p><b>Why this class exists:</b> {@link com.codename1.impl.javase.BuildHintEditor}
* is the dialog that lets developers set build hints from the
* Simulator menu (Project &rarr; Build Hints). It populates its rows by
Expand Down Expand Up @@ -57,14 +63,15 @@ static void register() {
+ "legacy themes remain selectable via the values below.");

// Cross-platform meta hint.
set("{{#nativeTheme#cn1.nativeTheme}}.label", "Shared override");
set("{{#nativeTheme#cn1.nativeTheme}}.type", "Select");
set("{{#nativeTheme#cn1.nativeTheme}}.values", "modern,legacy,custom");
set("{{#nativeTheme#cn1.nativeTheme}}.description",
set("{{#nativeTheme#nativeTheme}}.label", "Shared override");
set("{{#nativeTheme#nativeTheme}}.type", "Select");
set("{{#nativeTheme#nativeTheme}}.values", "modern,legacy,custom");
set("{{#nativeTheme#nativeTheme}}.description",
"Overrides both iOS and Android native theme selection. "
+ "\"modern\" = liquid glass / Material 3. \"legacy\" = iOS 7 "
+ "flat / Android Holo Light. \"custom\" disables the framework "
+ "default and expects the app to install its own.");
+ "default and expects the app to install its own. "
+ "(Deprecated alias: cn1.nativeTheme.)");

// iOS.
set("{{#nativeTheme#ios.themeMode}}.label", "iOS theme");
Expand All @@ -76,13 +83,14 @@ static void register() {
+ "legacy / iphone = pre-iOS7 theme.");

// Android.
set("{{#nativeTheme#cn1.androidTheme}}.label", "Android theme");
set("{{#nativeTheme#cn1.androidTheme}}.type", "Select");
set("{{#nativeTheme#cn1.androidTheme}}.values", "material,hololight,legacy");
set("{{#nativeTheme#cn1.androidTheme}}.description",
"material = Material 3 (default). hololight = Android Holo "
+ "Light (API 14+). legacy = pre-Holo Android theme. "
+ "and.hololight=true is accepted for back-compat.");
set("{{#nativeTheme#and.themeMode}}.label", "Android theme");
set("{{#nativeTheme#and.themeMode}}.type", "Select");
set("{{#nativeTheme#and.themeMode}}.values", "auto,modern,hololight,legacy");
set("{{#nativeTheme#and.themeMode}}.description",
"auto = modern (default). modern / material = Material 3. "
+ "hololight = Android Holo Light (API 14+). legacy = pre-Holo "
+ "Android theme. (Deprecated alias: cn1.androidTheme; "
+ "and.hololight=true is also accepted for back-compat.)");
}

/** Idempotent setter: does not overwrite user / project-level hint metadata. */
Expand Down
202 changes: 180 additions & 22 deletions Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,126 @@ public void installNativeTheme() {
}
}

/**
* Resolves the override theme for the "auto" Native Theme menu choice.
* Reads ios.themeMode / and.themeMode / nativeTheme from the project's
* build hints (loaded into system properties as
* codename1.arg.&lt;hint&gt;), falling back to the deprecated
* cn1.androidTheme / cn1.nativeTheme aliases. When no hint is set, the
* platformName drives the choice and "ios" / "and" map to the modern
* themes that initializr / Playground now default to. Returns the
* theme resource basename (without ".res") or {@code null} to fall back
* to the skin's embedded theme.
*/
private static String resolveAutoNativeTheme(String platformName) {
if ("ios".equals(platformName)) {
String iosMode = buildHint("ios.themeMode");
if (iosMode != null) {
if ("modern".equalsIgnoreCase(iosMode) || "liquid".equalsIgnoreCase(iosMode)) {
return "iOSModernTheme";
}
if ("ios7".equalsIgnoreCase(iosMode) || "flat".equalsIgnoreCase(iosMode)) {
return "iOS7Theme";
}
if ("legacy".equalsIgnoreCase(iosMode) || "iphone".equalsIgnoreCase(iosMode)) {
return "iPhoneTheme";
}
}
String shared = sharedNativeThemeHint();
if ("legacy".equalsIgnoreCase(shared)) {
return "iOS7Theme";
}
if ("custom".equalsIgnoreCase(shared)) {
return null;
}
// Default for an iOS skin is the modern theme.
return "iOSModernTheme";
}
if ("and".equals(platformName)) {
String andMode = buildHint("and.themeMode");
if (andMode == null) {
andMode = buildHint("cn1.androidTheme");
}
if (andMode != null) {
if ("modern".equalsIgnoreCase(andMode) || "material".equalsIgnoreCase(andMode)) {
return "AndroidMaterialTheme";
}
if ("hololight".equalsIgnoreCase(andMode) || "holo".equalsIgnoreCase(andMode)) {
return "android_holo_light";
}
if ("legacy".equalsIgnoreCase(andMode)) {
return "androidTheme";
}
}
String shared = sharedNativeThemeHint();
if ("legacy".equalsIgnoreCase(shared)) {
return "android_holo_light";
}
if ("custom".equalsIgnoreCase(shared)) {
return null;
}
// Default for an Android skin is Material 3.
return "AndroidMaterialTheme";
}
return null;
}

private static String sharedNativeThemeHint() {
String v = buildHint("nativeTheme");
if (v == null) {
v = buildHint("cn1.nativeTheme");
}
return v;
}

/**
* Reads a build hint from the runtime - first the
* codename1.arg.&lt;name&gt; system property (set by the
* Maven plugin / Simulator), then the loaded
* codenameone_settings.properties on disk so unit-test invocations
* without the simulator wrapper still see the value.
*/
private static String buildHint(String name) {
String v = System.getProperty("codename1.arg." + name);
if (v != null && !v.isEmpty()) {
return v;
}
Properties cnop = loadCodenameOneSettings();
if (cnop != null) {
v = cnop.getProperty("codename1.arg." + name);
if (v != null && !v.isEmpty()) {
return v;
}
}
return null;
}

private static Properties cachedCnopProperties;
private static long cachedCnopMtime = -1L;

private static Properties loadCodenameOneSettings() {
File f = new File(getCWD(), "codenameone_settings.properties");
if (!f.exists()) {
f = new File(getCWD(), "common" + File.separator + "codenameone_settings.properties");
}
if (!f.exists()) {
return null;
}
long mtime = f.lastModified();
if (cachedCnopProperties != null && cachedCnopMtime == mtime) {
return cachedCnopProperties;
}
Properties p = new Properties();
try (FileInputStream in = new FileInputStream(f)) {
p.load(in);
} catch (IOException ex) {
return cachedCnopProperties;
}
cachedCnopProperties = p;
cachedCnopMtime = mtime;
return p;
}

/**
* @return the useNativeInput
*/
Expand Down Expand Up @@ -2775,20 +2895,21 @@ private void loadSkinFile(InputStream skin, final JFrame frm) {
// plus the legacy ones). The user can override via the
// Simulator's "Native Theme" submenu (stored in the
// simulatorNativeTheme Preference) or the cn1.forceSimulatorTheme
// system property. If neither is set, platformName maps ios ->
// iOSModernTheme and and -> AndroidMaterialTheme. Anything else
// keeps whatever the skin archive embedded.
// system property. When "auto" is selected we consult the
// project's build hints (ios.themeMode / and.themeMode /
// nativeTheme, plus the deprecated cn1.androidTheme /
// cn1.nativeTheme aliases) so a project that opted in to
// ios.themeMode=modern actually previews with the modern
// theme instead of an unrelated default. If no hint is set we
// keep mapping platformName ios -> iOSModernTheme and
// and -> AndroidMaterialTheme - the new defaults shipped by
// the initializr / Playground - so a brand new simulator run
// matches what the device build will look like.
String overrideTheme = System.getProperty("cn1.forceSimulatorTheme",
Preferences.userNodeForPackage(JavaSEPort.class)
.get("simulatorNativeTheme", null));
if (overrideTheme == null || overrideTheme.isEmpty() || "auto".equalsIgnoreCase(overrideTheme)) {
if ("ios".equals(platformName)) {
overrideTheme = "iOSModernTheme";
} else if ("and".equals(platformName)) {
overrideTheme = "AndroidMaterialTheme";
} else {
overrideTheme = null;
}
overrideTheme = resolveAutoNativeTheme(platformName);
} else if ("embedded".equalsIgnoreCase(overrideTheme)) {
// Explicit "keep the skin's embedded theme".
overrideTheme = null;
Expand Down Expand Up @@ -3667,6 +3788,41 @@ public void itemStateChanged(ItemEvent e) {
});
simulatorMenu.add(autoLocalizationMenu);

// Rotate menu item: only added when app-frame mode is off. The
// app-frame toolbar already exposes Portrait / Landscape RotateAction
// buttons, so duplicating them in the menu would be confusing. When
// the user has Single Window mode disabled there is no toolbar, so
// the only way to rotate is via this menu item - it was lost when
// the toolbar buttons were introduced and the menu entry was
// removed wholesale instead of gated behind appFrame == null.
final JMenuItem rotateMenu = new JMenuItem("Rotate");
rotateMenu.setEnabled(!desktopSkin);
rotateMenu.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
setPortrait(!portrait);
Container parent = canvas.getParent();
parent.remove(canvas);
canvas.setForcedSize(new java.awt.Dimension(
(int) (getSkin().getWidth() * zoomLevel),
(int) (getSkin().getHeight() * zoomLevel)));
if (window != null) {
window.setSize(new java.awt.Dimension(
(int) (getSkin().getWidth() * zoomLevel),
(int) (getSkin().getHeight() * zoomLevel)));
}
java.awt.Container top = ((JComponent) parent).getTopLevelAncestor();
top.revalidate();
top.repaint();
parent.add(BorderLayout.CENTER, canvas);
if (window != null) {
window.pack();
}
JavaSEPort.this.sizeChanged(getScreenCoordinates().width, getScreenCoordinates().height);
}
});
if (appFrame == null) simulatorMenu.add(rotateMenu);

final JCheckBoxMenuItem zoomMenu = new JCheckBoxMenuItem("Zoom", scrollableSkin);
if (appFrame == null) simulatorMenu.add(zoomMenu);

Expand Down Expand Up @@ -4671,7 +4827,7 @@ public void actionPerformed(ActionEvent e) {
bar.add(simulateMenu);
bar.add(toolsMenu);
bar.add(skinMenu);
bar.add(createNativeThemeMenu());
bar.add(createNativeThemeMenu(frm));
bar.add(helpMenu);
}

Expand Down Expand Up @@ -4780,18 +4936,21 @@ private String getCurrentSkinName() {
}

/**
* Build the Native Theme override menu. By default the simulator picks a
* theme from the current skin's platformName ("ios" -&gt; iOSModernTheme,
* "and" -&gt; AndroidMaterialTheme); this menu lets the user force one
* of the shipped themes or "Use skin's embedded theme" to bypass the
* heuristic entirely. Selection is written to the simulatorNativeTheme
* Preference and the simulator is reloaded.
* Build the Native Theme override menu. "Auto" defers to the project's
* build hints (ios.themeMode / and.themeMode / nativeTheme) so a project
* that opted in via codenameone_settings.properties previews with the
* theme it would ship with; the explicit choices below force a
* specific theme regardless of build hints. Selection is written to
* the simulatorNativeTheme Preference and the simulator is reloaded
* via {@code reload.simulator} - the same mechanism the skin menu
* uses, so disposing the JFrame is what actually triggers the
* Simulator polling thread to pick up the new theme.
*/
private JMenu createNativeThemeMenu() {
private JMenu createNativeThemeMenu(final JFrame frm) {
JMenu m = new JMenu("Native Theme");
m.setDoubleBuffered(true);
String[][] items = {
{"auto", "Auto (based on skin)"},
{"auto", "Auto (from build hints)"},
{"iOSModernTheme", "iOS Modern (Liquid Glass)"},
{"iOS7Theme", "iOS 7 (Flat)"},
{"iPhoneTheme", "iPhone (Pre-Flat)"},
Expand All @@ -4810,10 +4969,9 @@ private JMenu createNativeThemeMenu() {
public void actionPerformed(ActionEvent e) {
Preferences.userNodeForPackage(JavaSEPort.class)
.put("simulatorNativeTheme", entry[0]);
deinitializeSync();
frm.dispose();
System.setProperty("reload.simulator", "true");
if (window != null) {
window.dispose();
}
}
});
group.add(mi);
Expand Down
Loading
Loading