diff --git a/bundles/org.eclipse.e4.ui.css.swt.theme/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.ui.css.swt.theme/META-INF/MANIFEST.MF index 779d1ad1941..137edc5df3c 100644 --- a/bundles/org.eclipse.e4.ui.css.swt.theme/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.e4.ui.css.swt.theme/META-INF/MANIFEST.MF @@ -6,7 +6,7 @@ Bundle-Name: %pluginName Bundle-Vendor: %providerName Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-21 -Require-Bundle: org.eclipse.swt;bundle-version="[3.133.0,4.0.0)", +Require-Bundle: org.eclipse.swt;bundle-version="[3.134.0,4.0.0)", org.eclipse.e4.ui.css.swt;bundle-version="0.13.100", org.eclipse.e4.ui.css.core;bundle-version="0.12.200", org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)" diff --git a/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/ThemeEngine.java b/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/ThemeEngine.java index 8ddcf3d802e..5c7e4724459 100644 --- a/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/ThemeEngine.java +++ b/bundles/org.eclipse.e4.ui.css.swt.theme/src/org/eclipse/e4/ui/css/swt/internal/theme/ThemeEngine.java @@ -510,6 +510,9 @@ public void setTheme(ITheme theme, boolean restore, boolean force) { ThemeEngineManager.logError(e.getMessage(), e); } } + boolean isDark = theme.getId().contains("dark"); //$NON-NLS-1$ + display.setDarkThemePreferred(isDark); + sendThemeChangeEvent(restore); for (CSSEngine engine : cssEngines) { diff --git a/bundles/org.eclipse.ui.ide.application/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.ide.application/META-INF/MANIFEST.MF index 7c3f352aad7..370f91c94c4 100644 --- a/bundles/org.eclipse.ui.ide.application/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.ide.application/META-INF/MANIFEST.MF @@ -18,6 +18,7 @@ Require-Bundle: org.eclipse.ui.ide;bundle-version="[3.21.0,4.0.0)", org.eclipse.e4.core.di.extensions, org.eclipse.e4.core.services;bundle-version="2.4.0", org.eclipse.e4.core.contexts;bundle-version="[1.12.0,2.0.0)", + org.eclipse.ui.forms, org.eclipse.urischeme;bundle-version="[1.3.0,2.0.0)", org.eclipse.e4.ui.di Export-Package: org.eclipse.ui.internal.ide.application;x-internal:=true, diff --git a/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java b/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java index a4bc366a90f..3b5eb4cfbe5 100644 --- a/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java +++ b/bundles/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java @@ -36,12 +36,15 @@ import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExecutableExtension; +import org.eclipse.core.runtime.IProduct; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.preferences.ConfigurationScope; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.UserScope; import org.eclipse.equinox.app.IApplication; import org.eclipse.equinox.app.IApplicationContext; import org.eclipse.jface.dialogs.IDialogConstants; @@ -52,14 +55,18 @@ import org.eclipse.osgi.service.datalocation.Location; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.forms.widgets.ExpandableComposite; import org.eclipse.ui.internal.Workbench; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.internal.WorkspaceLock; @@ -94,6 +101,9 @@ public class IDEApplication implements IApplication, IExecutableExtension { private static final String USER_NAME = "user.name"; //$NON-NLS-1$ + private boolean isDark; + private Listener darkThemeShowListener; + // Use the branding plug-in of the platform feature since this is most likely // to change on an update of the IDE. private static final String WORKSPACE_CHECK_REFERENCE_BUNDLE_NAME = "org.eclipse.platform"; //$NON-NLS-1$ @@ -145,6 +155,9 @@ public Object start(IApplicationContext appContext) throws Exception { Job.getJobManager().suspend(); Display display = createDisplay(); + + initializeDefaultTheme(display); + // processor must be created before we start event loop DelayedEventsProcessor processor = new DelayedEventsProcessor(display); @@ -167,6 +180,10 @@ public Object start(IApplicationContext appContext) throws Exception { return instanceLocationCheck; } + // Reset early dark theme styling before the workbench starts; + // the ThemeEngine will apply the correct theme from here on. + resetEarlyDarkTheme(display); + // create the workbench with this advisor and run it until it exits // N.B. createWorkbench remembers the advisor, and also registers // the workbench globally so that all UI plug-ins can find it using @@ -587,6 +604,14 @@ protected Shell getParentShell() { return null; } + @Override + protected Control createContents(Composite parent) { + Control contents = super.createContents(parent); + if (isDark) { + applyDarkStyles(getShell()); + } + return contents; + } }.prompt(force); } @@ -852,6 +877,85 @@ protected static Version toMajorMinorVersion(Version version) { return new Version(version.getMajor(), version.getMinor(), 0); } + protected void initializeDefaultTheme(Display display) { + IEclipsePreferences themeNode = UserScope.INSTANCE.getNode("org.eclipse.e4.ui.css.swt.theme"); //$NON-NLS-1$ + String productOrAppId = getProductOrApplicationId(); + String defaultThemeId; + if (productOrAppId != null) { + defaultThemeId = themeNode.node(productOrAppId).get("themeid", null); //$NON-NLS-1$ + } else { + defaultThemeId = themeNode.get("themeid", null); //$NON-NLS-1$ + } + isDark = defaultThemeId != null && defaultThemeId.contains("dark"); //$NON-NLS-1$ + if (isDark) { + display.setDarkThemePreferred(true); + darkThemeShowListener = event -> { + if (event.widget instanceof Shell shell) { + applyDarkStyles(shell); + } + }; + display.addListener(SWT.Show, darkThemeShowListener); + } + } + + /** + * Removes the early dark theme styling applied for the workspace selection + * dialog. Must be called before the workbench starts so the ThemeEngine can + * manage the theme without interference. + */ + protected void resetEarlyDarkTheme(Display display) { + if (darkThemeShowListener != null) { + display.removeListener(SWT.Show, darkThemeShowListener); + darkThemeShowListener = null; + } + if (isDark) { + display.setDarkThemePreferred(false); + } + } + + /** + * Returns the product ID if a product is configured, otherwise falls back to + * the application ID from the system property. Returns {@code null} if neither + * is available. + */ + private static String getProductOrApplicationId() { + IProduct product = Platform.getProduct(); + if (product != null) { + return product.getId(); + } + return System.getProperty("eclipse.application"); //$NON-NLS-1$ + } + + private void applyDarkStyles(Shell shell) { + Color bg = new Color(72, 72, 76); // #48484c + Color fg = new Color(238, 238, 238); // #eeeeee + Color linkColor = new Color(111, 197, 238); // #6FC5EE + shell.setBackground(bg); + shell.setForeground(fg); + applyStylesRecursive(shell, bg, fg, linkColor); + } + + private void applyStylesRecursive(Control control, Color bg, Color fg, Color linkColor) { + control.setBackground(bg); + if (control instanceof Link link) { + link.setLinkForeground(linkColor); + } else { + control.setForeground(fg); + + } + + if (control instanceof ExpandableComposite expandable) { + expandable.setTitleBarForeground(fg); + expandable.setToggleColor(fg); + expandable.setActiveToggleColor(fg); + } + if (control instanceof Composite composite) { + for (Control child : composite.getChildren()) { + applyStylesRecursive(child, bg, fg, linkColor); + } + } + } + @Override public void stop() { final IWorkbench workbench = PlatformUI.getWorkbench(); diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/ChooseWorkspaceDialog.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/ChooseWorkspaceDialog.java index 78701c3ece1..8c5de79ef9f 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/ChooseWorkspaceDialog.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/ChooseWorkspaceDialog.java @@ -39,6 +39,7 @@ import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.jface.resource.JFaceColors; import org.eclipse.jface.util.Geometry; import org.eclipse.jface.window.Window; import org.eclipse.osgi.util.NLS; @@ -319,6 +320,10 @@ private void createRecentWorkspacesComposite(final Composite composite) { recentWorkspacesForm.getBody().setLayout(new GridLayout()); ExpandableComposite recentWorkspacesExpandable = toolkit.createExpandableComposite(recentWorkspacesForm.getBody(), ExpandableComposite.TWISTIE); + recentWorkspacesExpandable.setTitleBarForeground(composite.getForeground()); + recentWorkspacesExpandable.setForeground(composite.getForeground()); + recentWorkspacesExpandable.setToggleColor(composite.getForeground()); + recentWorkspacesExpandable.setActiveToggleColor(composite.getForeground()); recentWorkspacesForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); recentWorkspacesExpandable.setBackground(composite.getBackground()); recentWorkspacesExpandable.setText(IDEWorkbenchMessages.ChooseWorkspaceDialog_recentWorkspaces); @@ -352,6 +357,7 @@ public void expansionStateChanged(ExpansionEvent e) { final String recentWorkspace = uniqueWorkspaceEntry.getValue(); Link link = new Link(panel, SWT.WRAP); + link.setForeground(JFaceColors.getHyperlinkText(composite.getDisplay())); link.setLayoutData(new RowData(SWT.DEFAULT, SWT.DEFAULT)); link.setText("" + uniqueWorkspaceEntry.getKey() + ""); //$NON-NLS-1$ //$NON-NLS-2$ link.setToolTipText(recentWorkspace); diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/ChooseWorkspaceWithSettingsDialog.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/ChooseWorkspaceWithSettingsDialog.java index 0955fae4425..25d2398adf9 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/ChooseWorkspaceWithSettingsDialog.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/ChooseWorkspaceWithSettingsDialog.java @@ -128,6 +128,9 @@ private void createSettingsControls(Composite workArea) { copySettingsExpandable.setText(IDEWorkbenchMessages.ChooseWorkspaceWithSettingsDialog_SettingsGroupName); copySettingsExpandable.setBackground(workArea.getBackground()); + copySettingsExpandable.setTitleBarForeground(workArea.getForeground()); + copySettingsExpandable.setToggleColor(workArea.getForeground()); + copySettingsExpandable.setActiveToggleColor(workArea.getForeground()); copySettingsExpandable.setLayout(new GridLayout()); copySettingsExpandable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); copySettingsExpandable.addExpansionListener(new IExpansionListener() { diff --git a/tests/org.eclipse.ui.tests.forms/forms/org/eclipse/ui/tests/forms/util/FlatLookTest.java b/tests/org.eclipse.ui.tests.forms/forms/org/eclipse/ui/tests/forms/util/FlatLookTest.java index 81ec404c8b8..900976f410c 100644 --- a/tests/org.eclipse.ui.tests.forms/forms/org/eclipse/ui/tests/forms/util/FlatLookTest.java +++ b/tests/org.eclipse.ui.tests.forms/forms/org/eclipse/ui/tests/forms/util/FlatLookTest.java @@ -60,7 +60,7 @@ public void testFormHeadingFlatLook() { FormHeading head = (FormHeading) form.getHead(); head.setSize(100, 50); - Color color = new Color(display, 255, 0, 0); + Color color = new Color(255, 0, 0); Color[] identicalColors = new Color[] { color, color }; int[] percents = new int[] { 100 }; @@ -74,7 +74,7 @@ public void testFormHeadingFlatLook() { }); // Verify with distinct colors as well to ensure both paths work - Color color2 = new Color(display, 0, 0, 255); + Color color2 = new Color(0, 0, 255); Color[] distinctColors = new Color[] { color, color2 }; head.setTextBackground(distinctColors, percents, true); @@ -90,7 +90,7 @@ public void testFlatLookUsesSolidBackground() { FormHeading head = (FormHeading) form.getHead(); head.setSize(100, 50); - Color color = new Color(display, 200, 100, 50); + Color color = new Color(200, 100, 50); head.setTextBackground(new Color[] { color, color }, new int[] { 100 }, true); // Flat look: identical colors should use solid background, no gradient @@ -107,8 +107,8 @@ public void testGradientLookUsesBackgroundImage() { FormHeading head = (FormHeading) form.getHead(); head.setSize(100, 50); - Color color1 = new Color(display, 255, 0, 0); - Color color2 = new Color(display, 0, 0, 255); + Color color1 = new Color(255, 0, 0); + Color color2 = new Color(0, 0, 255); head.setTextBackground(new Color[] { color1, color2 }, new int[] { 100 }, true); // Gradient look: distinct colors should generate a background image @@ -123,13 +123,13 @@ public void testSwitchFromGradientToFlat() { head.setSize(100, 50); // First set a real gradient - Color color1 = new Color(display, 255, 0, 0); - Color color2 = new Color(display, 0, 0, 255); + Color color1 = new Color(255, 0, 0); + Color color2 = new Color(0, 0, 255); head.setTextBackground(new Color[] { color1, color2 }, new int[] { 100 }, true); assertNotNull(head.getBackgroundImage()); // Switch to flat look — gradient image should be cleaned up - Color flat = new Color(display, 128, 128, 128); + Color flat = new Color(128, 128, 128); head.setTextBackground(new Color[] { flat, flat }, new int[] { 100 }, true); assertNull(head.getBackgroundImage(), "Gradient image should be removed when switching to flat look"); @@ -142,7 +142,7 @@ public void testSingleColorArrayIsFlatLook() { FormHeading head = (FormHeading) form.getHead(); head.setSize(100, 50); - Color color = new Color(display, 42, 42, 42); + Color color = new Color(42, 42, 42); head.setTextBackground(new Color[] { color }, new int[] { 100 }, true); assertNull(head.getBackgroundImage(), @@ -156,7 +156,7 @@ public void testFlatLookRendersWithoutErrors() { FormHeading head = (FormHeading) form.getHead(); head.setSize(100, 50); - Color color = new Color(display, 200, 200, 200); + Color color = new Color(200, 200, 200); head.setTextBackground(new Color[] { color, color }, new int[] { 100 }, true); assertDoesNotThrow(() -> { @@ -171,8 +171,8 @@ public void testResetToNullClearsGradient() { FormHeading head = (FormHeading) form.getHead(); head.setSize(100, 50); - Color color1 = new Color(display, 255, 0, 0); - Color color2 = new Color(display, 0, 0, 255); + Color color1 = new Color(255, 0, 0); + Color color2 = new Color(0, 0, 255); head.setTextBackground(new Color[] { color1, color2 }, new int[] { 100 }, true); assertNotNull(head.getBackgroundImage()); @@ -185,7 +185,7 @@ public void testResetToNullClearsGradient() { @Test public void testSectionFlatLook() { Section section = new Section(shell, Section.TITLE_BAR); - Color bg = new Color(display, 240, 240, 240); + Color bg = new Color(240, 240, 240); section.setTitleBarBackground(bg); section.setTitleBarBorderColor(bg); @@ -200,7 +200,7 @@ public void testSectionFlatLook() { @Test public void testFormImagesFlatGradient() throws Exception { FormImages instance = FormImages.getInstance(); - Color color = new Color(display, 100, 100, 100); + Color color = new Color(100, 100, 100); // test simple gradient with identical colors org.eclipse.swt.graphics.Image img1 = instance.getGradient(color, color, 10, 10, 0, display);