Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
798d114
Add website-targeted JavaScript Skin Designer integration
liannacasper Apr 15, 2026
eebbcbc
Fix skindesigner page loader asset path
liannacasper Apr 16, 2026
a55dca4
Ensure skindesigner app is built and embedded from app root
liannacasper Apr 16, 2026
715fcb9
Bootstrap skindesigner ZipSupport before website JS build
liannacasper Apr 16, 2026
85d7eed
Prepare skindesigner ZipSupport jar from repository assets
liannacasper Apr 16, 2026
c356f44
Normalize skindesigner ZipSupport metadata during sync
liannacasper Apr 16, 2026
6a9c56d
Polish Skin Designer UI and wire website dark mode
liannacasper Apr 16, 2026
a548fc3
Fix SkinDesigner UITimer import package
liannacasper Apr 16, 2026
9f15dff
Remove skindesigner tab overlays and improve dark mode sync
liannacasper Apr 16, 2026
0ec6cdd
Move skindesigner actions off toolbar and init theme after show
liannacasper Apr 16, 2026
41dcc63
Restore skindesigner tabs and move theme bridge into ShouldExecute
liannacasper Apr 16, 2026
2f0e4b8
Restore built-in skindesigner tabs and harden native bridge lookup
liannacasper Apr 16, 2026
9be2061
Drive skindesigner theme from iframe query and remove native bridge d…
liannacasper Apr 16, 2026
edd3b08
Harden skindesigner iframe theme URL assignment
liannacasper Apr 16, 2026
122d95f
Improve skindesigner tab/icon theming and expose primary actions
liannacasper Apr 17, 2026
f49b0c5
Fix skindesigner action button UX and positioning form chrome
liannacasper Apr 17, 2026
443b8fd
Refine SkinDesigner action buttons and help theming
liannacasper Apr 17, 2026
51b428f
Fix SkinDesigner tab initialization compile order
liannacasper Apr 17, 2026
3235330
Add dark help HTML and drag positioning support
liannacasper Apr 17, 2026
fa743b5
Improve landscape coordinate defaults and skin safe-area support
liannacasper Apr 17, 2026
4dc2c5a
Fix landscape pan defaults and expose advanced skin settings
liannacasper Apr 17, 2026
65d3404
Add flood-fill screen selection and safe-area preview controls
liannacasper Apr 17, 2026
6c59a70
Refine screen detection UX and split-pane layout
liannacasper Apr 18, 2026
7470234
Fix split orientation and tighten flood-fill detection
liannacasper Apr 18, 2026
c15de56
Make mask usage opt-in and handle transparent flood-fill seeds
liannacasper Apr 18, 2026
d6342a1
Make mask opt-in in UI and move safe area with pan
liannacasper Apr 19, 2026
aa80cc5
Rewrite aim form to fix freezes, pan, and zoom drift
shai-almog Apr 20, 2026
b4c37b9
Make safe area opt-in and polish aim form controls
shai-almog Apr 20, 2026
3ed9338
Fix aim form navigation, drop Pan, allow numeric + resize edits
shai-almog Apr 20, 2026
40e0202
Merge branch 'master' into codex/build-javascript-version-of-skindesi…
shai-almog Apr 30, 2026
d6e8f95
Complete redesign of the app
shai-almog Apr 30, 2026
5adf2f9
Potential fix for pull request finding 'Unused import'
liannacasper May 2, 2026
a8a1bd4
Skin Designer wizard polish: clickable cards, true circle stepper, ca…
shai-almog May 2, 2026
9846be4
Skin Designer device + source step polish (round borders, scroll-stab…
shai-almog May 2, 2026
fb2c59d
Skin Designer: real hover effect + user-gesture skin download
shai-almog May 2, 2026
f20199e
Skin Designer: correct CN1 CSS border syntax + cutout fix + safe-area…
shai-almog May 2, 2026
339dde4
Skin Designer: dark-mode-aware hover + transparent icon bg + em-dash …
shai-almog May 3, 2026
7c6fcd9
Skin Designer: search hint icon bg + dark-themed filter rebuild + lig…
shai-almog May 3, 2026
945c142
Skin Designer: skin .properties matches simulator expectations
shai-almog May 3, 2026
d3d17c6
Skin Designer: filter chip dark mode + safe-area in display coords
shai-almog May 3, 2026
5fa3853
Skin Designer: cutouts in top frame + rounded screen corners
shai-almog May 3, 2026
82252c3
Skin Designer: Dynamic Island floats inside the screen, notches in frame
shai-almog May 3, 2026
7234d77
Skin Designer: roundScreen=true so the skin paints over UI (floating …
shai-almog May 3, 2026
d53547a
Add Skin Designer developer-guide tutorial + CI screenshot harness
shai-almog May 3, 2026
ed02eff
Skin Designer screenshots: capture inside CN1 instead of via AWT Robot
shai-almog May 3, 2026
511a451
Skin Designer screenshots CI: extract ZipSupport main.zip on a fresh …
shai-almog May 3, 2026
3a6db4a
Skin Designer screenshots CI: wrap mvn install in xvfb-run
shai-almog May 3, 2026
abd06b7
Skin Designer: fix isDarkMode NPE + drop unsupported CSS values
shai-almog May 3, 2026
c528e13
Skin Designer: ship placeholder PNGs so the website build stops failing
shai-almog May 3, 2026
351c961
Skin Designer: generate screenshots inside the website build
shai-almog May 3, 2026
8e75529
Skin Designer screenshots: drop simulator, capture headless
shai-almog May 3, 2026
49ad157
Skin Designer screenshots: pull codename1-javase via test classpath
shai-almog May 3, 2026
682e485
Skin Designer screenshots: load /theme before building forms
shai-almog May 3, 2026
b7757f4
Skin Designer screenshots: enable global Toolbar in headless mode
shai-almog May 3, 2026
3ba90e0
Skin Designer guide: drop launching/install copy, widen screenshots
shai-almog May 3, 2026
c99996c
Simulator Skins menu: hoist Add Skin + Skin Designer to top level
shai-almog May 4, 2026
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
91 changes: 91 additions & 0 deletions .github/workflows/skin-designer-devices-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Skin Designer Device DB Update

# Runs only on master so it doesn't fire on every PR that touches the script.
# Each run pulls a small batch of the latest GSMArena entries and merges them
# into the bundled devices.json. Slow-and-steady avoids the 30+ minute scrapes
# that risk IP bans and CI cancellation.
on:
schedule:
# Every 6 hours. Each run only scrapes the "latest mobiles" page, so we
# accumulate fresh devices without ever doing a multi-thousand-page crawl.
- cron: '0 */6 * * *'
workflow_dispatch:
inputs:
mode:
description: "trickle (~50 latest) or full (all curated brands)"
required: false
default: "trickle"
type: choice
options: [trickle, full]

permissions:
actions: write
contents: write
pull-requests: write

jobs:
update-device-db:
# Only run on the canonical repo's master, never on forks or feature branches.
if: github.repository == 'codenameone/CodenameOne' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
timeout-minutes: 25

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Restore HTML scrape cache
uses: actions/cache@v4
with:
path: scripts/skindesigner/tools/devicedb/cache
key: skin-designer-devicedb-cache-${{ github.run_id }}
restore-keys: |
skin-designer-devicedb-cache-

- name: Trickle scrape (~50 latest)
if: github.event_name == 'schedule' || github.event.inputs.mode == 'trickle' || github.event.inputs.mode == ''
run: |
python3 scripts/skindesigner/tools/devicedb/build_devices_json.py \
--mode latest --limit 50 --delay 2.0 --max-pages 1

- name: Full scrape (all curated brands)
if: github.event_name == 'workflow_dispatch' && github.event.inputs.mode == 'full'
run: |
python3 scripts/skindesigner/tools/devicedb/build_devices_json.py \
--mode brands --delay 2.0 --max-pages 12

- name: Bail out if nothing changed
id: diff
run: |
if git diff --quiet -- scripts/skindesigner/common/src/main/resources/devices.json; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "Device DB unchanged; nothing to commit."
else
echo "changed=true" >> "$GITHUB_OUTPUT"
git --no-pager diff --stat -- scripts/skindesigner/common/src/main/resources/devices.json
fi

- name: Create pull request
if: steps.diff.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7
with:
commit-message: "Refresh Skin Designer device database"
title: "Refresh Skin Designer device database"
body: |
## Summary
- Regenerates `scripts/skindesigner/common/src/main/resources/devices.json`
by scraping a small batch of the latest devices from GSMArena and
merging into the existing catalog.

Created automatically by the Skin Designer device DB workflow.
base: master
branch: automation/skin-designer-device-db
delete-branch: true
labels: |
dependencies
automation
26 changes: 26 additions & 0 deletions .github/workflows/website-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@ on:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- 'docs/website/**'
- 'docs/developer-guide/**'
- 'scripts/cn1playground/**'
- 'scripts/initializr/**'
- 'scripts/website/**'
- 'scripts/skindesigner/**'
- '.github/workflows/website-docs.yml'
push:
branches: [main, master]
paths:
- 'docs/website/**'
- 'docs/developer-guide/**'
- 'scripts/cn1playground/**'
- 'scripts/initializr/**'
- 'scripts/website/**'
- 'scripts/skindesigner/**'
- '.github/workflows/website-docs.yml'
workflow_dispatch:

Expand Down Expand Up @@ -76,6 +80,28 @@ jobs:
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Set up Java 17 for Skin Designer screenshots
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'

- name: Set up xvfb for headless simulator
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends xvfb

- name: Bootstrap Codename One tooling for screenshots
run: |
set -euo pipefail
mkdir -p "$HOME/.codenameone"
cp maven/CodeNameOneBuildClient.jar "$HOME/.codenameone/CodeNameOneBuildClient.jar"
touch "$HOME/.codenameone/guibuilder.jar"

- name: Generate Skin Designer screenshots
run: ./scripts/skindesigner/screenshots/take-screenshots.sh

- name: Set up Java 25 for website build
uses: actions/setup-java@v4
with:
Expand Down
142 changes: 97 additions & 45 deletions Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
Original file line number Diff line number Diff line change
Expand Up @@ -2735,7 +2735,17 @@ private void loadSkinFile(InputStream skin, final JFrame frm) {

landscapeSkinHotspots = new HashMap<Point, Integer>();
landscapeScreenCoordinates = new Rectangle();
if(props.getProperty("roundScreen", "false").equalsIgnoreCase("true")) {
boolean roundScreen = props.getProperty("roundScreen", "false").equalsIgnoreCase("true");
boolean hasSafeAreaProps =
props.getProperty("safePortraitX") != null ||
props.getProperty("safePortraitY") != null ||
props.getProperty("safePortraitWidth") != null ||
props.getProperty("safePortraitHeight") != null ||
props.getProperty("safeLandscapeX") != null ||
props.getProperty("safeLandscapeY") != null ||
props.getProperty("safeLandscapeWidth") != null ||
props.getProperty("safeLandscapeHeight") != null;
if(roundScreen) {
safeAreaLandscape = new Rectangle();
safeAreaPortrait = new Rectangle();

Expand Down Expand Up @@ -2764,6 +2774,22 @@ private void loadSkinFile(InputStream skin, final JFrame frm) {
} else {
initializeCoordinates(map, props, portraitSkinHotspots, portraitScreenCoordinates);
initializeCoordinates(landscapeMap, props, landscapeSkinHotspots, landscapeScreenCoordinates);
if (hasSafeAreaProps) {
safeAreaPortrait = new Rectangle();
safeAreaLandscape = new Rectangle();
safeAreaPortrait.setBounds(
Integer.parseInt(props.getProperty("safePortraitX", "" + portraitScreenCoordinates.x)),
Integer.parseInt(props.getProperty("safePortraitY", "" + portraitScreenCoordinates.y)),
Integer.parseInt(props.getProperty("safePortraitWidth", "" + portraitScreenCoordinates.width)),
Integer.parseInt(props.getProperty("safePortraitHeight", "" + portraitScreenCoordinates.height))
);
safeAreaLandscape.setBounds(
Integer.parseInt(props.getProperty("safeLandscapeX", "" + landscapeScreenCoordinates.x)),
Integer.parseInt(props.getProperty("safeLandscapeY", "" + landscapeScreenCoordinates.y)),
Integer.parseInt(props.getProperty("safeLandscapeWidth", "" + landscapeScreenCoordinates.width)),
Integer.parseInt(props.getProperty("safeLandscapeHeight", "" + landscapeScreenCoordinates.height))
);
}
}


Expand Down Expand Up @@ -4832,6 +4858,74 @@ private JMenu createSkinsMenu(final JFrame frm, final JMenu menu) throws Malform
m.removeAll();
}
final JMenu skinMenu = m;

// Top-level: file picker for a user-supplied .skin
JMenuItem addSkin = new JMenuItem("Add Skin");
addSkin.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
FileDialog picker = new FileDialog(frm, "Add Skin");
picker.setMode(FileDialog.LOAD);
picker.setFilenameFilter(new FilenameFilter() {
public boolean accept(File file, String string) {
return string.endsWith(".skin");
}
});
picker.setModal(true);
picker.setVisible(true);
String file = picker.getFile();
if (file != null) {
if (netMonitor != null) {
netMonitor.dispose();
netMonitor = null;
}
if (perfMonitor != null) {
perfMonitor.dispose();
perfMonitor = null;
}
String mainClass = System.getProperty("MainClass");
if (mainClass != null) {
Preferences p = Preferences.userNodeForPackage(JavaSEPort.class);
p.put("skin", picker.getDirectory() + File.separator + file);
deinitializeSync();
frm.dispose();
System.setProperty("reload.simulator", "true");
} else {
loadSkinFile(picker.getDirectory() + File.separator + file, frm);
refreshSkin(frm);
}
}
}
});
skinMenu.add(addSkin);

// Top-level: hand off to the hosted Skin Designer for building
// a new skin from scratch. Replaces the bundled gallery; the
// pre-built skins all live behind the "Legacy Skins" submenu.
JMenuItem designerItem = new JMenuItem("Skin Designer");
designerItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
try {
Desktop.getDesktop().browse(new URI("https://www.codenameone.com/skindesigner/"));
} catch (Exception err) {
Logger.getLogger(JavaSEPort.class.getName()).log(Level.WARNING,
"Could not open Skin Designer in browser", err);
}
}
});
skinMenu.add(designerItem);

