From 5ed40cab1d7fb6dc02269d680a6849faa871e1e5 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 29 Mar 2020 18:02:35 +0200 Subject: [PATCH] Demo: support using own FlatLaf themes (`.properties` files) that are located in working directory of Demo application --- CHANGELOG.md | 3 + .../java/com/formdev/flatlaf/FlatLaf.java | 11 +- .../com/formdev/flatlaf/IntelliJTheme.java | 2 +- .../com/formdev/flatlaf/UIDefaultsLoader.java | 10 +- flatlaf-demo/DemoLaf.properties | 13 ++ .../com/formdev/flatlaf/demo/DemoPrefs.java | 32 +++-- .../demo/intellijthemes/IJThemesManager.java | 9 +- .../demo/intellijthemes/IJThemesPanel.java | 111 +++++++++++++++--- 8 files changed, 159 insertions(+), 32 deletions(-) create mode 100644 flatlaf-demo/DemoLaf.properties diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e9fd22f..b00180af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ FlatLaf Change Log on JetBrains Runtime. (issue #69) - Tree: Fixed repainting wide selection on focus gained/lost. - No longer use system property `sun.java2d.uiScale`. (Java 8 only) +- Demo: Support using own FlatLaf themes (`.properties` files) that are located + in working directory of Demo application. Shown in the "Themes" list under + category "Current Directory". ## 0.28 diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java index 1f11462b..632d2db1 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -34,6 +34,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.ServiceLoader; import java.util.function.Consumer; import java.util.logging.Level; @@ -278,9 +279,9 @@ public abstract class FlatLaf // load defaults from properties List> lafClassesForDefaultsLoading = getLafClassesForDefaultsLoading(); if( lafClassesForDefaultsLoading != null ) - UIDefaultsLoader.loadDefaultsFromProperties( lafClassesForDefaultsLoading, addons, defaults ); + UIDefaultsLoader.loadDefaultsFromProperties( lafClassesForDefaultsLoading, addons, getAdditionalDefaults(), defaults ); else - UIDefaultsLoader.loadDefaultsFromProperties( getClass(), addons, defaults ); + UIDefaultsLoader.loadDefaultsFromProperties( getClass(), addons, getAdditionalDefaults(), defaults ); // use Aqua MenuBarUI if Mac screen menubar is enabled if( SystemInfo.IS_MAC && Boolean.getBoolean( "apple.laf.useScreenMenuBar" ) ) @@ -307,7 +308,11 @@ public abstract class FlatLaf void applyAdditionalDefaults( UIDefaults defaults ) { } - List> getLafClassesForDefaultsLoading() { + protected List> getLafClassesForDefaultsLoading() { + return null; + } + + protected Properties getAdditionalDefaults() { return null; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java index c0304f4b..15779ab5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java @@ -518,7 +518,7 @@ public class IntelliJTheme } @Override - ArrayList> getLafClassesForDefaultsLoading() { + protected ArrayList> getLafClassesForDefaultsLoading() { ArrayList> lafClasses = new ArrayList<>(); lafClasses.add( FlatLaf.class ); lafClasses.add( theme.dark ? FlatDarkLaf.class : FlatLightLaf.class ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java index cc508fe2..26a5a6b7 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java @@ -69,7 +69,7 @@ class UIDefaultsLoader private static final String GLOBAL_PREFIX = "*."; static void loadDefaultsFromProperties( Class lookAndFeelClass, List addons, - UIDefaults defaults ) + Properties additionalDefaults, UIDefaults defaults ) { // determine classes in class hierarchy in reverse order ArrayList> lafClasses = new ArrayList<>(); @@ -80,11 +80,11 @@ class UIDefaultsLoader lafClasses.add( 0, lafClass ); } - loadDefaultsFromProperties( lafClasses, addons, defaults ); + loadDefaultsFromProperties( lafClasses, addons, additionalDefaults, defaults ); } static void loadDefaultsFromProperties( List> lafClasses, List addons, - UIDefaults defaults ) + Properties additionalDefaults, UIDefaults defaults ) { try { // load core properties files @@ -115,6 +115,10 @@ class UIDefaultsLoader addonClassLoaders.add( addonClassLoader ); } + // add additional defaults + if( additionalDefaults != null ) + properties.putAll( additionalDefaults ); + // collect all platform specific keys (but do not modify properties) ArrayList platformSpecificKeys = new ArrayList<>(); for( Object key : properties.keySet() ) { diff --git a/flatlaf-demo/DemoLaf.properties b/flatlaf-demo/DemoLaf.properties new file mode 100644 index 00000000..6a76fcd6 --- /dev/null +++ b/flatlaf-demo/DemoLaf.properties @@ -0,0 +1,13 @@ +# This file demonstrates using a FlatLaf theme file in the FlatLaf Demo application. +# Must be in the working directory of the Demo application. +# Shown in the "Themes" list under category "Current Directory". +# +# Modifications to this file are automatically loaded by the FlatLaf Demo application +# when the Demo window is activated. + + +# base theme (light, dark, intellij or darcula) +@baseTheme=light + +# add you theme defaults here +@background=#ccc diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoPrefs.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoPrefs.java index 5a0317fb..ffbfab7e 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoPrefs.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoPrefs.java @@ -16,6 +16,7 @@ package com.formdev.flatlaf.demo; +import java.io.File; import java.io.FileInputStream; import java.util.prefs.Preferences; import javax.swing.UIManager; @@ -23,6 +24,8 @@ import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLightLaf; import com.formdev.flatlaf.IntelliJTheme; import com.formdev.flatlaf.demo.intellijthemes.IJThemesPanel; +import com.formdev.flatlaf.demo.intellijthemes.IJThemesPanel.PropertiesLaf; +import com.formdev.flatlaf.util.StringUtils; /** * @author Karl Tauber @@ -30,12 +33,12 @@ import com.formdev.flatlaf.demo.intellijthemes.IJThemesPanel; public class DemoPrefs { public static final String KEY_LAF = "laf"; - public static final String KEY_LAF_INTELLIJ_THEME = "lafIntelliJTheme"; + public static final String KEY_LAF_THEME = "lafTheme"; public static final String RESOURCE_PREFIX = "res:"; public static final String FILE_PREFIX = "file:"; - public static final String INTELLIJ_THEME_UI_KEY = "__FlatLaf.demo.intelliJTheme"; + public static final String THEME_UI_KEY = "__FlatLaf.demo.theme"; private static Preferences state; @@ -55,16 +58,27 @@ public class DemoPrefs else { String lafClassName = state.get( KEY_LAF, FlatLightLaf.class.getName() ); if( IntelliJTheme.ThemeLaf.class.getName().equals( lafClassName ) ) { - String intelliJTheme = state.get( KEY_LAF_INTELLIJ_THEME, "" ); - if( intelliJTheme.startsWith( RESOURCE_PREFIX ) ) - IntelliJTheme.install( IJThemesPanel.class.getResourceAsStream( intelliJTheme.substring( RESOURCE_PREFIX.length() ) ) ); - else if( intelliJTheme.startsWith( FILE_PREFIX ) ) - FlatLaf.install( IntelliJTheme.createLaf( new FileInputStream( intelliJTheme.substring( FILE_PREFIX.length() ) ) ) ); + String theme = state.get( KEY_LAF_THEME, "" ); + if( theme.startsWith( RESOURCE_PREFIX ) ) + IntelliJTheme.install( IJThemesPanel.class.getResourceAsStream( theme.substring( RESOURCE_PREFIX.length() ) ) ); + else if( theme.startsWith( FILE_PREFIX ) ) + FlatLaf.install( IntelliJTheme.createLaf( new FileInputStream( theme.substring( FILE_PREFIX.length() ) ) ) ); else FlatLightLaf.install(); - if( !intelliJTheme.isEmpty() ) - UIManager.getLookAndFeelDefaults().put( INTELLIJ_THEME_UI_KEY, intelliJTheme ); + if( !theme.isEmpty() ) + UIManager.getLookAndFeelDefaults().put( THEME_UI_KEY, theme ); + } else if( IJThemesPanel.PropertiesLaf.class.getName().equals( lafClassName ) ) { + String theme = state.get( KEY_LAF_THEME, "" ); + if( theme.startsWith( FILE_PREFIX ) ) { + File themeFile = new File( theme.substring( FILE_PREFIX.length() ) ); + String themeName = StringUtils.removeTrailing( themeFile.getName(), ".properties" ); + FlatLaf.install( new PropertiesLaf( themeName, themeFile ) ); + } else + FlatLightLaf.install(); + + if( !theme.isEmpty() ) + UIManager.getLookAndFeelDefaults().put( THEME_UI_KEY, theme ); } else UIManager.setLookAndFeel( lafClassName ); } diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesManager.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesManager.java index 6e7969a2..22e568cd 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesManager.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesManager.java @@ -68,7 +68,9 @@ class IJThemesManager // get current working directory File directory = new File( "" ).getAbsoluteFile(); - File[] themeFiles = directory.listFiles( (dir, name) -> name.endsWith( ".theme.json" ) ); + File[] themeFiles = directory.listFiles( (dir, name) -> { + return name.endsWith( ".theme.json" ) || name.endsWith( ".properties" ); + } ); if( themeFiles == null ) return; @@ -77,7 +79,10 @@ class IJThemesManager moreThemes.clear(); for( File f : themeFiles ) { - String name = StringUtils.removeTrailing( f.getName(), ".theme.json" ); + String fname = f.getName(); + String name = fname.endsWith( ".properties" ) + ? StringUtils.removeTrailing( fname, ".properties" ) + : StringUtils.removeTrailing( fname, ".theme.json" ); moreThemes.add( new IJThemeInfo( name, null, null, null, null, null, f, null ) ); lastModifiedMap.put( f, f.lastModified() ); } diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesPanel.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesPanel.java index d5b5269d..43b26e42 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesPanel.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/intellijthemes/IJThemesPanel.java @@ -28,6 +28,7 @@ import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; @@ -37,6 +38,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Objects; +import java.util.Properties; import java.util.function.Predicate; import javax.swing.*; import javax.swing.border.CompoundBorder; @@ -71,6 +73,7 @@ public class IJThemesPanel private Window window; private File lastDirectory; + private boolean isAdjustingThemesList; public IJThemesPanel() { initComponents(); @@ -134,6 +137,10 @@ public class IJThemesPanel themes.add( new IJThemeInfo( "Flat IntelliJ", null, null, null, null, null, null, FlatIntelliJLaf.class.getName() ) ); themes.add( new IJThemeInfo( "Flat Darcula", null, null, null, null, null, null, FlatDarculaLaf.class.getName() ) ); + // add themes from directory + categories.put( themes.size(), "Current Directory" ); + themes.addAll( themesManager.moreThemes ); + // add uncategorized bundled themes categories.put( themes.size(), "IntelliJ Themes" ); for( IJThemeInfo ti : themesManager.bundledThemes ) { @@ -157,10 +164,6 @@ public class IJThemesPanel themes.add( ti ); } - // add themes from directory - categories.put( themes.size(), "Current Directory" ); - themes.addAll( themesManager.moreThemes ); - // remember selection IJThemeInfo oldSel = themesList.getSelectedValue(); @@ -193,7 +196,7 @@ public class IJThemesPanel } private void themesListValueChanged( ListSelectionEvent e ) { - if( e.getValueIsAdjusting() ) + if( e.getValueIsAdjusting() || isAdjustingThemesList ) return; IJThemeInfo themeInfo = themesList.getSelectedValue(); @@ -223,15 +226,19 @@ public class IJThemesPanel } } else if( themeInfo.themeFile != null ) { try { - FlatLaf.install( IntelliJTheme.createLaf( new FileInputStream( themeInfo.themeFile ) ) ); - DemoPrefs.getState().put( DemoPrefs.KEY_LAF_INTELLIJ_THEME, DemoPrefs.FILE_PREFIX + themeInfo.themeFile ); + if( themeInfo.themeFile.getName().endsWith( ".properties" ) ) { + FlatLaf.install( new PropertiesLaf( themeInfo.name, themeInfo.themeFile ) ); + } else + FlatLaf.install( IntelliJTheme.createLaf( new FileInputStream( themeInfo.themeFile ) ) ); + + DemoPrefs.getState().put( DemoPrefs.KEY_LAF_THEME, DemoPrefs.FILE_PREFIX + themeInfo.themeFile ); } catch( Exception ex ) { ex.printStackTrace(); showInformationDialog( "Failed to load '" + themeInfo.themeFile + "'.", ex ); } } else { IntelliJTheme.install( getClass().getResourceAsStream( themeInfo.resourceName ) ); - DemoPrefs.getState().put( DemoPrefs.KEY_LAF_INTELLIJ_THEME, DemoPrefs.RESOURCE_PREFIX + themeInfo.resourceName ); + DemoPrefs.getState().put( DemoPrefs.KEY_LAF_THEME, DemoPrefs.RESOURCE_PREFIX + themeInfo.resourceName ); } // update all components @@ -331,17 +338,17 @@ public class IJThemesPanel private void selectedCurrentLookAndFeel() { LookAndFeel lookAndFeel = UIManager.getLookAndFeel(); - String intelliJTheme = UIManager.getLookAndFeelDefaults().getString( DemoPrefs.INTELLIJ_THEME_UI_KEY ); + String theme = UIManager.getLookAndFeelDefaults().getString( DemoPrefs.THEME_UI_KEY ); - if( intelliJTheme == null && lookAndFeel instanceof IntelliJTheme.ThemeLaf ) + if( theme == null && (lookAndFeel instanceof IntelliJTheme.ThemeLaf || lookAndFeel instanceof PropertiesLaf) ) return; Predicate test; - if( intelliJTheme != null && intelliJTheme.startsWith( DemoPrefs.RESOURCE_PREFIX ) ) { - String resourceName = intelliJTheme.substring( DemoPrefs.RESOURCE_PREFIX.length() ); + if( theme != null && theme.startsWith( DemoPrefs.RESOURCE_PREFIX ) ) { + String resourceName = theme.substring( DemoPrefs.RESOURCE_PREFIX.length() ); test = ti -> Objects.equals( ti.resourceName, resourceName ); - } else if( intelliJTheme != null && intelliJTheme.startsWith( DemoPrefs.FILE_PREFIX ) ) { - File themeFile = new File( intelliJTheme.substring( DemoPrefs.FILE_PREFIX.length() ) ); + } else if( theme != null && theme.startsWith( DemoPrefs.FILE_PREFIX ) ) { + File themeFile = new File( theme.substring( DemoPrefs.FILE_PREFIX.length() ) ); test = ti -> Objects.equals( ti.themeFile, themeFile ); } else { String lafClassName = lookAndFeel.getClass().getName(); @@ -356,11 +363,13 @@ public class IJThemesPanel } } + isAdjustingThemesList = true; if( newSel >= 0 ) { if( newSel != themesList.getSelectedIndex() ) themesList.setSelectedIndex( newSel ); } else themesList.clearSelection(); + isAdjustingThemesList = false; } private void initComponents() { @@ -420,4 +429,78 @@ public class IJThemesPanel private JScrollPane themesScrollPane; private JList themesList; // JFormDesigner - End of variables declaration //GEN-END:variables + + //---- class PropertiesLaf ------------------------------------------------ + + public static class PropertiesLaf + extends FlatLaf + { + private final String name; + private final String baseTheme; + private final boolean dark; + private final Properties properties; + + public PropertiesLaf( String name, File propertiesFile ) + throws IOException + { + this.name = name; + + properties = new Properties(); + try( InputStream in = new FileInputStream( propertiesFile ) ) { + if( in != null ) + properties.load( in ); + } + + baseTheme = properties.getProperty( "@baseTheme", "light" ); + dark = "dark".equalsIgnoreCase( baseTheme ) || "darcula".equalsIgnoreCase( baseTheme ); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return name; + } + + @Override + public boolean isDark() { + return dark; + } + + @Override + protected ArrayList> getLafClassesForDefaultsLoading() { + ArrayList> lafClasses = new ArrayList<>(); + lafClasses.add( FlatLaf.class ); + switch( baseTheme.toLowerCase() ) { + default: + case "light": + lafClasses.add( FlatLightLaf.class ); + break; + + case "dark": + lafClasses.add( FlatDarkLaf.class ); + break; + + case "intellij": + lafClasses.add( FlatLightLaf.class ); + lafClasses.add( FlatIntelliJLaf.class ); + break; + + case "darcula": + lafClasses.add( FlatDarkLaf.class ); + lafClasses.add( FlatDarculaLaf.class ); + break; + } + lafClasses.add( PropertiesLaf.class ); + return lafClasses; + } + + @Override + protected Properties getAdditionalDefaults() { + return properties; + } + } }