From 026d7960002e2afae17d749c223f792e9ef480b0 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:01:01 +0300 Subject: [PATCH 01/56] Playground UI redesign --- .../common/src/main/css/theme.css | 1217 ++++++++++++----- .../codenameone/playground/CN1Playground.java | 695 +++++++--- .../playground/PlaygroundActivityBar.java | 104 ++ .../playground/PlaygroundExamples.java | 25 +- .../playground/PlaygroundHistoryPanel.java | 116 ++ .../playground/PlaygroundPreviewColumn.java | 258 ++++ .../playground/PlaygroundSamplesPanel.java | 115 ++ .../playground/PlaygroundSegmented.java | 106 ++ .../playground/PlaygroundSidePanel.java | 76 + .../playground/PlaygroundStateStore.java | 42 +- .../playground/PlaygroundStatusPill.java | 75 + .../playground/PlaygroundTopBar.java | 173 +++ 12 files changed, 2470 insertions(+), 532 deletions(-) create mode 100644 scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundActivityBar.java create mode 100644 scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundHistoryPanel.java create mode 100644 scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundPreviewColumn.java create mode 100644 scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundSamplesPanel.java create mode 100644 scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundSegmented.java create mode 100644 scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundSidePanel.java create mode 100644 scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundStatusPill.java create mode 100644 scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundTopBar.java diff --git a/scripts/cn1playground/common/src/main/css/theme.css b/scripts/cn1playground/common/src/main/css/theme.css index 53c30f0afc..15e9b75e6f 100644 --- a/scripts/cn1playground/common/src/main/css/theme.css +++ b/scripts/cn1playground/common/src/main/css/theme.css @@ -6,13 +6,34 @@ sideMenuSizeTabLandscapeInt: "75"; } -/** Style for Button class */ +/* ------------------------------------------------------------ + * DESIGN TOKENS + * + * Light palette Dark palette + * --bg-app #F7F8FB #0A0F1E + * --bg-panel #FFFFFF #102B66 + * --bg-subtle #F0F2F7 #0B2055 + * --bg-input #FFFFFF #0E2A61 + * --border #E1E4EA #253B73 + * --text #112247 #F5F8FF + * --muted #7F8AA3 #A8B8DA + * --soft #AAB2C5 #6E80A6 + * --accent #2F6BFF #4D86FF + * --accent-soft #E6EDFF #1F3A7A + * --success #36A853 #5FCF7C + * --danger #D93636 #FF6B6B + * --activity-bar #0E1A34 (both themes) + * --activity-text #A2ADC9 + * --activity-active #FFFFFF + * ------------------------------------------------------------ */ + +/* ----- Standard component defaults ----- */ + Button { font-family: "native:MainLight"; font-size: 3mm; } -/** Style for App Title Bar Text */ Title { font-family: "native:MainLight"; font-size: 6mm; @@ -22,85 +43,918 @@ TitleArea { background: #F3F4F7; } +DialogBody { + font-family: "native:MainLight"; + font-size: 2.8mm; +} + +DialogTitle { + font-family: "native:MainLight"; + font-size: 4.5mm; +} + +/* ----- Shell: form + body chrome ----- */ + PlaygroundForm, PlaygroundContent { background: #F7F8FB; } PlaygroundFormDark, PlaygroundContentDark { - background: #071B4D; + background: #0A0F1E; +} + +PlaygroundBody { + background: #F7F8FB; + padding: 0; + margin: 0; +} + +PlaygroundBodyDark { + background: #0A0F1E; + padding: 0; + margin: 0; } PlaygroundToolbar { background: #F3F4F7; - padding: 1mm 2mm 1mm 2mm; + padding: 0; } PlaygroundToolbarDark { - background: #102B66; - padding: 1mm 2mm 1mm 2mm; + background: #0A0F1E; + padding: 0; } PlaygroundTitle { color: #112247; - background: #F3F4F7; + background: transparent; font-family: "native:MainLight"; font-size: 6mm; } PlaygroundTitleDark { color: #F5F8FF; - background: #102B66; + background: transparent; font-family: "native:MainLight"; font-size: 6mm; } -PlaygroundPanel { +/* ----- Top bar ----- */ + +PlaygroundTopBar { + background: #FFFFFF; + border-bottom: 1px solid #E1E4EA; + padding: 1.5mm 3mm 1.5mm 3mm; +} + +PlaygroundTopBarDark { + background: #102B66; + border-bottom: 1px solid #253B73; + padding: 1.5mm 3mm 1.5mm 3mm; +} + +PlaygroundWordmark { + color: #112247; + background: transparent; + font-family: "native:MainBold"; + font-size: 3.5mm; + padding: 0 2mm 0 2mm; +} + +PlaygroundWordmarkDark { + color: #F5F8FF; + background: transparent; + font-family: "native:MainBold"; + font-size: 3.5mm; + padding: 0 2mm 0 2mm; +} + +/* ----- Segmented control ----- */ + +PlaygroundSegment { + background: #F0F2F7; + border: 1px solid #E1E4EA; + padding: 0.5mm; + margin: 0; +} + +PlaygroundSegmentDark { + background: #0B2055; + border: 1px solid #253B73; + padding: 0.5mm; + margin: 0; +} + +PlaygroundSegmentOption { + background: transparent; + color: #7F8AA3; + border: none; + font-family: "native:MainLight"; + font-size: 2.8mm; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 0; + text-decoration: none; +} + +PlaygroundSegmentOptionDark { + background: transparent; + color: #A8B8DA; + border: none; + font-family: "native:MainLight"; + font-size: 2.8mm; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 0; + text-decoration: none; +} + +PlaygroundSegmentOptionSelected { + background: #FFFFFF; + color: #112247; + border: none; + font-family: "native:MainBold"; + font-size: 2.8mm; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 0; + text-decoration: none; +} + +PlaygroundSegmentOptionSelectedDark { + background: #163575; + color: #F5F8FF; + border: none; + font-family: "native:MainBold"; + font-size: 2.8mm; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 0; + text-decoration: none; +} + +/* ----- Status pill ----- */ + +PlaygroundStatusPill { + background: #F0F2F7; + color: #112247; + font-family: "native:MainLight"; + font-size: 2.6mm; + padding: 0.8mm 2mm 0.8mm 2mm; + margin: 0 1mm 0 1mm; + text-decoration: none; +} + +PlaygroundStatusPillDark { + background: #0B2055; + color: #F5F8FF; + font-family: "native:MainLight"; + font-size: 2.6mm; + padding: 0.8mm 2mm 0.8mm 2mm; + margin: 0 1mm 0 1mm; + text-decoration: none; +} + +PlaygroundStatusPillError { + background: #FBE6E6; + color: #D93636; + font-family: "native:MainBold"; + font-size: 2.6mm; + padding: 0.8mm 2mm 0.8mm 2mm; + margin: 0 1mm 0 1mm; + text-decoration: none; +} + +PlaygroundStatusPillErrorDark { + background: #3A1515; + color: #FF6B6B; + font-family: "native:MainBold"; + font-size: 2.6mm; + padding: 0.8mm 2mm 0.8mm 2mm; + margin: 0 1mm 0 1mm; + text-decoration: none; +} + +PlaygroundStatusDot { + background: #36A853; + padding: 0; + margin: 0 1mm 0 0; +} + +PlaygroundStatusDotDark { + background: #5FCF7C; + padding: 0; + margin: 0 1mm 0 0; +} + +PlaygroundStatusDotError { + background: #D93636; + padding: 0; + margin: 0 1mm 0 0; +} + +PlaygroundStatusDotErrorDark { + background: #FF6B6B; + padding: 0; + margin: 0 1mm 0 0; +} + +/* ----- Top bar buttons ----- */ + +PlaygroundShareButton { + background: transparent; + color: #112247; + border: 1px solid #E1E4EA; + font-family: "native:MainLight"; + font-size: 2.8mm; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 0 1mm 0 1mm; + text-decoration: none; +} + +PlaygroundShareButtonDark { + background: transparent; + color: #F5F8FF; + border: 1px solid #253B73; + font-family: "native:MainLight"; + font-size: 2.8mm; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 0 1mm 0 1mm; + text-decoration: none; +} + +PlaygroundDownloadButton { + background: #2F6BFF; + color: #FFFFFF; + border: none; + font-family: "native:MainBold"; + font-size: 2.8mm; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 0 0 0 1mm; + text-decoration: none; +} + +PlaygroundDownloadButtonDark { + background: #4D86FF; + color: #0A0F1E; + border: none; + font-family: "native:MainBold"; + font-size: 2.8mm; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 0 0 0 1mm; + text-decoration: none; +} + +/* ----- Activity bar (stays navy in both themes) ----- */ + +PlaygroundActivityBar { + background: #0E1A34; + padding: 2mm 0 2mm 0; + border-right: 1px solid #1A2645; +} + +PlaygroundActivityBarDark { + background: #0E1A34; + padding: 2mm 0 2mm 0; + border-right: 1px solid #1A2645; +} + +PlaygroundActivityButton { + background: transparent; + color: #A2ADC9; + border: none; + font-family: "native:MainLight"; + font-size: 2.6mm; + padding: 2mm; + margin: 0.5mm 1mm 0.5mm 1mm; + text-decoration: none; +} + +PlaygroundActivityButtonDark { + background: transparent; + color: #A2ADC9; + border: none; + font-family: "native:MainLight"; + font-size: 2.6mm; + padding: 2mm; + margin: 0.5mm 1mm 0.5mm 1mm; + text-decoration: none; +} + +PlaygroundActivityButtonActive { + background: #1F2E52; + color: #FFFFFF; + border-left: 0.8mm solid #4D86FF; + font-family: "native:MainBold"; + font-size: 2.6mm; + padding: 2mm; + margin: 0.5mm 1mm 0.5mm 0; + text-decoration: none; +} + +PlaygroundActivityButtonActiveDark { + background: #1F2E52; + color: #FFFFFF; + border-left: 0.8mm solid #4D86FF; + font-family: "native:MainBold"; + font-size: 2.6mm; + padding: 2mm; + margin: 0.5mm 1mm 0.5mm 0; + text-decoration: none; +} + +/* ----- Side panel (Samples / History / Inspector dock) ----- */ + +PlaygroundSidePanel { + background: #FFFFFF; + border-right: 1px solid #E1E4EA; + padding: 0; +} + +PlaygroundSidePanelDark { + background: #102B66; + border-right: 1px solid #253B73; + padding: 0; +} + +PlaygroundSidePanelHeader { + background: transparent; + color: #7F8AA3; + font-family: "native:MainBold"; + font-size: 2.6mm; + padding: 2mm 3mm 1.5mm 3mm; + margin: 0; +} + +PlaygroundSidePanelHeaderDark { + background: transparent; + color: #A8B8DA; + font-family: "native:MainBold"; + font-size: 2.6mm; + padding: 2mm 3mm 1.5mm 3mm; + margin: 0; +} + +PlaygroundSidePanelClose { + background: transparent; + color: #7F8AA3; + border: none; + padding: 0.5mm; + margin: 1.5mm 3mm 1.5mm 0; +} + +PlaygroundSidePanelCloseDark { + background: transparent; + color: #A8B8DA; + border: none; + padding: 0.5mm; + margin: 1.5mm 3mm 1.5mm 0; +} + +PlaygroundSearchField { background: #FFFFFF; - margin: 1mm; + color: #112247; + border: 1px solid #E1E4EA; + font-family: "native:MainLight"; + font-size: 2.8mm; + padding: 1mm 2mm 1mm 2mm; + margin: 0 2mm 1.5mm 2mm; + text-decoration: none; +} + +PlaygroundSearchFieldDark { + background: #0E2A61; + color: #F5F8FF; + border: 1px solid #253B73; + font-family: "native:MainLight"; + font-size: 2.8mm; + padding: 1mm 2mm 1mm 2mm; + margin: 0 2mm 1.5mm 2mm; + text-decoration: none; +} + +PlaygroundSampleItem { + background: transparent; + color: #112247; + border: none; + font-family: "native:MainLight"; + font-size: 3mm; + padding: 1.5mm 2.5mm 1.5mm 2.5mm; + margin: 0 1.5mm 0 1.5mm; + text-decoration: none; +} + +PlaygroundSampleItemDark { + background: transparent; + color: #F5F8FF; + border: none; + font-family: "native:MainLight"; + font-size: 3mm; + padding: 1.5mm 2.5mm 1.5mm 2.5mm; + margin: 0 1.5mm 0 1.5mm; + text-decoration: none; +} + +PlaygroundSampleItemSelected { + background: #E6EDFF; + color: #2F6BFF; + border: none; + font-family: "native:MainBold"; + font-size: 3mm; + padding: 1.5mm 2.5mm 1.5mm 2.5mm; + margin: 0 1.5mm 0 1.5mm; + text-decoration: none; +} + +PlaygroundSampleItemSelectedDark { + background: #1F3A7A; + color: #4D86FF; + border: none; + font-family: "native:MainBold"; + font-size: 3mm; + padding: 1.5mm 2.5mm 1.5mm 2.5mm; + margin: 0 1.5mm 0 1.5mm; + text-decoration: none; +} + +PlaygroundHistoryItem { + background: transparent; + border: none; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 0 1.5mm 0 1.5mm; +} + +PlaygroundHistoryItemDark { + background: transparent; + border: none; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 0 1.5mm 0 1.5mm; +} + +PlaygroundHistoryLine1 { + background: transparent; + color: #112247; + font-family: "native:MainLight"; + font-size: 3mm; + margin: 0; + padding: 0; + text-decoration: none; +} + +PlaygroundHistoryLine1Dark { + background: transparent; + color: #F5F8FF; + font-family: "native:MainLight"; + font-size: 3mm; + margin: 0; + padding: 0; + text-decoration: none; +} + +PlaygroundHistoryLine2 { + background: transparent; + color: #7F8AA3; + font-family: "native:MainLight"; + font-size: 2.4mm; + margin: 0; + padding: 0; + text-decoration: none; +} + +PlaygroundHistoryLine2Dark { + background: transparent; + color: #A8B8DA; + font-family: "native:MainLight"; + font-size: 2.4mm; + margin: 0; + padding: 0; + text-decoration: none; +} + +/* ----- Editor column ----- */ + +PlaygroundPanel { + background: #FFFFFF; + margin: 0; + padding: 0; +} + +PlaygroundPanelDark { + background: #102B66; + margin: 0; + padding: 0; +} + +/* ----- Preview column ----- */ + +PlaygroundPreviewToolbar { + background: #FFFFFF; + border-bottom: 1px solid #E1E4EA; + padding: 1mm 2mm 1mm 2mm; +} + +PlaygroundPreviewToolbarDark { + background: #102B66; + border-bottom: 1px solid #253B73; + padding: 1mm 2mm 1mm 2mm; +} + +PlaygroundDimensions { + background: transparent; + color: #AAB2C5; + font-family: "native:MainLight"; + font-size: 2.6mm; + padding: 0 2mm 0 2mm; +} + +PlaygroundDimensionsDark { + background: transparent; + color: #6E80A6; + font-family: "native:MainLight"; + font-size: 2.6mm; + padding: 0 2mm 0 2mm; +} + +PlaygroundDeviceStage { + background: #F0F2F7; + padding: 3mm; + margin: 0; +} + +PlaygroundDeviceStageDark { + background: #0B2055; + padding: 3mm; + margin: 0; +} + +PlaygroundNoSkinStage { + background: #FFFFFF; + padding: 0; + margin: 0; +} + +PlaygroundNoSkinStageDark { + background: #102B66; + padding: 0; + margin: 0; +} + +PlaygroundDeviceBezel { + background: #1A1A1C; + border: 1px solid #2A2A2D; + padding: 3mm; + margin: 0; +} + +PlaygroundDeviceBezelDark { + background: #1A1A1C; + border: 1px solid #2A2A2D; + padding: 3mm; + margin: 0; +} + +PlaygroundDeviceScreen { + background: #FFFFFF; + padding: 0; + margin: 0; +} + +PlaygroundDeviceScreenDark { + background: #102B66; + padding: 0; + margin: 0; +} + +PlaygroundStaleChip { + background: #FBE6E6; + color: #D93636; + font-family: "native:MainBold"; + font-size: 2.6mm; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 2mm; + text-decoration: none; +} + +PlaygroundStaleChipDark { + background: #3A1515; + color: #FF6B6B; + font-family: "native:MainBold"; + font-size: 2.6mm; + padding: 1mm 2.5mm 1mm 2.5mm; + margin: 2mm; + text-decoration: none; +} + +/* ----- Embedded forms rendered inside the preview ----- */ + +PlaygroundPreview { + background: #FFFFFF; +} + +PlaygroundPreviewDark { + background: #112F70; +} + +PlaygroundEmbeddedForm { + background: #F7F8FB; +} + +PlaygroundEmbeddedFormDark { + background: #112247; +} + +PlaygroundEmbeddedTitleArea { + background: #F3F4F7; +} + +PlaygroundEmbeddedTitleAreaDark { + background: #163575; +} + +/* ----- Inspector ----- */ + +PlaygroundInspectorRoot { + background: #FFFFFF; + padding: 1mm; +} + +PlaygroundInspectorRootDark { + background: #102B66; + padding: 1mm; +} + +PlaygroundInspectorTree { + background: #FFFFFF; + color: #112247; + margin: 0; + padding: 0; +} + +PlaygroundInspectorTreeDark { + background: #102B66; + color: #F5F8FF; + margin: 0; + padding: 0; +} + +PlaygroundInspectorProps { + background: #FFFFFF; + padding: 1mm; + margin: 0; +} + +PlaygroundInspectorPropsDark { + background: #102B66; + padding: 1mm; + margin: 0; +} + +PlaygroundPropRow { + background: transparent; + margin: 0 0 1mm 0; + padding: 0; +} + +PlaygroundPropName { + color: #7F8AA3; + font-family: "native:MainBold"; + font-size: 2.6mm; + padding: 0.5mm 0mm 0.5mm 1.5mm; + margin: 0; +} + +PlaygroundPropNameDark { + color: #A8B8DA; + font-family: "native:MainBold"; + font-size: 2.6mm; + padding: 0.5mm 0mm 0.5mm 1.5mm; + margin: 0; +} + +PlaygroundPropValue { + color: #112247; + background: #FFFFFF; + border: 1px solid #E1E4EA; + font-family: "native:MainLight"; + font-size: 2.8mm; + padding: 0.5mm 1mm 0.5mm 1mm; + margin: 0.5mm 0.5mm 0.5mm 0; +} + +PlaygroundPropValueDark { + color: #F5F8FF; + background: #0E2A61; + border: 1px solid #253B73; + font-family: "native:MainLight"; + font-size: 2.8mm; + padding: 0.5mm 1mm 0.5mm 1mm; + margin: 0.5mm 0.5mm 0.5mm 0; +} + +PlaygroundPropSmall { + color: #112247; + background: #FFFFFF; + border: 1px solid #E1E4EA; + font-family: "native:MainLight"; + font-size: 2.6mm; + padding: 0.5mm; + margin: 0 0.5mm 0 0; +} + +PlaygroundPropSmallDark { + color: #F5F8FF; + background: #0E2A61; + border: 1px solid #253B73; + font-family: "native:MainLight"; + font-size: 2.6mm; + padding: 0.5mm; + margin: 0 0.5mm 0 0; +} + +PlaygroundPropUnit { + color: #112247; + background: #FFFFFF; + border: 1px solid #E1E4EA; +} + +PlaygroundPropUnitDark { + color: #F5F8FF; + background: #0E2A61; + border: 1px solid #253B73; +} + +PlaygroundPropEmpty { + color: #7F8AA3; + background: transparent; + font-family: "native:MainLight"; + font-size: 3mm; + padding: 1mm; +} + +PlaygroundPropEmptyDark { + color: #A8B8DA; + background: transparent; + font-family: "native:MainLight"; + font-size: 3mm; + padding: 1mm; +} + +PlaygroundPropGroup { + background: transparent; + padding: 0.5mm 0 0.5mm 0; + margin: 0; +} + +PlaygroundColorPreview { + background: #FFFFFF; + border: 1px solid #E1E4EA; + padding: 0.5mm 1mm 0.5mm 1mm; +} + +PlaygroundColorPreviewDark { + background: #0E2A61; + border: 1px solid #253B73; + padding: 0.5mm 1mm 0.5mm 1mm; +} + +PlaygroundInspectorTreeNode { + color: #112247; + background: #FFFFFF; + border: none; + text-decoration: none; + font-family: "native:MainLight"; + font-size: 2.8mm; + margin: 0; + padding: 0; +} + +PlaygroundInspectorTreeNodeDark { + color: #F5F8FF; + background: #102B66; + border: none; + text-decoration: none; + font-family: "native:MainLight"; + font-size: 2.8mm; + margin: 0; + padding: 0; +} + +/* ----- Mobile top tabs + bottom nav ----- */ + +PlaygroundTopTabs { + background: #FFFFFF; + border-bottom: 1px solid #E1E4EA; +} + +PlaygroundTopTabsDark { + background: #102B66; + border-bottom: 1px solid #253B73; +} + +PlaygroundTopTab { + background: transparent; + color: #7F8AA3; + border: none; + font-family: "native:MainLight"; + font-size: 2.8mm; + padding: 1.5mm 2mm 1.5mm 2mm; + text-decoration: none; +} + +PlaygroundTopTabDark { + background: transparent; + color: #A8B8DA; + border: none; + font-family: "native:MainLight"; + font-size: 2.8mm; + padding: 1.5mm 2mm 1.5mm 2mm; + text-decoration: none; +} + +PlaygroundTopTabSelected { + background: transparent; + color: #2F6BFF; + border-bottom: 0.6mm solid #2F6BFF; + font-family: "native:MainBold"; + font-size: 2.8mm; + padding: 1.5mm 2mm 1.5mm 2mm; + text-decoration: none; +} + +PlaygroundTopTabSelectedDark { + background: transparent; + color: #4D86FF; + border-bottom: 0.6mm solid #4D86FF; + font-family: "native:MainBold"; + font-size: 2.8mm; + padding: 1.5mm 2mm 1.5mm 2mm; + text-decoration: none; +} + +PlaygroundBottomNav { + background: #FFFFFF; + border: none; + border-bottom: 1px solid #E1E4EA; + padding: 1mm 0 1mm 0; +} + +PlaygroundBottomNavDark { + background: #102B66; + border: none; + border-bottom: 1px solid #253B73; + padding: 1mm 0 1mm 0; +} + +PlaygroundBottomNavItem { + background: transparent; + color: #7F8AA3; + border: none; + font-family: "native:MainLight"; + font-size: 2.4mm; padding: 1mm; + text-decoration: none; } -PlaygroundPanelDark { - background: #102B66; - margin: 1mm; +PlaygroundBottomNavItemDark { + background: transparent; + color: #A8B8DA; + border: none; + font-family: "native:MainLight"; + font-size: 2.4mm; padding: 1mm; + text-decoration: none; } -PlaygroundPreview { - background: #FFFFFF; -} - -PlaygroundPreviewDark { - background: #112F70; +PlaygroundBottomNavItemActive { + background: transparent; + color: #2F6BFF; + border: none; + font-family: "native:MainBold"; + font-size: 2.4mm; + padding: 1mm; + text-decoration: none; } -/** Style for Dialog body */ -DialogBody { - font-family: "native:MainLight"; - font-size: 2.8mm; +PlaygroundBottomNavItemActiveDark { + background: transparent; + color: #4D86FF; + border: none; + font-family: "native:MainBold"; + font-size: 2.4mm; + padding: 1mm; + text-decoration: none; } -/** Style for Dialog title bar text */ -DialogTitle { - font-family: "native:MainLight"; - font-size: 4.5mm; -} +/* ----- Existing side menu (kept around for overlay fallback) ----- */ -/** Style for the side menu */ SideNavigationPanel { background: #FFFFFF; padding: 2mm 1mm 1mm 1mm; } SideNavigationPanelDark { - background: #071B4D; + background: #0A0F1E; padding: 2mm 1mm 1mm 1mm; } @media platform-ios { - /** iOS Only styles for side menu. */ SideNavigationPanel { - /** Extra top padding to deal with notch on iPhoneX */ padding: 6mm 1mm 1mm 1mm; } SideNavigationPanelDark { @@ -108,7 +962,6 @@ SideNavigationPanelDark { } } -/** Style for commands in side menu. */ SideCommand { padding: 1mm; border: none; @@ -117,7 +970,7 @@ SideCommand { background: #FFFFFF; font-family: "native:MainLight"; font-size: 4mm; - border-bottom: 2px solid #D9DEE8; + border-bottom: 2px solid #E1E4EA; } StatusBarSideMenu { @@ -126,7 +979,7 @@ StatusBarSideMenu { } StatusBarSideMenuDark { - background: #071B4D; + background: #0A0F1E; padding: 0mm; } @@ -138,12 +991,12 @@ PlaygroundSideCommand { background: #FFFFFF; font-family: "native:MainLight"; font-size: 4mm; - border-bottom: 2px solid #D9DEE8; + border-bottom: 2px solid #E1E4EA; } PlaygroundSideCommandDark { color: #F5F8FF; - background: #071B4D; + background: #0A0F1E; border-bottom: 2px solid #102B66; } @@ -151,13 +1004,13 @@ PlaygroundMenuSection { padding: 2mm 1mm 0.5mm 1mm; margin: 1mm 0 0 0; border: none; - border-bottom: 1px solid #D9DEE8; + border-bottom: 1px solid #E1E4EA; background: #FFFFFF; } PlaygroundMenuSectionDark { - border-bottom: 1px solid #4C6EA8; - background: #071B4D; + border-bottom: 1px solid #253B73; + background: #0A0F1E; } PlaygroundMenuSectionTitle { @@ -181,7 +1034,7 @@ PlaygroundMenuEmpty { PlaygroundMenuEmptyDark { color: #A8B8DA; - background: #071B4D; + background: #0A0F1E; } PlaygroundMenuContainer { @@ -189,194 +1042,41 @@ PlaygroundMenuContainer { } PlaygroundMenuContainerDark { - background: #071B4D; -} - -@media (prefers-color-scheme: dark) { - SideNavigationPanel { - background: #071B4D; - } - SideNavigationPanelDark { - background: #071B4D; - } - - SideCommand { - color: #F5F8FF; - background: #071B4D; - border-bottom: 2px solid #102B66; - } - - StatusBarSideMenu { - background: #071B4D; - } - StatusBarSideMenuDark { - background: #071B4D; - } - - PlaygroundSideCommand { - color: #F5F8FF; - background: #071B4D; - border-bottom: 2px solid #102B66; - } - PlaygroundSideCommandDark { - color: #F5F8FF; - background: #071B4D; - border-bottom: 2px solid #102B66; - } - - PlaygroundSideCommandLine1 { - color: #F5F8FF; - } - - PlaygroundSideCommandLine2 { - color: #A8B8DA; - } - - PlaygroundMenuSection { - border-bottom: 1px solid #4C6EA8; - background: #071B4D; - } - PlaygroundMenuSectionDark { - border-bottom: 1px solid #4C6EA8; - background: #071B4D; - } - - PlaygroundMenuSectionTitle { - color: #A8B8DA; - } - PlaygroundMenuSectionTitleDark { - color: #A8B8DA; - } - - PlaygroundMenuEmpty { - color: #A8B8DA; - background: #071B4D; - } - PlaygroundMenuEmptyDark { - color: #A8B8DA; - background: #071B4D; - } - - PlaygroundMenuContainer { - background: #071B4D; - } - PlaygroundMenuContainerDark { - background: #071B4D; - } -} - -PlaygroundEmbeddedForm { - background: #F7F8FB; -} - -PlaygroundEmbeddedFormDark { - background: #112247; -} - -PlaygroundEmbeddedTitleArea { - background: #F3F4F7; -} - -PlaygroundEmbeddedTitleAreaDark { - background: #163575; -} - -PlaygroundInspectorRoot { - background: #FFFFFF; - padding: 1mm; -} - -PlaygroundInspectorRootDark { - background: #102B66; - padding: 1mm; + background: #0A0F1E; } -PlaygroundInspectorTree { - background: #FFFFFF; +PlaygroundSideCommandLine1 { color: #112247; - margin: 0; - padding: 0; -} - -PlaygroundInspectorTreeDark { - background: #102B66; - color: #F5F8FF; - margin: 0; - padding: 0; -} - -PlaygroundPropRow { background: transparent; - margin: 0 0 1mm 0; - padding: 0; -} - -PlaygroundPropUnit { - color: #112247; - background: #FFFFFF; - border: 1px solid #D9DEE8; + font-family: "native:MainLight"; + font-size: 4mm; } -PlaygroundPropUnitDark { +PlaygroundSideCommandLine1Dark { color: #F5F8FF; - background: #0E2A61; - border: 1px solid #4C6EA8; + background: transparent; + font-family: "native:MainLight"; + font-size: 4mm; } -PlaygroundPropEmpty { +PlaygroundSideCommandLine2 { color: #7F8AA3; background: transparent; font-family: "native:MainLight"; - font-size: 3mm; - padding: 1mm; + font-size: 2.6mm; } -PlaygroundPropEmptyDark { +PlaygroundSideCommandLine2Dark { color: #A8B8DA; background: transparent; font-family: "native:MainLight"; - font-size: 3mm; - padding: 1mm; -} - -PlaygroundColorPreview { - background: #FFFFFF; - border: 1px solid #D9DEE8; - padding: 0.5mm 1mm 0.5mm 1mm; -} - -PlaygroundColorPreviewDark { - background: #0E2A61; - border: 1px solid #4C6EA8; - padding: 0.5mm 1mm 0.5mm 1mm; -} - -PlaygroundInspectorTreeNode { - color: #112247; - background: #FFFFFF; - border: none; - text-decoration: none; - font-family: "native:MainLight"; - font-size: 2.8mm; - margin: 0; - padding: 0; -} - -PlaygroundInspectorTreeNodeDark { - color: #F5F8FF; - background: #102B66; - border: none; - text-decoration: none; - font-family: "native:MainLight"; - font-size: 2.8mm; - margin: 0; - padding: 0; + font-size: 2.6mm; } Tab { background: #F3F4F7; color: #112247; - border: 1px solid #D9DEE8; + border: 1px solid #E1E4EA; font-family: "native:MainLight"; font-size: 3mm; text-align: center; @@ -393,7 +1093,7 @@ PlaygroundEditorTabsDark { TabDark { background: #102B66; color: #F5F8FF; - border: 1px solid #4C6EA8; + border: 1px solid #253B73; font-family: "native:MainLight"; font-size: 3mm; text-align: center; @@ -418,104 +1118,3 @@ TabsContainer { TabsContainerDark { background: #112247; } -PlaygroundSideCommandLine1 { - color: #112247; - background: transparent; - font-family: "native:MainLight"; - font-size: 4mm; -} - -PlaygroundSideCommandLine1Dark { - color: #F5F8FF; - background: transparent; - font-family: "native:MainLight"; - font-size: 4mm; -} - -PlaygroundSideCommandLine2 { - color: #7F8AA3; - background: transparent; - font-family: "native:MainLight"; - font-size: 2.6mm; -} - -PlaygroundSideCommandLine2Dark { - color: #A8B8DA; - background: transparent; - font-family: "native:MainLight"; - font-size: 2.6mm; -} - -PlaygroundInspectorProps { - background: #FFFFFF; - padding: 1mm; - margin: 0; -} - -PlaygroundInspectorPropsDark { - background: #102B66; - padding: 1mm; - margin: 0; -} - -PlaygroundPropName { - color: #7F8AA3; - font-family: "native:MainBold"; - font-size: 2.6mm; - padding: 0.5mm 0mm 0.5mm 1.5mm; - margin: 0; -} - -PlaygroundPropNameDark { - color: #A8B8DA; - font-family: "native:MainBold"; - font-size: 2.6mm; - padding: 0.5mm 0mm 0.5mm 1.5mm; - margin: 0; -} - -PlaygroundPropValue { - color: #112247; - background: #FFFFFF; - border: 1px solid #D9DEE8; - font-family: "native:MainLight"; - font-size: 2.8mm; - padding: 0.5mm 1mm 0.5mm 1mm; - margin: 0.5mm 0.5mm 0.5mm 0; -} - -PlaygroundPropValueDark { - color: #F5F8FF; - background: #0E2A61; - border: 1px solid #4C6EA8; - font-family: "native:MainLight"; - font-size: 2.8mm; - padding: 0.5mm 1mm 0.5mm 1mm; - margin: 0.5mm 0.5mm 0.5mm 0; -} - -PlaygroundPropSmall { - color: #112247; - background: #FFFFFF; - border: 1px solid #D9DEE8; - font-family: "native:MainLight"; - font-size: 2.6mm; - padding: 0.5mm; - margin: 0 0.5mm 0 0; -} - -PlaygroundPropSmallDark { - color: #F5F8FF; - background: #0E2A61; - border: 1px solid #4C6EA8; - font-family: "native:MainLight"; - font-size: 2.6mm; - padding: 0.5mm; - margin: 0 0.5mm 0 0; -} - -PlaygroundPropGroup { - background: transparent; - padding: 0.5mm 0 0.5mm 0; - margin: 0; -} diff --git a/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java b/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java index e6bb591002..663dc0a08a 100644 --- a/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java +++ b/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java @@ -1,7 +1,5 @@ package com.codenameone.playground; -import com.codename1.components.MultiButton; -import com.codename1.components.SplitPane; import com.codename1.io.Log; import com.codename1.io.NetworkEvent; import com.codename1.io.Util; @@ -13,12 +11,14 @@ import com.codename1.ui.Component; import com.codename1.ui.Container; import com.codename1.ui.Display; +import com.codename1.ui.FontImage; import com.codename1.ui.Form; import com.codename1.ui.Label; -import com.codename1.ui.Tabs; import com.codename1.ui.Toolbar; import com.codename1.ui.layouts.BorderLayout; import com.codename1.ui.layouts.BoxLayout; +import com.codename1.ui.layouts.FlowLayout; +import com.codename1.ui.layouts.GridLayout; import com.codename1.ui.plaf.UIManager; import com.codename1.ui.util.Resources; import com.codename1.ui.util.UITimer; @@ -36,7 +36,15 @@ public class CN1Playground extends Lifecycle { private static final String THEME_ROLE = "playgroundThemeRole"; private static final String ROLE_EMBEDDED_FORM = "embeddedForm"; private static final String ROLE_EMBEDDED_TITLE_AREA = "embeddedTitleArea"; - private static final String SHARE_BUTTON_LABEL = "Copy Shareable Playground URL"; + + private static final String PANEL_SAMPLES = "samples"; + private static final String PANEL_INSPECTOR = "inspector"; + private static final String PANEL_HISTORY = "history"; + + private static final String MOBILE_TAB_CODE = "code"; + private static final String MOBILE_TAB_CSS = "css"; + private static final String MOBILE_TAB_PREVIEW = "preview"; + private final PlaygroundRunner runner = new PlaygroundRunner(); private final PlaygroundProjectExporter projectExporter = new PlaygroundProjectExporter(); @@ -44,27 +52,44 @@ public class CN1Playground extends Lifecycle { private PlaygroundBrowserEditor editor; private PlaygroundBrowserEditor cssEditor; private PlaygroundInspector inspector; - private Container previewRoot; - private Container historyMenu; - private final List sideMenuComponents = new ArrayList<>(); + private PlaygroundTopBar topBar; + private PlaygroundActivityBar activityBar; + private PlaygroundSamplesPanel samplesPanel; + private PlaygroundHistoryPanel historyPanel; + private Container inspectorWrapper; + private PlaygroundPreviewColumn previewColumn; + private PlaygroundSegmented mobileTopTabs; + private Container bottomNav; + + private Container bodyContainer; + private Container editorHost; + private Container previewContainer; + private Container sidePanelSlot; + private Resources theme; private boolean websiteDarkMode = DEFAULT_DARK_MODE; private String currentScript; private String currentCss; + private String currentMode = PlaygroundTopBar.MODE_CODE; + private String currentActivity = PlaygroundActivityBar.NONE; + private String currentMobileTab = MOBILE_TAB_CODE; private List currentMessages = new ArrayList<>(); private List currentCssMessages = new ArrayList<>(); private int editSequence; private int autoRunSequence; - private Tabs editorTabs; + private int historySequence; private WebsiteThemeNative websiteThemeNative; private boolean websiteThemeInitialized; + private String lastBreakpoint; + @Override public void runApp() { CN.setProperty("platformHint.javascript.beforeUnloadMessage", null); theme = Resources.getGlobalResources(); currentScript = resolveInitialScript(); currentCss = resolveInitialCss(); + currentMode = PlaygroundStateStore.loadMode(PlaygroundTopBar.MODE_CODE); appForm = new Form("Playground", new BorderLayout()); appForm.setUIID("PlaygroundForm"); @@ -72,41 +97,84 @@ public void runApp() { Toolbar toolbar = appForm.getToolbar(); toolbar.setUIID("PlaygroundToolbar"); toolbar.setTitleCentered(false); - if (toolbar.getTitleComponent() != null) { - toolbar.getTitleComponent().setUIID("PlaygroundTitle"); - } - - toolbar.addMaterialCommandToRightBar("Download", com.codename1.ui.FontImage.MATERIAL_DOWNLOAD, e -> projectExporter.export(currentScript, currentCss)); + toolbar.hideToolbar(); editor = new PlaygroundBrowserEditor(PlaygroundBrowserEditor.Mode.JAVA, currentScript, websiteDarkMode, this::handleSourceChanged); cssEditor = new PlaygroundBrowserEditor(PlaygroundBrowserEditor.Mode.CSS, currentCss, websiteDarkMode, this::handleCssChanged); inspector = new PlaygroundInspector(websiteDarkMode, (component, property, value) -> handlePropertyChanged(component)); - previewRoot = createPreviewRoot(); - historyMenu = new Container(BoxLayout.y()); - historyMenu.setUIID("PlaygroundMenuContainer"); + topBar = new PlaygroundTopBar(currentMode, websiteDarkMode, new PlaygroundTopBar.Actions() { + @Override + public void onModeChanged(String key) { + switchMode(key); + } + + @Override + public void onShare() { + copyCurrentSourceUrl(); + } + + @Override + public void onDownload() { + projectExporter.export(currentScript, currentCss); + } + }); + + PlaygroundActivityBar.Item[] activityItems = new PlaygroundActivityBar.Item[]{ + new PlaygroundActivityBar.Item(PANEL_SAMPLES, "Samples", FontImage.MATERIAL_DASHBOARD), + new PlaygroundActivityBar.Item(PANEL_INSPECTOR, "Inspector", FontImage.MATERIAL_ACCOUNT_TREE), + new PlaygroundActivityBar.Item(PANEL_HISTORY, "History", FontImage.MATERIAL_HISTORY) + }; + currentActivity = PlaygroundStateStore.loadPanel(PlaygroundActivityBar.NONE); + activityBar = new PlaygroundActivityBar(activityItems, currentActivity, websiteDarkMode, this::handleActivitySelected); + + samplesPanel = new PlaygroundSamplesPanel(websiteDarkMode, () -> handleActivitySelected(PlaygroundActivityBar.NONE), this::handleSampleSelected); + historyPanel = new PlaygroundHistoryPanel(websiteDarkMode, () -> handleActivitySelected(PlaygroundActivityBar.NONE), this::handleHistorySelected); + historyPanel.setEntries(PlaygroundStateStore.loadHistory()); + + inspectorWrapper = buildInspectorWrapper(); + + String device = PlaygroundStateStore.loadDevice(PlaygroundPreviewColumn.DEVICE_IPHONE); + String orientation = PlaygroundStateStore.loadOrientation(PlaygroundPreviewColumn.ORIENTATION_PORTRAIT); + previewColumn = new PlaygroundPreviewColumn(device, orientation, websiteDarkMode, () -> { + PlaygroundStateStore.saveDevice(previewColumn.getDevice()); + PlaygroundStateStore.saveOrientation(previewColumn.getOrientation()); + }); - Container editorPanel = wrapPanel(editor.getComponent()); - Container cssEditorPanel = wrapPanel(cssEditor.getComponent()); - Container inspectorPanel = wrapPanel(inspector.getComponent()); + mobileTopTabs = buildMobileTopTabs(); + bottomNav = buildBottomNav(); - editorTabs = new Tabs(); - editorTabs.setUIID("PlaygroundEditorTabs"); - editorTabs.addTab("Code", editorPanel); - editorTabs.addTab("CSS", cssEditorPanel); - editorTabs.addTab("Inspector", inspectorPanel); - applyTabsTheme(websiteDarkMode); + editorHost = new Container(new BorderLayout()); + editorHost.setUIID(websiteDarkMode ? "PlaygroundPanelDark" : "PlaygroundPanel"); - Container previewPanel = wrapPanel(previewRoot); + previewContainer = new Container(new BorderLayout()); + previewContainer.setUIID(websiteDarkMode ? "PlaygroundPanelDark" : "PlaygroundPanel"); + previewContainer.add(BorderLayout.CENTER, previewColumn); - appForm.add(BorderLayout.CENTER, createMainContent(editorTabs, previewPanel)); + sidePanelSlot = new Container(new BorderLayout()); + sidePanelSlot.getAllStyles().setBgTransparency(0); - installSideMenu(toolbar); - applyWebsiteTheme(appForm, websiteDarkMode); + bodyContainer = new Container(new BorderLayout()); + bodyContainer.setUIID(websiteDarkMode ? "PlaygroundBodyDark" : "PlaygroundBody"); + + Container topBarStack = new Container(BoxLayout.y()); + topBarStack.getAllStyles().setBgTransparency(0); + topBarStack.add(topBar); + appForm.add(BorderLayout.NORTH, topBarStack); + appForm.add(BorderLayout.CENTER, bodyContainer); + + applyLayoutForCurrentSize(); + applyMode(currentMode, false); + applyActivity(currentActivity); + + applyWebsiteTheme(appForm, websiteDarkMode); runScript(appForm); initWebsiteThemeSync(appForm); + appForm.addOrientationListener(e -> applyLayoutForCurrentSize()); + appForm.addSizeChangedListener(e -> applyLayoutForCurrentSize()); + appForm.show(); notifyWebsiteUiReady(); } @@ -116,11 +184,234 @@ protected void handleNetworkError(NetworkEvent err) { Log.p("Networking error: " + err); } - private Container wrapPanel(Component content) { - Container panel = new Container(new BorderLayout()); - panel.setUIID("PlaygroundPanel"); - panel.add(BorderLayout.CENTER, content); - return panel; + private Container buildInspectorWrapper() { + Container wrapper = new Container(new BorderLayout()); + wrapper.setUIID(websiteDarkMode ? "PlaygroundSidePanelDark" : "PlaygroundSidePanel"); + + Label headerLabel = new Label("INSPECTOR"); + headerLabel.setUIID(websiteDarkMode ? "PlaygroundSidePanelHeaderDark" : "PlaygroundSidePanelHeader"); + + Button closeButton = new Button(); + closeButton.setUIID(websiteDarkMode ? "PlaygroundSidePanelCloseDark" : "PlaygroundSidePanelClose"); + FontImage.setMaterialIcon(closeButton, FontImage.MATERIAL_CLOSE, 3f); + closeButton.addActionListener(e -> handleActivitySelected(PlaygroundActivityBar.NONE)); + + Container header = new Container(new BorderLayout()); + header.getAllStyles().setBgTransparency(0); + header.add(BorderLayout.CENTER, headerLabel); + header.add(BorderLayout.EAST, closeButton); + + wrapper.add(BorderLayout.NORTH, header); + wrapper.add(BorderLayout.CENTER, inspector.getComponent()); + wrapper.setPreferredW(Display.getInstance().convertToPixels(60f)); + return wrapper; + } + + private PlaygroundSegmented buildMobileTopTabs() { + PlaygroundSegmented.Option[] opts = new PlaygroundSegmented.Option[]{ + new PlaygroundSegmented.Option(MOBILE_TAB_CODE, "Code", FontImage.MATERIAL_CODE), + new PlaygroundSegmented.Option(MOBILE_TAB_CSS, "CSS", FontImage.MATERIAL_PALETTE), + new PlaygroundSegmented.Option(MOBILE_TAB_PREVIEW, "Preview", FontImage.MATERIAL_VISIBILITY) + }; + return new PlaygroundSegmented(opts, MOBILE_TAB_CODE, websiteDarkMode, key -> { + currentMobileTab = key; + refreshMobileTabContent(); + }); + } + + private Container buildBottomNav() { + Container nav = new Container(new GridLayout(1, 3)); + nav.setUIID(websiteDarkMode ? "PlaygroundBottomNavDark" : "PlaygroundBottomNav"); + nav.add(createBottomNavButton(PANEL_SAMPLES, "Samples", FontImage.MATERIAL_DASHBOARD)); + nav.add(createBottomNavButton(PANEL_INSPECTOR, "Inspector", FontImage.MATERIAL_ACCOUNT_TREE)); + nav.add(createBottomNavButton(PANEL_HISTORY, "History", FontImage.MATERIAL_HISTORY)); + return nav; + } + + private Button createBottomNavButton(String key, String label, char icon) { + Button btn = new Button(label); + boolean active = key.equals(currentActivity); + String uiid; + if (active) { + uiid = websiteDarkMode ? "PlaygroundBottomNavItemActiveDark" : "PlaygroundBottomNavItemActive"; + } else { + uiid = websiteDarkMode ? "PlaygroundBottomNavItemDark" : "PlaygroundBottomNavItem"; + } + btn.setUIID(uiid); + btn.setTextPosition(Component.BOTTOM); + FontImage.setMaterialIcon(btn, icon, 4f); + btn.putClientProperty("navKey", key); + btn.addActionListener(e -> handleActivitySelected(key)); + return btn; + } + + private void refreshBottomNav() { + if (bottomNav == null) { + return; + } + for (int i = 0; i < bottomNav.getComponentCount(); i++) { + Component c = bottomNav.getComponentAt(i); + if (!(c instanceof Button)) { + continue; + } + Button btn = (Button) c; + Object keyObj = btn.getClientProperty("navKey"); + if (!(keyObj instanceof String)) { + continue; + } + boolean active = keyObj.equals(currentActivity); + String uiid; + if (active) { + uiid = websiteDarkMode ? "PlaygroundBottomNavItemActiveDark" : "PlaygroundBottomNavItemActive"; + } else { + uiid = websiteDarkMode ? "PlaygroundBottomNavItemDark" : "PlaygroundBottomNavItem"; + } + btn.setUIID(uiid); + } + bottomNav.setUIID(websiteDarkMode ? "PlaygroundBottomNavDark" : "PlaygroundBottomNav"); + if (bottomNav.getComponentForm() != null) { + bottomNav.revalidate(); + } + } + + private void applyLayoutForCurrentSize() { + String bp = computeBreakpoint(); + if (bp.equals(lastBreakpoint)) { + return; + } + lastBreakpoint = bp; + + bodyContainer.removeAll(); + + switch (bp) { + case "mobile": + assembleMobileLayout(); + break; + case "tablet": + assembleDesktopLayout(true); + break; + default: + assembleDesktopLayout(false); + break; + } + + if (bodyContainer.getComponentForm() != null) { + bodyContainer.revalidate(); + } + } + + private String computeBreakpoint() { + int w = Display.getInstance().getDisplayWidth(); + int dipsW = (int) (w / (float) Display.getInstance().convertToPixels(1f)); + if (dipsW < 720) { + return "mobile"; + } + if (dipsW < 1100) { + return "tablet"; + } + return "desktop"; + } + + private void assembleDesktopLayout(boolean compact) { + topBar.setCompact(compact); + previewColumn.setCompact(compact); + + Container center = new Container(new BorderLayout()); + center.getAllStyles().setBgTransparency(0); + + Container sideAndEditor = new Container(new BorderLayout()); + sideAndEditor.getAllStyles().setBgTransparency(0); + sideAndEditor.add(BorderLayout.WEST, sidePanelSlot); + sideAndEditor.add(BorderLayout.CENTER, editorHost); + + center.add(BorderLayout.CENTER, sideAndEditor); + center.add(BorderLayout.EAST, previewContainer); + + Container bodyInner = new Container(new BorderLayout()); + bodyInner.getAllStyles().setBgTransparency(0); + bodyInner.add(BorderLayout.WEST, activityBar); + bodyInner.add(BorderLayout.CENTER, center); + + bodyContainer.add(BorderLayout.CENTER, bodyInner); + + int previewW = Display.getInstance().convertToPixels(compact ? 85f : 110f); + previewContainer.setPreferredW(previewW); + + attachEditorsToHost(); + } + + private void assembleMobileLayout() { + topBar.setCompact(true); + previewColumn.setCompact(true); + + Container stack = new Container(BoxLayout.y()); + stack.getAllStyles().setBgTransparency(0); + stack.add(mobileTopTabs); + + Container tabContent = new Container(new BorderLayout()); + tabContent.getAllStyles().setBgTransparency(0); + tabContent.putClientProperty("mobileTabContent", Boolean.TRUE); + + Container mobileLayout = new Container(new BorderLayout()); + mobileLayout.getAllStyles().setBgTransparency(0); + mobileLayout.add(BorderLayout.NORTH, stack); + mobileLayout.add(BorderLayout.CENTER, tabContent); + mobileLayout.add(BorderLayout.SOUTH, bottomNav); + + bodyContainer.add(BorderLayout.CENTER, mobileLayout); + refreshMobileTabContent(); + } + + private void refreshMobileTabContent() { + Container tabContent = findMobileTabContent(bodyContainer); + if (tabContent == null) { + return; + } + tabContent.removeAll(); + + switch (currentMobileTab) { + case MOBILE_TAB_CSS: + tabContent.add(BorderLayout.CENTER, cssEditor.getComponent()); + break; + case MOBILE_TAB_PREVIEW: + tabContent.add(BorderLayout.CENTER, previewColumn); + break; + default: + tabContent.add(BorderLayout.CENTER, editor.getComponent()); + break; + } + if (tabContent.getComponentForm() != null) { + tabContent.revalidate(); + } + } + + private Container findMobileTabContent(Container root) { + if (root == null) { + return null; + } + Object flag = root.getClientProperty("mobileTabContent"); + if (Boolean.TRUE.equals(flag)) { + return root; + } + for (int i = 0; i < root.getComponentCount(); i++) { + Component c = root.getComponentAt(i); + if (c instanceof Container) { + Container found = findMobileTabContent((Container) c); + if (found != null) { + return found; + } + } + } + return null; + } + + private void attachEditorsToHost() { + editorHost.removeAll(); + Component active = PlaygroundTopBar.MODE_CSS.equals(currentMode) ? cssEditor.getComponent() : editor.getComponent(); + editorHost.add(BorderLayout.CENTER, active); + if (editorHost.getComponentForm() != null) { + editorHost.revalidate(); + } } private void handlePropertyChanged(Component component) { @@ -149,20 +440,56 @@ private void handleCssChanged(String source, int version) { applyCurrentCss(); } - private Container createPreviewRoot() { - Container root = new Container(new BorderLayout()); - root.setScrollableY(true); - root.setUIID("PlaygroundPreview"); - return root; + private void handleActivitySelected(String key) { + currentActivity = key == null ? PlaygroundActivityBar.NONE : key; + if (activityBar != null) { + activityBar.setActive(currentActivity); + } + PlaygroundStateStore.savePanel(currentActivity); + applyActivity(currentActivity); + refreshBottomNav(); } - private Component createMainContent(Tabs tabs, Container previewPanel) { - tabs.setSwipeActivated(false); - if (!Display.getInstance().isPortrait()) { - return new SplitPane(SplitPane.HORIZONTAL_SPLIT, tabs, previewPanel, "25%", "50%", "75%"); + private void applyActivity(String key) { + sidePanelSlot.removeAll(); + if (PANEL_SAMPLES.equals(key)) { + sidePanelSlot.add(BorderLayout.CENTER, samplesPanel.getComponent()); + } else if (PANEL_HISTORY.equals(key)) { + historyPanel.setEntries(PlaygroundStateStore.loadHistory()); + sidePanelSlot.add(BorderLayout.CENTER, historyPanel.getComponent()); + } else if (PANEL_INSPECTOR.equals(key)) { + sidePanelSlot.add(BorderLayout.CENTER, inspectorWrapper); + } + if (sidePanelSlot.getComponentForm() != null) { + sidePanelSlot.revalidate(); + } + } + + private void handleSampleSelected(PlaygroundExamples.Sample sample) { + setScript(sample.script, true); + } + + private void handleHistorySelected(PlaygroundStateStore.HistoryEntry entry) { + setScript(entry.script, true); + } + + private void switchMode(String mode) { + applyMode(mode, true); + } + + private void applyMode(String mode, boolean userInitiated) { + if (mode == null) { + mode = PlaygroundTopBar.MODE_CODE; + } + currentMode = mode; + if (topBar != null) { + topBar.setMode(mode); + } + PlaygroundStateStore.saveMode(mode); + attachEditorsToHost(); + if (userInitiated) { + refreshMobileTabContent(); } - tabs.addTab("Preview", previewPanel); - return tabs; } private void runScript(Form form) { @@ -171,13 +498,10 @@ private void runScript(Form form) { private void executeRunScript(Form form) { CN.callSerially(() -> { - List history = PlaygroundStateStore.pushHistory(currentScript); - refreshHistoryMenu(form.getToolbar(), history); - List loggedMessages = new ArrayList<>(); PlaygroundContext context = new PlaygroundContext( form, - previewRoot, + previewColumn.getContentHost(), theme, message -> loggedMessages.add(new PlaygroundRunner.InlineMessage(0, message, "info")) ); @@ -187,21 +511,36 @@ private void executeRunScript(Form form) { currentMessages = new ArrayList<>(loggedMessages); currentMessages.addAll(result.getMessages()); - replacePreview(result.getComponent()); - editor.setMarkers(result.getDiagnostics()); + boolean hasErrors = false; + List diagnostics = result.getDiagnostics(); + for (int i = 0; i < diagnostics.size(); i++) { + if ("error".equalsIgnoreCase(diagnostics.get(i).severity)) { + hasErrors = true; + break; + } + } + + if (hasErrors) { + topBar.showFailed(); + previewColumn.setStale(true); + } else { + topBar.showLive(); + previewColumn.setStale(false); + replacePreview(result.getComponent()); + } + + editor.setMarkers(diagnostics); editor.setInlineMessages(currentMessages); - editor.setUiidCompletions(PlaygroundCssSupport.collectVisibleUiids(previewRoot)); + editor.setUiidCompletions(PlaygroundCssSupport.collectVisibleUiids(previewColumn.getContentHost())); applyCurrentCss(); persistCurrentState(); }); } private void replacePreview(Component component) { - previewRoot.removeAll(); - if (component == null) { + previewColumn.setPreview(null); inspector.setPreviewRoot(null); - previewRoot.revalidate(); return; } @@ -209,9 +548,8 @@ private void replacePreview(Component component) { markEmbeddedPreviewRoles(component); applyWebsiteTheme(component, websiteDarkMode); - previewRoot.add(BorderLayout.CENTER, component); - inspector.setPreviewRoot(previewRoot); - previewRoot.revalidate(); + previewColumn.setPreview(component); + inspector.setPreviewRoot(previewColumn.getContentHost()); } private void detachForPreview(Component component) { @@ -221,11 +559,6 @@ private void detachForPreview(Component component) { } } - /** - * Small special-case hook: - * when a Form or its title area is rendered inside the preview, - * keep its semantic appearance without introducing a whole second theme system. - */ private void markEmbeddedPreviewRoles(Component component) { if (component instanceof Form) { component.putClientProperty(THEME_ROLE, ROLE_EMBEDDED_FORM); @@ -234,7 +567,6 @@ private void markEmbeddedPreviewRoles(Component component) { f.getTitleArea().putClientProperty(THEME_ROLE, ROLE_EMBEDDED_TITLE_AREA); } } - if (component instanceof Container) { Container cnt = (Container) component; for (int i = 0; i < cnt.getComponentCount(); i++) { @@ -243,78 +575,6 @@ private void markEmbeddedPreviewRoles(Component component) { } } - private void installSideMenu(Toolbar toolbar) { - Toolbar.setEnableSideMenuSwipe(false); - PlaygroundMenuSection shareSection = new PlaygroundMenuSection("Share"); - addSideMenuComponent(toolbar, shareSection); - addSideMenuComponent(toolbar, createSideMenuButton(SHARE_BUTTON_LABEL, () -> { - copyCurrentSourceUrl(); - toolbar.closeSideMenu(); - })); - - PlaygroundMenuSection samplesSection = new PlaygroundMenuSection("Samples"); - addSideMenuComponent(toolbar, samplesSection); - for (PlaygroundExamples.Sample sample : PlaygroundExamples.SAMPLES) { - addSideMenuComponent(toolbar, createSideMenuButton(sample.title, () -> { - setScript(sample.script, true); - toolbar.closeSideMenu(); - })); - } - - PlaygroundMenuSection historySection = new PlaygroundMenuSection("History"); - addSideMenuComponent(toolbar, historySection); - addSideMenuComponent(toolbar, historyMenu); - - refreshHistoryMenu(toolbar, PlaygroundStateStore.loadHistory()); - } - - private void addSideMenuComponent(Toolbar toolbar, Component component) { - applyWebsiteTheme(component, websiteDarkMode); - sideMenuComponents.add(component); - toolbar.addComponentToSideMenu(component); - } - - private void refreshHistoryMenu(Toolbar toolbar, List history) { - historyMenu.removeAll(); - - if (history.isEmpty()) { - Label empty = new Label("No saved runs yet"); - empty.setUIID("PlaygroundMenuEmpty"); - historyMenu.add(empty); - } else { - for (PlaygroundStateStore.HistoryEntry entry : history) { - historyMenu.add(createHistoryButton(entry, history, toolbar)); - } - } - - applyWebsiteTheme(historyMenu, websiteDarkMode); - historyMenu.revalidate(); - } - - private MultiButton createHistoryButton(PlaygroundStateStore.HistoryEntry entry, - List history, - Toolbar toolbar) { - MultiButton button = new MultiButton(entry.title()); - button.setTextLine2(entry.detail(history)); - button.setUIID("PlaygroundSideCommand"); - button.setUIIDLine1("PlaygroundSideCommandLine1"); - button.setUIIDLine2("PlaygroundSideCommandLine2"); - button.addActionListener(e -> { - setScript(entry.script, true); - toolbar.closeSideMenu(); - }); - applyWebsiteTheme(button, websiteDarkMode); - return button; - } - - private Button createSideMenuButton(String text, Runnable action) { - Button button = new Button(text); - button.setUIID("PlaygroundSideCommand"); - button.addActionListener(e -> action.run()); - applyWebsiteTheme(button, websiteDarkMode); - return button; - } - private String resolveInitialScript() { String sharedScript = scriptFromUrl(); if (sharedScript != null) { @@ -451,6 +711,7 @@ private void setScript(String script, boolean runNow) { if (editor != null) { editor.setSource(currentScript); } + applyMode(PlaygroundTopBar.MODE_CODE, true); persistCurrentState(); if (runNow && appForm != null) { runScript(appForm); @@ -462,7 +723,6 @@ private void applyCurrentCss() { return; } restoreThemeDefaults(); - applySideMenuPalette(websiteDarkMode); List diagnostics = new ArrayList(); List messages = new ArrayList(); try { @@ -478,8 +738,7 @@ private void applyCurrentCss() { currentCssMessages = messages; cssEditor.setMarkers(diagnostics); cssEditor.setInlineMessages(messages); - cssEditor.setUiidCompletions(PlaygroundCssSupport.collectVisibleUiids(previewRoot)); - applyTabsTheme(websiteDarkMode); + cssEditor.setUiidCompletions(PlaygroundCssSupport.collectVisibleUiids(previewColumn.getContentHost())); appForm.refreshTheme(); } @@ -509,9 +768,10 @@ private void applyCssToPreview(Form form, String css) { Hashtable customTheme = resource.getTheme("PlaygroundCustomTheme"); if (customTheme != null && !customTheme.isEmpty()) { UIManager.getInstance().addThemeProps(customTheme); - if (previewRoot != null) { - previewRoot.refreshTheme(); - previewRoot.revalidate(); + Container host = previewColumn.getContentHost(); + if (host != null) { + host.refreshTheme(); + host.revalidate(); } else { form.refreshTheme(); } @@ -548,11 +808,13 @@ private void scheduleHistorySnapshot() { if (appForm == null) { return; } - - final int snapshot = ++editSequence; - UITimer.timer(1200, false, appForm, () -> { - if (snapshot == editSequence) { - refreshHistoryMenu(appForm.getToolbar(), PlaygroundStateStore.pushHistory(currentScript)); + final int snapshot = ++historySequence; + UITimer.timer(2000, false, appForm, () -> { + if (snapshot == historySequence) { + List updated = PlaygroundStateStore.pushHistory(currentScript); + if (historyPanel != null) { + historyPanel.setEntries(updated); + } } }); } @@ -561,7 +823,6 @@ private void scheduleAutoRun() { if (appForm == null) { return; } - final int runTicket = ++autoRunSequence; UITimer.timer(850, false, appForm, () -> { if (runTicket == autoRunSequence) { @@ -574,7 +835,6 @@ private void initWebsiteThemeSync(Form form) { websiteThemeNative = NativeLookup.create(WebsiteThemeNative.class); refreshWebsiteTheme(form); UITimer.timer(900, true, form, () -> refreshWebsiteTheme(form)); - UITimer.timer(250, true, form, this::syncOpenSideMenuTheme); } private void notifyWebsiteUiReady() { @@ -650,10 +910,8 @@ private void applyDarkMode(Form form, boolean dark) { if (!websiteThemeInitialized || dark != websiteDarkMode) { websiteDarkMode = dark; websiteThemeInitialized = true; - applySideMenuPalette(dark); + applyWebsiteTheme(form, dark); - applyTabsTheme(dark); - form.refreshTheme(); if (editor != null) { editor.applyTheme(dark); @@ -664,62 +922,39 @@ private void applyDarkMode(Form form, boolean dark) { if (inspector != null) { inspector.applyTheme(dark); } - for (Component cmp : sideMenuComponents) { - applyWebsiteTheme(cmp, dark); + if (topBar != null) { + topBar.applyTheme(dark); + } + if (activityBar != null) { + activityBar.applyTheme(dark); + } + if (samplesPanel != null) { + samplesPanel.applyTheme(dark); + } + if (historyPanel != null) { + historyPanel.applyTheme(dark); + } + if (previewColumn != null) { + previewColumn.applyTheme(dark); + } + if (mobileTopTabs != null) { + mobileTopTabs.applyTheme(dark); } - } - } - - private void applySideMenuPalette(boolean dark) { - Hashtable sideMenuPalette = new Hashtable(); - int bgColor = dark ? 0x0f172a : 0xffffff; - int borderColor = dark ? 0x1f2937 : 0xcccccc; - - sideMenuPalette.put("SideNavigationPanel.bgColor", bgColor); - sideMenuPalette.put("SideNavigationPanel.bgTransparency", 255); - sideMenuPalette.put("SideNavigationPanelDark.bgColor", bgColor); - sideMenuPalette.put("SideNavigationPanelDark.bgTransparency", 255); - sideMenuPalette.put("RightSideNavigationPanel.bgColor", bgColor); - sideMenuPalette.put("RightSideNavigationPanel.bgTransparency", 255); - - sideMenuPalette.put("StatusBarSideMenu.bgColor", bgColor); - sideMenuPalette.put("StatusBarSideMenu.bgTransparency", 255); - sideMenuPalette.put("StatusBarSideMenuDark.bgColor", bgColor); - sideMenuPalette.put("StatusBarSideMenuDark.bgTransparency", 255); - - sideMenuPalette.put("SideCommand.bgColor", bgColor); - sideMenuPalette.put("SideCommand.bgTransparency", 255); - sideMenuPalette.put("SideCommand.border", com.codename1.ui.plaf.Border.createLineBorder(2, borderColor)); - UIManager.getInstance().addThemeProps(sideMenuPalette); - } - - private void syncOpenSideMenuTheme() { - Form current = Display.getInstance().getCurrent(); - if (current == null) { - return; - } - applySideMenuContainerTheme(current); - } - private void applySideMenuContainerTheme(Component component) { - if (component == null) { - return; - } - String uiid = component.getUIID(); - if ("SideNavigationPanel".equals(uiid) - || "SideNavigationPanelDark".equals(uiid) - || "RightSideNavigationPanel".equals(uiid) - || "StatusBarSideMenu".equals(uiid) - || "StatusBarSideMenuDark".equals(uiid)) { - applyWebsiteTheme(component, websiteDarkMode); - component.getAllStyles().setBgTransparency(255); - component.getAllStyles().setBgColor(websiteDarkMode ? 0x0f172a : 0xffffff); - } - if (component instanceof Container) { - Container cnt = (Container) component; - for (int i = 0; i < cnt.getComponentCount(); i++) { - applySideMenuContainerTheme(cnt.getComponentAt(i)); + if (inspectorWrapper != null) { + inspectorWrapper.setUIID(dark ? "PlaygroundSidePanelDark" : "PlaygroundSidePanel"); + } + if (editorHost != null) { + editorHost.setUIID(dark ? "PlaygroundPanelDark" : "PlaygroundPanel"); + } + if (previewContainer != null) { + previewContainer.setUIID(dark ? "PlaygroundPanelDark" : "PlaygroundPanel"); + } + if (bodyContainer != null) { + bodyContainer.setUIID(dark ? "PlaygroundBodyDark" : "PlaygroundBody"); } + refreshBottomNav(); + form.refreshTheme(); } } @@ -777,8 +1012,43 @@ private boolean supportsDarkVariant(String uiid) { switch (uiid) { case "PlaygroundForm": case "PlaygroundContent": + case "PlaygroundBody": case "PlaygroundToolbar": case "PlaygroundTitle": + case "PlaygroundTopBar": + case "PlaygroundWordmark": + case "PlaygroundSegment": + case "PlaygroundSegmentOption": + case "PlaygroundSegmentOptionSelected": + case "PlaygroundStatusPill": + case "PlaygroundStatusPillError": + case "PlaygroundShareButton": + case "PlaygroundDownloadButton": + case "PlaygroundActivityBar": + case "PlaygroundActivityButton": + case "PlaygroundActivityButtonActive": + case "PlaygroundSidePanel": + case "PlaygroundSidePanelHeader": + case "PlaygroundSidePanelClose": + case "PlaygroundSearchField": + case "PlaygroundSampleItem": + case "PlaygroundSampleItemSelected": + case "PlaygroundHistoryItem": + case "PlaygroundHistoryLine1": + case "PlaygroundHistoryLine2": + case "PlaygroundPreviewToolbar": + case "PlaygroundDimensions": + case "PlaygroundDeviceStage": + case "PlaygroundNoSkinStage": + case "PlaygroundDeviceBezel": + case "PlaygroundDeviceScreen": + case "PlaygroundStaleChip": + case "PlaygroundTopTabs": + case "PlaygroundTopTab": + case "PlaygroundTopTabSelected": + case "PlaygroundBottomNav": + case "PlaygroundBottomNavItem": + case "PlaygroundBottomNavItemActive": case "PlaygroundPanel": case "PlaygroundPreview": case "SideNavigationPanel": @@ -809,21 +1079,6 @@ private boolean supportsDarkVariant(String uiid) { } } - private void applyTabsTheme(boolean dark) { - if (editorTabs != null) { - String tabsUiid = dark ? "PlaygroundEditorTabsDark" : "PlaygroundEditorTabs"; - String tabUiid = dark ? "TabDark" : "Tab"; - editorTabs.setUIID(tabsUiid); - editorTabs.setTabUIID(tabUiid); - Container tabsContainer = editorTabs.getTabsContainer(); - for (int i = 0; i < tabsContainer.getComponentCount(); i++) { - tabsContainer.getComponentAt(i).setUIID(tabUiid); - } - editorTabs.refreshTheme(); - editorTabs.revalidate(); - } - } - private String getThemeRole(Component component) { Object val = component.getClientProperty(THEME_ROLE); return val instanceof String ? (String) val : null; diff --git a/scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundActivityBar.java b/scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundActivityBar.java new file mode 100644 index 0000000000..34e782de83 --- /dev/null +++ b/scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundActivityBar.java @@ -0,0 +1,104 @@ +package com.codenameone.playground; + +import com.codename1.ui.Button; +import com.codename1.ui.Component; +import com.codename1.ui.Container; +import com.codename1.ui.Display; +import com.codename1.ui.FontImage; +import com.codename1.ui.layouts.BoxLayout; + +import java.util.ArrayList; +import java.util.List; + +final class PlaygroundActivityBar extends Container { + interface Listener { + void onActivitySelected(String key); + } + + static final class Item { + final String key; + final String label; + final char icon; + + Item(String key, String label, char icon) { + this.key = key; + this.label = label; + this.icon = icon; + } + } + + static final String NONE = ""; + + private final List items = new ArrayList(); + private final List