skinMenu.addSeparator();

final JMenu legacyMenu = new JMenu("Legacy Skins");
legacyMenu.setDoubleBuffered(true);
skinMenu.add(legacyMenu);
populateLegacySkinsMenu(frm, legacyMenu);
return skinMenu;
}

private void populateLegacySkinsMenu(final JFrame frm, final JMenu legacyMenu) throws MalformedURLException {
legacyMenu.removeAll();
final JMenu skinMenu = legacyMenu;
Preferences pref = Preferences.userNodeForPackage(JavaSEPort.class);
String skinNames = pref.get("skins", DEFAULT_SKINS);
if (skinNames != null) {
Expand Down Expand Up @@ -5147,7 +5241,7 @@ public void run() {
downloadMessage.setVisible(false);
d.setVisible(false);
try {
createSkinsMenu(frm, skinMenu);
populateLegacySkinsMenu(frm, skinMenu);
} catch (MalformedURLException ex) {
Logger.getLogger(JavaSEPort.class.getName()).log(Level.SEVERE, null, ex);
}
Expand All @@ -5174,47 +5268,6 @@ public void run() {
}
});

skinMenu.addSeparator();
JMenuItem addSkin = new JMenuItem("Add New...");
skinMenu.add(addSkin);
addSkin.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent ae) {
FileDialog picker = new FileDialog(frm, "Add Skin");
picker.setMode(FileDialog.LOAD);
picker.setFilenameFilter(new FilenameFilter() {

public boolean accept(File file, String string) {
return string.endsWith(".skin");
}
});
picker.setModal(true);
picker.setVisible(true);
String file = picker.getFile();
if (file != null) {
if (netMonitor != null) {
netMonitor.dispose();
netMonitor = null;
}
if (perfMonitor != null) {
perfMonitor.dispose();
perfMonitor = null;
}
String mainClass = System.getProperty("MainClass");
if (mainClass != null) {
Preferences pref = Preferences.userNodeForPackage(JavaSEPort.class);
pref.put("skin", picker.getDirectory() + File.separator + file);
deinitializeSync();
frm.dispose();
System.setProperty("reload.simulator", "true");
} else {
loadSkinFile(picker.getDirectory() + File.separator + file, frm);
refreshSkin(frm);
}
}
}
});

skinMenu.addSeparator();
JMenuItem reset = new JMenuItem("Reset Skins");
skinMenu.add(reset);
Expand Down Expand Up @@ -5259,10 +5312,9 @@ public void actionPerformed(ActionEvent ae) {
}

}

}
});
return skinMenu;
}

InputStream openSkinsURL() throws IOException {
Expand Down
Loading
Loading