Upload files to "src/main/java/com/formdev/flatlaf/intellijthemes"
Some checks failed
Build the Jar / build (push) Has been cancelled

This commit is contained in:
2026-01-15 12:11:19 -06:00
parent c9be279695
commit 42e51e6328
3 changed files with 699 additions and 0 deletions

View File

@@ -0,0 +1,555 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.intellijthemes;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.event.*;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatDarculaLaf;
import com.formdev.flatlaf.FlatDarkLaf;
import com.formdev.flatlaf.FlatIntelliJLaf;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf;
import com.formdev.flatlaf.FlatPropertiesLaf;
import com.formdev.flatlaf.IntelliJTheme;
import com.formdev.flatlaf.demo.DemoPrefs;
import com.formdev.flatlaf.extras.FlatAnimatedLafChange;
import com.formdev.flatlaf.extras.FlatSVGIcon;
import com.formdev.flatlaf.themes.*;
import com.formdev.flatlaf.ui.FlatListUI;
import com.formdev.flatlaf.util.LoggingFacade;
import net.miginfocom.swing.*;
/**
* @author Karl Tauber
*/
public class IJThemesPanel
extends JPanel
{
public static final String THEMES_PACKAGE = "/com/formdev/flatlaf/intellijthemes/themes/";
private final IJThemesManager themesManager = new IJThemesManager();
private final List<IJThemeInfo> themes = new ArrayList<>();
private final HashMap<Integer, String> categories = new HashMap<>();
private final PropertyChangeListener lafListener = this::lafChanged;
private final WindowListener windowListener = new WindowAdapter() {
@Override
public void windowActivated( WindowEvent e ) {
IJThemesPanel.this.windowActivated();
}
};
private Window window;
private boolean isAdjustingThemesList;
private long lastLafChangeTime = System.currentTimeMillis();
public IJThemesPanel() {
initComponents();
pluginButton.setEnabled( false );
sourceCodeButton.setEnabled( false );
// create renderer
themesList.setCellRenderer( new DefaultListCellRenderer() {
private int index;
private boolean isSelected;
private int titleHeight;
@Override
public Component getListCellRendererComponent( JList<?> list, Object value,
int index, boolean isSelected, boolean cellHasFocus )
{
this.index = index;
this.isSelected = isSelected;
this.titleHeight = 0;
String title = categories.get( index );
String name = ((IJThemeInfo)value).name;
int sep = name.indexOf( '/' );
if( sep >= 0 )
name = name.substring( sep + 1 ).trim();
JComponent c = (JComponent) super.getListCellRendererComponent( list, name, index, isSelected, cellHasFocus );
c.setToolTipText( buildToolTip( (IJThemeInfo) value ) );
if( title != null ) {
Border titledBorder = new ListCellTitledBorder( themesList, title );
c.setBorder( new CompoundBorder( titledBorder, c.getBorder() ) );
titleHeight = titledBorder.getBorderInsets( c ).top;
}
return c;
}
@Override
public boolean isOpaque() {
return !isSelectedTitle();
}
@Override
protected void paintComponent( Graphics g ) {
if( isSelectedTitle() ) {
g.setColor( getBackground() );
FlatListUI.paintCellSelection( themesList, g, index, 0, titleHeight, getWidth(), getHeight() - titleHeight );
}
super.paintComponent( g );
}
private boolean isSelectedTitle() {
return titleHeight > 0 && isSelected && UIManager.getLookAndFeel() instanceof FlatLaf;
}
private String buildToolTip( IJThemeInfo ti ) {
if( ti.themeFile != null )
return ti.themeFile.getPath();
if( ti.license == null )
return ti.name;
return "Name: " + ti.name
+ "\nLicense: " + ti.license
+ "\nSource Code: " + ti.sourceCodeUrl;
}
} );
updateThemesList();
}
private void updateThemesList() {
int filterLightDark = filterComboBox.getSelectedIndex();
boolean showLight = (filterLightDark != 2);
boolean showDark = (filterLightDark != 1);
// load theme infos
themesManager.loadBundledThemes();
themesManager.loadThemesFromDirectory();
// sort themes by name
Comparator<? super IJThemeInfo> comparator = (t1, t2) -> t1.name.compareToIgnoreCase( t2.name );
themesManager.bundledThemes.sort( comparator );
themesManager.moreThemes.sort( comparator );
// remember selection (must be invoked before clearing themes field)
IJThemeInfo oldSel = themesList.getSelectedValue();
themes.clear();
categories.clear();
// add core themes at beginning
categories.put( themes.size(), "Core Themes" );
if( showLight )
themes.add( new IJThemeInfo( "FlatLaf Light", false, FlatLightLaf.class.getName() ) );
if( showDark )
themes.add( new IJThemeInfo( "FlatLaf Dark", true, FlatDarkLaf.class.getName() ) );
if( showLight )
themes.add( new IJThemeInfo( "FlatLaf IntelliJ", false, FlatIntelliJLaf.class.getName() ) );
if( showDark )
themes.add( new IJThemeInfo( "FlatLaf Darcula", true, FlatDarculaLaf.class.getName() ) );
if( showLight )
themes.add( new IJThemeInfo( "FlatLaf macOS Light", false, FlatMacLightLaf.class.getName() ) );
if( showDark )
themes.add( new IJThemeInfo( "FlatLaf macOS Dark", true, FlatMacDarkLaf.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 ) {
boolean show = (showLight && !ti.dark) || (showDark && ti.dark);
if( show && !ti.name.contains( "/" ) )
themes.add( ti );
}
// add categorized bundled themes
String lastCategory = null;
for( IJThemeInfo ti : themesManager.bundledThemes ) {
boolean show = (showLight && !ti.dark) || (showDark && ti.dark);
int sep = ti.name.indexOf( '/' );
if( !show || sep < 0 )
continue;
String category = ti.name.substring( 0, sep ).trim();
if( !Objects.equals( lastCategory, category ) ) {
lastCategory = category;
categories.put( themes.size(), category );
}
themes.add( ti );
}
// fill themes list
themesList.setModel( new AbstractListModel<IJThemeInfo>() {
@Override
public int getSize() {
return themes.size();
}
@Override
public IJThemeInfo getElementAt( int index ) {
return themes.get( index );
}
} );
// restore selection
if( oldSel != null ) {
for( int i = 0; i < themes.size(); i++ ) {
IJThemeInfo theme = themes.get( i );
if( oldSel.name.equals( theme.name ) &&
Objects.equals( oldSel.themeFile, theme.themeFile ) &&
Objects.equals( oldSel.lafClassName, theme.lafClassName ) )
{
themesList.setSelectedIndex( i );
break;
}
}
// select first theme if none selected
if( themesList.getSelectedIndex() < 0 )
themesList.setSelectedIndex( 0 );
}
// scroll selection into visible area
int sel = themesList.getSelectedIndex();
if( sel >= 0 ) {
Rectangle bounds = themesList.getCellBounds( sel, sel );
if( bounds != null )
themesList.scrollRectToVisible( bounds );
}
}
public void selectPreviousTheme() {
int sel = themesList.getSelectedIndex();
if( sel > 0 )
themesList.setSelectedIndex( sel - 1 );
}
public void selectNextTheme() {
int sel = themesList.getSelectedIndex();
themesList.setSelectedIndex( sel + 1 );
}
private void themesListValueChanged( ListSelectionEvent e ) {
IJThemeInfo themeInfo = themesList.getSelectedValue();
pluginButton.setEnabled( themeInfo != null && themeInfo.pluginUrl != null );
sourceCodeButton.setEnabled( themeInfo != null && themeInfo.sourceCodePath != null );
if( e.getValueIsAdjusting() || isAdjustingThemesList )
return;
EventQueue.invokeLater( () -> {
setTheme( themeInfo, false );
} );
}
private void setTheme( IJThemeInfo themeInfo, boolean reload ) {
if( themeInfo == null )
return;
// change look and feel
if( themeInfo.lafClassName != null ) {
if( !reload && themeInfo.lafClassName.equals( UIManager.getLookAndFeel().getClass().getName() ) )
return;
if( !reload )
FlatAnimatedLafChange.showSnapshot();
try {
UIManager.setLookAndFeel( themeInfo.lafClassName );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
showInformationDialog( "Failed to create '" + themeInfo.lafClassName + "'.", ex );
}
} else if( themeInfo.themeFile != null ) {
if( !reload )
FlatAnimatedLafChange.showSnapshot();
try {
if( themeInfo.themeFile.getName().endsWith( ".properties" ) )
FlatLaf.setup( new FlatPropertiesLaf( themeInfo.name, themeInfo.themeFile ) );
else
FlatLaf.setup( IntelliJTheme.createLaf( new FileInputStream( themeInfo.themeFile ) ) );
DemoPrefs.getState().put( DemoPrefs.KEY_LAF_THEME_FILE, themeInfo.themeFile.getAbsolutePath() );
} catch( Exception ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
showInformationDialog( "Failed to load '" + themeInfo.themeFile + "'.", ex );
}
} else {
JOptionPane.showMessageDialog( SwingUtilities.windowForComponent( this ),
"Missing lafClassName for '" + themeInfo.name + "'",
"FlatLaf", JOptionPane.INFORMATION_MESSAGE );
return;
}
// update all components
FlatLaf.updateUI();
if( !reload )
FlatAnimatedLafChange.hideSnapshotWithAnimation();
}
private void browsePlugin() {
IJThemeInfo themeInfo = themesList.getSelectedValue();
if( themeInfo == null || themeInfo.pluginUrl == null )
return;
browse( themeInfo.pluginUrl );
}
private void browseSourceCode() {
IJThemeInfo themeInfo = themesList.getSelectedValue();
if( themeInfo == null || themeInfo.sourceCodeUrl == null )
return;
String themeUrl = themeInfo.sourceCodeUrl;
if( themeInfo.sourceCodePath != null )
themeUrl += '/' + themeInfo.sourceCodePath;
browse( themeUrl );
}
private void browse( String url ) {
url = url.replace( " ", "%20" );
try {
Desktop.getDesktop().browse( new URI( url ) );
} catch( IOException | URISyntaxException ex ) {
showInformationDialog( "Failed to browse '" + url + "'.", ex );
}
}
private void showInformationDialog( String message, Exception ex ) {
JOptionPane.showMessageDialog( SwingUtilities.windowForComponent( this ),
message + "\n\n" + ex.getMessage(),
"FlatLaf", JOptionPane.INFORMATION_MESSAGE );
}
@Override
public void addNotify() {
super.addNotify();
selectedCurrentLookAndFeel();
UIManager.addPropertyChangeListener( lafListener );
window = SwingUtilities.windowForComponent( this );
if( window != null )
window.addWindowListener( windowListener );
}
@Override
public void removeNotify() {
super.removeNotify();
UIManager.removePropertyChangeListener( lafListener );
if( window != null ) {
window.removeWindowListener( windowListener );
window = null;
}
}
private void lafChanged( PropertyChangeEvent e ) {
if( "lookAndFeel".equals( e.getPropertyName() ) ) {
// use invokeLater() because KEY_LAF_THEME_FILE is updated after this event
EventQueue.invokeLater( () -> {
selectedCurrentLookAndFeel();
lastLafChangeTime = System.currentTimeMillis();
} );
}
}
private void windowActivated() {
// refresh themes list on window activation
if( themesManager.hasThemesFromDirectoryChanged() )
updateThemesList();
else {
// check whether core .properties files of current Laf have changed
// in development environment since last Laf change and reload theme
LookAndFeel laf = UIManager.getLookAndFeel();
if( laf instanceof FlatLaf ) {
List<Class<?>> lafClasses = new ArrayList<>();
// same as in UIDefaultsLoader.getLafClassesForDefaultsLoading()
for( Class<?> lafClass = laf.getClass();
FlatLaf.class.isAssignableFrom( lafClass );
lafClass = lafClass.getSuperclass() )
{
lafClasses.add( 0, lafClass );
}
// same as in IntelliJTheme.ThemeLaf.getLafClassesForDefaultsLoading()
if( laf instanceof IntelliJTheme.ThemeLaf ) {
boolean dark = ((FlatLaf)laf).isDark();
lafClasses.add( 1, dark ? FlatDarkLaf.class : FlatLightLaf.class );
lafClasses.add( 2, dark ? FlatDarculaLaf.class : FlatIntelliJLaf.class );
}
boolean reload = false;
for( Class<?> lafClass : lafClasses ) {
String propertiesName = '/' + lafClass.getName().replace( '.', '/' ) + ".properties";
URL url = lafClass.getResource( propertiesName );
if( url != null && "file".equals( url.getProtocol() ) ) {
try {
File file = new File( url.toURI() );
if( file.lastModified() > lastLafChangeTime ) {
reload = true;
break;
}
} catch( URISyntaxException ex ) {
// ignore
}
}
}
if( reload )
setTheme( themesList.getSelectedValue(), true );
}
}
}
private void selectedCurrentLookAndFeel() {
Predicate<IJThemeInfo> test;
String lafClassName = UIManager.getLookAndFeel().getClass().getName();
if( FlatPropertiesLaf.class.getName().equals( lafClassName ) ||
IntelliJTheme.ThemeLaf.class.getName().equals( lafClassName ) )
{
String themeFileName = DemoPrefs.getState().get( DemoPrefs.KEY_LAF_THEME_FILE, "" );
if( themeFileName == null )
return;
File themeFile = new File( themeFileName );
test = ti -> Objects.equals( ti.themeFile, themeFile );
} else
test = ti -> Objects.equals( ti.lafClassName, lafClassName );
int newSel = -1;
for( int i = 0; i < themes.size(); i++ ) {
if( test.test( themes.get( i ) ) ) {
newSel = i;
break;
}
}
isAdjustingThemesList = true;
if( newSel >= 0 ) {
if( newSel != themesList.getSelectedIndex() )
themesList.setSelectedIndex( newSel );
} else
themesList.clearSelection();
isAdjustingThemesList = false;
}
private void filterChanged() {
updateThemesList();
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
JLabel themesLabel = new JLabel();
toolBar = new JToolBar();
pluginButton = new JButton();
sourceCodeButton = new JButton();
filterComboBox = new JComboBox<>();
themesScrollPane = new JScrollPane();
themesList = new JList<>();
//======== this ========
setLayout(new MigLayout(
"insets dialog,hidemode 3",
// columns
"[grow,fill]",
// rows
"[]3" +
"[grow,fill]"));
//---- themesLabel ----
themesLabel.setText("Themes:");
add(themesLabel, "cell 0 0");
//======== toolBar ========
{
toolBar.setFloatable(false);
//---- pluginButton ----
pluginButton.setToolTipText("Opens the IntelliJ plugin page of selected IntelliJ theme in the browser.");
pluginButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/plugin.svg"));
pluginButton.addActionListener(e -> browsePlugin());
toolBar.add(pluginButton);
//---- sourceCodeButton ----
sourceCodeButton.setToolTipText("Opens the source code repository of selected IntelliJ theme in the browser.");
sourceCodeButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/github.svg"));
sourceCodeButton.addActionListener(e -> browseSourceCode());
toolBar.add(sourceCodeButton);
}
add(toolBar, "cell 0 0,alignx right,growx 0");
//---- filterComboBox ----
filterComboBox.setModel(new DefaultComboBoxModel<>(new String[] {
"all",
"light",
"dark"
}));
filterComboBox.putClientProperty(FlatClientProperties.MINIMUM_WIDTH, 0);
filterComboBox.setFocusable(false);
filterComboBox.addActionListener(e -> filterChanged());
add(filterComboBox, "cell 0 0,alignx right,growx 0");
//======== themesScrollPane ========
{
//---- themesList ----
themesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
themesList.addListSelectionListener(e -> themesListValueChanged(e));
themesScrollPane.setViewportView(themesList);
}
add(themesScrollPane, "cell 0 1");
// JFormDesigner - End of component initialization //GEN-END:initComponents
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JToolBar toolBar;
private JButton pluginButton;
private JButton sourceCodeButton;
private JComboBox<String> filterComboBox;
private JScrollPane themesScrollPane;
private JList<IJThemeInfo> themesList;
// JFormDesigner - End of variables declaration //GEN-END:variables
}

View File

@@ -0,0 +1,71 @@
JFDML JFormDesigner: "8.3" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
root: new FormRoot {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets dialog,hidemode 3"
"$columnConstraints": "[grow,fill]"
"$rowConstraints": "[]3[grow,fill]"
} ) {
name: "this"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "themesLabel"
"text": "Themes:"
auxiliary() {
"JavaCodeGenerator.variableLocal": true
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
add( new FormContainer( "javax.swing.JToolBar", new FormLayoutManager( class javax.swing.JToolBar ) ) {
name: "toolBar"
"floatable": false
add( new FormComponent( "javax.swing.JButton" ) {
name: "pluginButton"
"toolTipText": "Opens the IntelliJ plugin page of selected IntelliJ theme in the browser."
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/plugin.svg" )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "browsePlugin", false ) )
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "sourceCodeButton"
"toolTipText": "Opens the source code repository of selected IntelliJ theme in the browser."
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/github.svg" )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "browseSourceCode", false ) )
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0,alignx right,growx 0"
} )
add( new FormComponent( "javax.swing.JComboBox" ) {
name: "filterComboBox"
"model": new javax.swing.DefaultComboBoxModel {
selectedItem: "all"
addElement( "all" )
addElement( "light" )
addElement( "dark" )
}
"$client.JComponent.minimumWidth": 0
"focusable": false
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "filterChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0,alignx right,growx 0"
} )
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
name: "themesScrollPane"
add( new FormComponent( "javax.swing.JList" ) {
name: "themesList"
"selectionMode": 0
auxiliary() {
"JavaCodeGenerator.typeParameters": "IJThemeInfo"
}
addEvent( new FormEvent( "javax.swing.event.ListSelectionListener", "valueChanged", "themesListValueChanged", true ) )
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1"
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 400, 300 )
} )
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2019 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.intellijthemes;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import com.formdev.flatlaf.util.LoggingFacade;
/**
* This tool updates all IntelliJ themes listed in themes.json by downloading
* them from the source code repositories.
*
* @author Karl Tauber
*/
public class IJThemesUpdater
{
public static void main( String[] args ) {
IJThemesManager themesManager = new IJThemesManager();
themesManager.loadBundledThemes();
for( IJThemeInfo ti : themesManager.bundledThemes ) {
if( ti.discontinued )
continue;
if( ti.sourceCodeUrl == null || ti.sourceCodePath == null ) {
System.out.println( " " + ti.name + " NOT downloaded. Needs manual update from release on JetBrains Plugin portal." );
continue;
}
String fromUrl = ti.sourceCodeUrl + "/" + ti.sourceCodePath;
if( fromUrl.contains( "github.com" ) )
fromUrl += "?raw=true";
else if( fromUrl.contains( "gitlab.com" ) )
fromUrl = fromUrl.replace( "/blob/", "/raw/" );
String toPath = "../flatlaf-intellij-themes/src/main/resources" + IJThemesPanel.THEMES_PACKAGE + ti.resourceName;
download( fromUrl, toPath );
}
}
private static void download( String fromUrl, String toPath ) {
System.out.println( "Download " + fromUrl );
Path out = new File( toPath ).toPath();
try {
URL url = new URL( fromUrl.replace( " ", "%20" ) );
URLConnection con = url.openConnection();
Files.copy( con.getInputStream(), out, StandardCopyOption.REPLACE_EXISTING );
} catch( IOException ex ) {
LoggingFacade.INSTANCE.logSevere( null, ex );
}
}
}