Compare commits

...

11 Commits
0.29 ... 0.30

17 changed files with 1023 additions and 9 deletions

View File

@@ -1,6 +1,12 @@
FlatLaf Change Log
==================
## 0.30
- Windows: Fixed rendering of Unicode characters. Previously not all Unicode
characters were rendered on Windows. (issue #81)
## 0.29
- Linux: Fixed scaling if `GDK_SCALE` environment variable is set or if running

View File

@@ -14,8 +14,8 @@
* limitations under the License.
*/
val releaseVersion = "0.29"
val developmentVersion = "0.30-SNAPSHOT"
val releaseVersion = "0.30"
val developmentVersion = "0.31-SNAPSHOT"
version = if( java.lang.Boolean.getBoolean( "release" ) ) releaseVersion else developmentVersion

View File

@@ -54,6 +54,7 @@ import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicLookAndFeel;
import javax.swing.text.StyleContext;
import javax.swing.text.html.HTMLEditorKit;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -324,7 +325,7 @@ public abstract class FlatLaf
if( SystemInfo.IS_WINDOWS ) {
Font winFont = (Font) Toolkit.getDefaultToolkit().getDesktopProperty( "win.messagebox.font" );
if( winFont != null )
uiFont = new FontUIResource( winFont );
uiFont = createCompositeFont( winFont.getFamily(), winFont.getStyle(), winFont.getSize() );
} else if( SystemInfo.IS_MAC ) {
String fontName;
@@ -335,7 +336,8 @@ public abstract class FlatLaf
// default font on older systems (see com.apple.laf.AquaFonts)
fontName = "Lucida Grande";
}
uiFont = new FontUIResource( fontName, Font.PLAIN, 13 );
uiFont = createCompositeFont( fontName, Font.PLAIN, 13 );
} else if( SystemInfo.IS_LINUX ) {
Font font = LinuxFontPolicy.getFont();
@@ -343,7 +345,7 @@ public abstract class FlatLaf
}
if( uiFont == null )
return;
uiFont = createCompositeFont( Font.SANS_SERIF, Font.PLAIN, 12 );
uiFont = UIScale.applyCustomScaleFactor( uiFont );
@@ -365,6 +367,14 @@ public abstract class FlatLaf
defaults.put( "defaultFont", uiFont );
}
static FontUIResource createCompositeFont( String family, int style, int size ) {
// using StyleContext.getFont() here because it uses
// sun.font.FontUtilities.getCompositeFontUIResource()
// and creates a composite font that is able to display all Unicode characters
Font font = new StyleContext().getFont( family, style, size );
return (font instanceof FontUIResource) ? (FontUIResource) font : new FontUIResource( font );
}
/**
* Adds the default color palette for action icons and object icons to the given UIDefaults.
* <p>

View File

@@ -29,7 +29,6 @@ import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import javax.swing.text.StyleContext;
import com.formdev.flatlaf.util.StringUtils;
import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale;
@@ -90,9 +89,7 @@ class LinuxFontPolicy
}
private static Font createFont( String family, int style, int size, double dsize ) {
// using StyleContext.getFont() here because it uses
// sun.font.FontUtilities.getCompositeFontUIResource()
Font font = new StyleContext().getFont( family, style, size );
Font font = FlatLaf.createCompositeFont( family, style, size );
// set font size in floating points
font = font.deriveFont( style, (float) dsize );

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2020 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
*
* https://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.
*/
plugins {
`java-library`
}
dependencies {
implementation( project( ":flatlaf-core" ) )
implementation( "com.fifesoft:rsyntaxtextarea:3.1.0" )
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2020 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
*
* https://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;
/**
* Enable accessing package private methods of {@link UIDefaultsLoader}.
*
* @author Karl Tauber
*/
public class UIDefaultsLoaderAccessor
{
public static int parseColorRGBA( String value ) {
return UIDefaultsLoader.parseColorRGBA( value );
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2020 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
*
* https://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.themeeditor;
import org.fife.ui.rsyntaxtextarea.OccurrenceMarker;
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaHighlighter;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rsyntaxtextarea.TokenImpl;
import org.fife.ui.rtextarea.SmartHighlightPainter;
/**
* Delegating occurrence marker that does not mark token at caret if it does
* not occur elsewhere.
*
* @author Karl Tauber
*/
class FlatOccurrenceMarker
implements OccurrenceMarker
{
private final OccurrenceMarker delegate;
FlatOccurrenceMarker( OccurrenceMarker delegate ) {
this.delegate = delegate;
}
@Override
public Token getTokenToMark( RSyntaxTextArea textArea ) {
return delegate.getTokenToMark( textArea );
}
@Override
public boolean isValidType( RSyntaxTextArea textArea, Token t ) {
return delegate.isValidType( textArea, t );
}
@Override
public void markOccurrences( RSyntaxDocument doc, Token t, RSyntaxTextAreaHighlighter h, SmartHighlightPainter p ) {
char[] lexeme = t.getLexeme().toCharArray();
int type = t.getType();
int lineCount = doc.getDefaultRootElement().getElementCount();
// make a copy of the token because it is overwritten in getTokenListForLine()
Token t2 = new TokenImpl( t );
// check whether token occurres more than once
boolean mark = false;
for( int i = 0; i < lineCount && !mark; i++ ) {
Token temp = doc.getTokenListForLine( i );
while( temp != null && temp.isPaintable() ) {
if( temp.is( type, lexeme ) && temp.getOffset() != t2.getOffset() ) {
mark = true;
break;
}
temp = temp.getNextToken();
}
}
if( mark )
delegate.markOccurrences( doc, t2, h, p );
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2020 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
*
* https://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.themeeditor;
import java.awt.Color;
import java.util.HashMap;
import java.util.Map;
import javax.swing.text.BadLocationException;
import org.fife.ui.rsyntaxtextarea.TextEditorPane;
import org.fife.ui.rsyntaxtextarea.Token;
import com.formdev.flatlaf.UIDefaultsLoaderAccessor;
/**
* A text area that supports editing FlatLaf themes.
*
* @author Karl Tauber
*/
class FlatSyntaxTextArea
extends TextEditorPane
{
private boolean useColorOfColorTokens;
private final Map<String, Color> parsedColorsMap = new HashMap<>();
FlatSyntaxTextArea() {
}
boolean isUseColorOfColorTokens() {
return useColorOfColorTokens;
}
void setUseColorOfColorTokens( boolean useColorOfColorTokens ) {
this.useColorOfColorTokens = useColorOfColorTokens;
setHighlightCurrentLine( !useColorOfColorTokens );
}
@Override
public Color getBackgroundForToken( Token t ) {
if( useColorOfColorTokens && t.getType() == FlatThemeTokenMaker.TOKEN_COLOR ) {
Color color = parseColor( t );
if( color != null )
return color;
}
return super.getBackgroundForToken( t );
}
@Override
public Color getForegroundForToken( Token t ) {
if( useColorOfColorTokens && t.getType() == FlatThemeTokenMaker.TOKEN_COLOR && !isCurrentLineHighlighted( t.getOffset() )) {
Color color = parseColor( t );
if( color != null ) {
return (colorLuminance( color ) > 164 || color.getAlpha() < 96)
? Color.black
: Color.white;
}
}
return super.getForegroundForToken( t );
}
private Color parseColor( Token token ) {
return parsedColorsMap.computeIfAbsent( token.getLexeme(), s -> {
try {
return new Color( UIDefaultsLoaderAccessor.parseColorRGBA( s ), true );
} catch( IllegalArgumentException ex ) {
return null;
}
} );
}
private int colorLuminance( Color c ) {
int red = c.getRed();
int green = c.getGreen();
int blue = c.getBlue();
int min = Math.min( red, Math.min( green, blue ) );
int max = Math.max( red, Math.max( green, blue ) );
return (max + min) / 2;
}
private boolean isCurrentLineHighlighted( int offset ) {
try {
return getHighlightCurrentLine() &&
getSelectionStart() == getSelectionEnd() &&
getLineOfOffset( offset ) == getLineOfOffset( getSelectionStart() );
} catch( BadLocationException ex ) {
return false;
}
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright 2020 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
*
* https://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.themeeditor;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import javax.swing.JComponent;
import javax.swing.JLayer;
import javax.swing.plaf.LayerUI;
import javax.swing.text.BadLocationException;
import org.fife.ui.rsyntaxtextarea.Token;
import com.formdev.flatlaf.UIDefaultsLoaderAccessor;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.HSLColor;
import com.formdev.flatlaf.util.UIScale;
/**
* An overlay layer that paints additional information about line content on the right side.
*
* @author Karl Tauber
*/
class FlatThemeEditorOverlay
extends LayerUI<FlatSyntaxTextArea>
{
private static final int COLOR_PREVIEW_WIDTH = 100;
private Font font;
private Font baseFont;
@Override
public void paint( Graphics g, JComponent c ) {
// paint the syntax text area
super.paint( g, c );
@SuppressWarnings( "unchecked" )
FlatSyntaxTextArea textArea = ((JLayer<FlatSyntaxTextArea>)c).getView();
Rectangle clipBounds = g.getClipBounds();
// determine first and last visible lines
int firstVisibleLine;
int lastVisibleLine;
try {
int startOffset = textArea.viewToModel( new Point( 0, clipBounds.y ) );
int endOffset = textArea.viewToModel( new Point( 0, clipBounds.y + clipBounds.height ) );
firstVisibleLine = textArea.getLineOfOffset( startOffset );
lastVisibleLine = textArea.getLineOfOffset( endOffset );
} catch( BadLocationException ex ) {
// ignore
return;
}
// compute font (and cache it)
if( baseFont != textArea.getFont() ) {
baseFont = textArea.getFont();
int fontSize = Math.max( (int) Math.round( baseFont.getSize() * 0.8 ), 8 );
font = baseFont.deriveFont( (float) fontSize );
}
FontMetrics fm = c.getFontMetrics( font );
int maxTextWidth = fm.stringWidth( "HSL 360 100 100" );
int textHeight = fm.getAscent() - fm.getLeading();
int width = c.getWidth();
int previewWidth = UIScale.scale( COLOR_PREVIEW_WIDTH );
int gap = UIScale.scale( 4 );
// check whether preview is outside of clip bounds
if( clipBounds.x + clipBounds.width < width - previewWidth - maxTextWidth - gap )
return;
g.setFont( font );
// paint additional information
for( int line = firstVisibleLine; line <= lastVisibleLine; line++ ) {
Color color = getColorInLine( textArea, line );
if( color == null )
continue;
try {
// paint color preview
int lineEndOffset = textArea.getLineEndOffset( line );
Rectangle r = textArea.modelToView( lineEndOffset - 1 );
int pw = Math.min( width - r.x - gap, previewWidth );
int px = width - pw;
g.setColor( color );
g.fillRect( px, r.y, pw, r.height );
// paint text
int textX = px - maxTextWidth;
if( textX > r.x + gap) {
float[] hsl = HSLColor.fromRGB( color );
String hslStr = String.format( "HSL %d %d %d",
Math.round( hsl[0] ), Math.round( hsl[1] ), Math.round( hsl[2] ) );
g.setColor( textArea.getForeground() );
FlatUIUtils.drawString( textArea, g, hslStr, textX,
r.y + ((r.height - textHeight) / 2) + textHeight );
}
} catch( BadLocationException ex ) {
// ignore
}
}
}
private Color getColorInLine( FlatSyntaxTextArea textArea, int line ) {
Token token = textArea.getTokenListForLine( line );
for( Token t = token; t != null && t.isPaintable(); t = t.getNextToken() ) {
if( t.getType() == FlatThemeTokenMaker.TOKEN_COLOR ) {
try {
return new Color( UIDefaultsLoaderAccessor.parseColorRGBA( t.getLexeme() ), true );
} catch( IllegalArgumentException ex ) {
break;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2020 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
*
* https://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.themeeditor;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.swing.JLayer;
import javax.swing.JPanel;
import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory;
import org.fife.ui.rsyntaxtextarea.FileLocation;
import org.fife.ui.rsyntaxtextarea.SyntaxScheme;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
import org.fife.ui.rtextarea.RTextScrollPane;
import com.formdev.flatlaf.util.UIScale;
/**
* A pane that supports editing FlatLaf themes.
*
* @author Karl Tauber
*/
class FlatThemeEditorPane
extends JPanel
{
private static final String FLATLAF_STYLE = "text/flatlaf";
private final RTextScrollPane scrollPane;
private final FlatSyntaxTextArea textArea;
FlatThemeEditorPane() {
super( new BorderLayout() );
// register FlatLaf token maker
AbstractTokenMakerFactory tmf = (AbstractTokenMakerFactory) TokenMakerFactory.getDefaultInstance();
tmf.putMapping( FLATLAF_STYLE, FlatThemeTokenMaker.class.getName() );
// create text area
textArea = new FlatSyntaxTextArea();
textArea.setSyntaxEditingStyle( FLATLAF_STYLE );
textArea.setMarkOccurrences( true );
textArea.addParser( new FlatThemeParser() );
// textArea.setUseColorOfColorTokens( true );
// theme
try {
Theme theme = Theme.load( getClass().getResourceAsStream( "light.xml" ) );
theme.apply( textArea );
} catch( IOException ex ) {
ex.printStackTrace();
}
// use semitransparent token background because token background
// is painted over mark occurrences background
SyntaxScheme scheme = textArea.getSyntaxScheme();
scheme.getStyle( FlatThemeTokenMaker.TOKEN_COLOR ).background = new Color( 0x0a000000, true );
scheme.getStyle( FlatThemeTokenMaker.TOKEN_VARIABLE ).background = new Color( 0x1800cc00, true );
// create overlay layer
JLayer<FlatSyntaxTextArea> overlay = new JLayer<>( textArea, new FlatThemeEditorOverlay() );
// create scroll pane
scrollPane = new RTextScrollPane( overlay );
scrollPane.setLineNumbersEnabled( true );
// scale fonts
if( UIScale.getUserScaleFactor() != 1 )
textArea.setFont( scaleFont( textArea.getFont() ) );
// use same font for line numbers as in editor
scrollPane.getGutter().setLineNumberFont( textArea.getFont() );
add( scrollPane, BorderLayout.CENTER );
}
private static Font scaleFont( Font font ) {
int newFontSize = UIScale.scale( font.getSize() );
return font.deriveFont( (float) newFontSize );
}
public void load( FileLocation loc ) throws IOException {
textArea.load( loc, StandardCharsets.ISO_8859_1 );
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2020 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
*
* https://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.themeeditor;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import javax.swing.*;
import org.fife.ui.rsyntaxtextarea.FileLocation;
import com.formdev.flatlaf.FlatLightLaf;
import com.formdev.flatlaf.util.UIScale;
/**
* TODO
*
* @author Karl Tauber
*/
public class FlatThemeFileEditor
extends JFrame
{
public static void main( String[] args ) {
File file = new File( args.length > 0
? args[0]
: "theme-editor-test.properties" ); // TODO
SwingUtilities.invokeLater( () -> {
FlatLightLaf.install();
FlatThemeFileEditor frame = new FlatThemeFileEditor();
try {
frame.themeEditorArea.load( FileLocation.create( file ) );
} catch( IOException ex ) {
ex.printStackTrace();
}
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
frame.setSize( Math.min( UIScale.scale( 800 ), screenSize.width ),
screenSize.height - UIScale.scale( 100 ) );
frame.setLocationRelativeTo( null );
frame.setVisible( true );
} );
}
public FlatThemeFileEditor() {
initComponents();
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
dialogPane = new JPanel();
themeEditorArea = new FlatThemeEditorPane();
//======== this ========
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setTitle("FlatLaf Theme Editor");
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
//======== dialogPane ========
{
dialogPane.setLayout(new BorderLayout());
dialogPane.add(themeEditorArea, BorderLayout.CENTER);
}
contentPane.add(dialogPane, BorderLayout.CENTER);
// JFormDesigner - End of component initialization //GEN-END:initComponents
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JPanel dialogPane;
private FlatThemeEditorPane themeEditorArea;
// JFormDesigner - End of variables declaration //GEN-END:variables
}

View File

@@ -0,0 +1,27 @@
JFDML JFormDesigner: "7.0.1.0.272" Java: "13.0.1" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
root: new FormRoot {
add( new FormWindow( "javax.swing.JFrame", new FormLayoutManager( class java.awt.BorderLayout ) ) {
name: "this"
"$locationPolicy": 2
"$sizePolicy": 2
"defaultCloseOperation": 2
"title": "FlatLaf Theme Editor"
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class java.awt.BorderLayout ) ) {
name: "dialogPane"
add( new FormComponent( "com.formdev.flatlaf.themeeditor.FlatThemeEditorPane" ) {
name: "themeEditorArea"
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "Center"
} )
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "Center"
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 535, 300 )
} )
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2020 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
*
* https://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.themeeditor;
import javax.swing.text.Element;
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rsyntaxtextarea.parser.AbstractParser;
import org.fife.ui.rsyntaxtextarea.parser.DefaultParseResult;
import org.fife.ui.rsyntaxtextarea.parser.DefaultParserNotice;
import org.fife.ui.rsyntaxtextarea.parser.ParseResult;
import com.formdev.flatlaf.UIDefaultsLoaderAccessor;
/**
* Parser for FlatLaf properties files that checks for invalid values.
*
* @author Karl Tauber
*/
class FlatThemeParser
extends AbstractParser
{
private final DefaultParseResult result;
FlatThemeParser() {
result = new DefaultParseResult( this );
}
@Override
public ParseResult parse( RSyntaxDocument doc, String style ) {
Element root = doc.getDefaultRootElement();
result.clearNotices();
result.setParsedLines( 0, root.getElementCount() - 1 );
for( Token token : doc ) {
if( token.getType() == FlatThemeTokenMaker.TOKEN_COLOR ) {
try {
UIDefaultsLoaderAccessor.parseColorRGBA( token.getLexeme() );
} catch( IllegalArgumentException ex ) {
result.addNotice( new DefaultParserNotice( this,
"Invalid color.\n\nUse #RRGGBB, #RRGGBBAA, #RGB or #RGBA",
root.getElementIndex( token.getOffset() ),
token.getOffset(), token.length() ) );
}
}
}
return result;
}
}

View File

@@ -0,0 +1,228 @@
/*
* Copyright 2020 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
*
* https://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.themeeditor;
import org.fife.ui.rsyntaxtextarea.OccurrenceMarker;
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rsyntaxtextarea.TokenMap;
import org.fife.ui.rsyntaxtextarea.TokenTypes;
import org.fife.ui.rsyntaxtextarea.modes.PropertiesFileTokenMaker;
/**
* Token maker for FlatLaf properties files.
* <p>
* Lets the super class parse the properties file and modify the added tokens.
* The super class uses {@link TokenTypes#RESERVED_WORD} for property keys and
* {@link TokenTypes#LITERAL_STRING_DOUBLE_QUOTE} for property values.
*
* @author Karl Tauber
*/
public class FlatThemeTokenMaker
extends PropertiesFileTokenMaker
{
static final int TOKEN_PROPERTY = Token.IDENTIFIER;
static final int TOKEN_VARIABLE = Token.VARIABLE;
static final int TOKEN_NUMBER = Token.LITERAL_NUMBER_DECIMAL_INT;
static final int TOKEN_COLOR = Token.LITERAL_NUMBER_HEXADECIMAL;
static final int TOKEN_STRING = Token.LITERAL_STRING_DOUBLE_QUOTE;
static final int TOKEN_FUNCTION = Token.FUNCTION;
static final int TOKEN_TYPE = Token.DATA_TYPE;
private final TokenMap tokenMap = new TokenMap();
public FlatThemeTokenMaker() {
// null, false, true
tokenMap.put( "null", Token.RESERVED_WORD );
tokenMap.put( "false", Token.LITERAL_BOOLEAN );
tokenMap.put( "true", Token.LITERAL_BOOLEAN );
// functions
tokenMap.put( "rgb", TOKEN_FUNCTION );
tokenMap.put( "rgba", TOKEN_FUNCTION );
tokenMap.put( "hsl", TOKEN_FUNCTION );
tokenMap.put( "hsla", TOKEN_FUNCTION );
tokenMap.put( "lighten", TOKEN_FUNCTION );
tokenMap.put( "darken", TOKEN_FUNCTION );
tokenMap.put( "lazy", TOKEN_FUNCTION );
// function options
tokenMap.put( "relative", Token.RESERVED_WORD );
tokenMap.put( "autoInverse", Token.RESERVED_WORD );
}
/**
* This method is only invoked from the super class.
*/
@Override
public void addToken( char[] array, int start, int end, int tokenType, int startOffset, boolean hyperlink ) {
// debugInputToken( array, start, end, tokenType, startOffset, hyperlink );
// ignore invalid token
if( end < start )
return;
if( tokenType == Token.RESERVED_WORD ) {
// key
int newTokenType = (array[start] == '@') ? TOKEN_VARIABLE : TOKEN_PROPERTY;
super.addToken( array, start, end, newTokenType, startOffset, hyperlink );
} else if( tokenType == Token.LITERAL_STRING_DOUBLE_QUOTE ) {
// value
tokenizeValue( array, start, end, startOffset );
} else if( tokenType == Token.VARIABLE ) {
// '{variable}'
super.addToken( array, start, end, TOKEN_TYPE, startOffset, hyperlink );
} else {
// comments or operators
super.addToken( array, start, end, tokenType, startOffset, hyperlink );
}
}
private void tokenizeValue( char[] array, int start, int end, int startOffset ) {
int newStartOffset = startOffset - start;
int currentTokenStart = start;
int currentTokenType = Token.NULL;
int parenthesisLevel = 0;
for( int i = start; i <= end; i++ ) {
int newTokenType;
char ch = array[i];
if( ch <= ' ' )
newTokenType = Token.WHITESPACE;
else if( ch == '#' || (currentTokenType == TOKEN_COLOR && RSyntaxUtilities.isHexCharacter( ch )) )
newTokenType = TOKEN_COLOR;
else if( ch == '$' || (currentTokenType == TOKEN_PROPERTY && isPropertyChar( ch )) )
newTokenType = TOKEN_PROPERTY;
else if( ch == '@' || (currentTokenType == TOKEN_VARIABLE && isPropertyChar( ch )) )
newTokenType = TOKEN_VARIABLE;
else if( currentTokenType != TOKEN_STRING && (RSyntaxUtilities.isDigit( ch ) || (currentTokenType == TOKEN_NUMBER && ch == '.')) )
newTokenType = TOKEN_NUMBER;
else if( ch == ',' || ch == '(' || ch == ')' || ch == '"' || ch == '%' )
newTokenType = TokenTypes.OPERATOR;
else
newTokenType = TOKEN_STRING;
if( currentTokenType == Token.NULL )
currentTokenType = newTokenType;
else if( newTokenType != currentTokenType ) {
addTokenImpl( array, currentTokenStart, i - 1, currentTokenType, newStartOffset + currentTokenStart, parenthesisLevel );
currentTokenType = newTokenType;
currentTokenStart = i;
}
if( ch == '(' )
parenthesisLevel++;
else if( ch == ')' )
parenthesisLevel--;
}
if( currentTokenType != Token.NULL )
addTokenImpl( array, currentTokenStart, end, currentTokenType, newStartOffset + currentTokenStart, parenthesisLevel );
}
private void addTokenImpl( char[] array, int start, int end, int tokenType, int startOffset, int parenthesisLevel ) {
if( tokenType == TOKEN_PROPERTY && array[start] == '$' ) {
// separate '$' from property token for mark occurrences to work
super.addToken( array, start, start, TokenTypes.OPERATOR, startOffset, false );
start++;
startOffset++;
} else if( tokenType == TOKEN_STRING ) {
// check for reserved words, functions, etc
int type = tokenMap.get( array, start, end );
if( type != -1 )
tokenType = type;
else if( parenthesisLevel > 0 ) {
// assume property reference if in function parameters
tokenType = TOKEN_PROPERTY;
}
}
// debugOutputToken( array, start, end, tokenType );
super.addToken( array, start, end, tokenType, startOffset, false );
}
private boolean isPropertyChar( char ch ) {
return RSyntaxUtilities.isLetterOrDigit( ch ) || ch == '.' || ch == '_' || ch == '-';
}
@Override
protected OccurrenceMarker createOccurrenceMarker() {
return new FlatOccurrenceMarker( super.createOccurrenceMarker() );
}
@Override
public boolean getMarkOccurrencesOfTokenType( int type ) {
switch( type ) {
case TOKEN_PROPERTY:
case TOKEN_VARIABLE:
case TOKEN_COLOR:
case TOKEN_FUNCTION:
case TOKEN_TYPE:
return true;
default:
return false;
}
}
/*debug
private java.util.HashMap<Integer, String> tokenTypeStrMap;
private void debugInputToken( char[] array, int start, int end, int tokenType, int startOffset, boolean hyperlink ) {
if( tokenTypeStrMap == null ) {
tokenTypeStrMap = new java.util.HashMap<>();
for( java.lang.reflect.Field f : TokenTypes.class.getFields() ) {
try {
tokenTypeStrMap.put( (Integer) f.get( null ), f.getName() );
} catch( IllegalArgumentException | IllegalAccessException ex ) {
ex.printStackTrace();
}
}
}
String tokenTypeStr = tokenTypeStrMap.computeIfAbsent( tokenType, t -> {
return "(unknown " + t + ")";
} );
System.out.printf( "%d-%d (%d) %-30s '%s'\n",
start, end, end - start, tokenTypeStr, new String( array, start, end - start + 1 ) );
}
private void debugOutputToken( char[] array, int start, int end, int tokenType ) {
String tokenTypeStr = null;
switch( tokenType ) {
case TOKEN_PROPERTY: tokenTypeStr = "PROPERTY"; break;
case TOKEN_VARIABLE: tokenTypeStr = "VARIABLE"; break;
case TOKEN_NUMBER: tokenTypeStr = "NUMBER"; break;
case TOKEN_COLOR: tokenTypeStr = "COLOR"; break;
case TOKEN_STRING: tokenTypeStr = "STRING"; break;
case TOKEN_FUNCTION: tokenTypeStr = "FUNCTION"; break;
case TOKEN_TYPE: tokenTypeStr = "TYPE"; break;
case TokenTypes.OPERATOR: tokenTypeStr = "OPERATOR"; break;
case TokenTypes.WHITESPACE: tokenTypeStr = "WHITESPACE"; break;
case TokenTypes.LITERAL_BOOLEAN: tokenTypeStr = "BOOLEAN"; break;
case TokenTypes.RESERVED_WORD: tokenTypeStr = "RESERVED_WORD"; break;
default:
throw new IllegalArgumentException( String.valueOf( tokenType ) );
}
System.out.printf( " %d-%d (%d) %-15s '%s'\n",
start, end, end - start, tokenTypeStr, new String( array, start, end - start + 1 ) );
}
debug*/
}

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE RSyntaxTheme SYSTEM "theme.dtd">
<!--
FlatLaf Light theme.
See theme.dtd and org.fife.ui.rsyntaxtextarea.Theme for more information.
-->
<RSyntaxTheme version="1.0">
<!-- Omitting baseFont will use a system-appropriate monospaced. -->
<baseFont size="12"/>
<!-- General editor colors. -->
<background color="ffffff"/>
<caret color="000000"/>
<selection fg="default" useFG="false" bg="A6D2FF"/>
<currentLineHighlight color="FCFAED" fade="false"/>
<marginLine fg="b0b4b9"/>
<markAllHighlight color="ffc800"/>
<markOccurrencesHighlight color="d4d4d4" border="false"/>
<matchedBracket fg="000080" bg="eaeaff" highlightBoth="false" animate="true"/>
<hyperlinks fg="0000ff"/>
<secondaryLanguages>
<language index="1" bg="fff0cc"/>
<language index="2" bg="dafeda"/>
<language index="3" bg="ffe0f0"/>
</secondaryLanguages>
<!-- Gutter styling. -->
<gutterBorder color="dddddd"/>
<lineNumbers fg="787878"/>
<foldIndicator fg="808080" iconBg="ffffff"/>
<iconRowHeader activeLineRange="3399ff"/>
<!-- Syntax tokens. -->
<tokenStyles>
<style token="IDENTIFIER" fg="000000"/>
<style token="RESERVED_WORD" fg="871094"/>
<style token="RESERVED_WORD_2" fg="0000ff"/>
<style token="ANNOTATION" fg="808080"/>
<style token="COMMENT_DOCUMENTATION" fg="a40000" italic="true"/>
<style token="COMMENT_EOL" fg="8C8C8C" italic="true"/>
<style token="COMMENT_MULTILINE" fg="8C8C8C" italic="true"/>
<style token="COMMENT_KEYWORD" fg="ff9900"/>
<style token="COMMENT_MARKUP" fg="808080"/>
<style token="DATA_TYPE" fg="8C8C8C"/>
<style token="FUNCTION" fg="871094"/>
<style token="LITERAL_BOOLEAN" fg="871094"/>
<style token="LITERAL_NUMBER_DECIMAL_INT" fg="1750EB"/>
<style token="LITERAL_NUMBER_FLOAT" fg="1750EB"/>
<style token="LITERAL_NUMBER_HEXADECIMAL" fg="1750EB"/>
<style token="LITERAL_STRING_DOUBLE_QUOTE" fg="008000"/>
<style token="LITERAL_CHAR" fg="DC009C"/>
<style token="LITERAL_BACKQUOTE" fg="DC009C"/>
<style token="MARKUP_TAG_DELIMITER" fg="ff0000"/>
<style token="MARKUP_TAG_NAME" fg="0000ff"/>
<style token="MARKUP_TAG_ATTRIBUTE" fg="3f7f7f"/>
<style token="MARKUP_TAG_ATTRIBUTE_VALUE" fg="DC009C"/>
<style token="MARKUP_COMMENT" fg="006000" italic="true"/>
<style token="MARKUP_DTD" fg="ad8000"/>
<style token="MARKUP_PROCESSING_INSTRUCTION" fg="808080"/>
<style token="MARKUP_CDATA" fg="cc6600"/>
<style token="MARKUP_CDATA_DELIMITER" fg="008080"/>
<style token="MARKUP_ENTITY_REFERENCE" fg="008000"/>
<style token="OPERATOR" fg="000000"/>
<style token="PREPROCESSOR" fg="808080"/>
<style token="REGEX" fg="008040"/>
<style token="SEPARATOR" fg="ff0000"/>
<style token="VARIABLE" fg="000000"/>
<style token="WHITESPACE" fg="000000"/>
<style token="ERROR_IDENTIFIER" fg="000000" bg="ffcccc"/>
<style token="ERROR_NUMBER_FORMAT" fg="000000" bg="ffcccc"/>
<style token="ERROR_STRING_DOUBLE" fg="000000" bg="ffcccc"/>
<style token="ERROR_CHAR" fg="000000" bg="ffcccc"/>
</tokenStyles>
</RSyntaxTheme>

View File

@@ -0,0 +1,42 @@
#
# some comment
#
ButtonUI=com.formdev.flatlaf.ui.FlatButtonUI
CheckBoxUI = com.formdev.flatlaf.ui.FlatCheckBoxUI
Prop.string=some string
Prop.string2="some string 123 #f80"
Prop.char1=x
Prop.char2=\u2022
Prop.integer=123
Prop.float=456.123
Prop.color1=#f80
Prop.color2=#ff8800
Prop.color3=#f804
Prop.color4=#ff880044
Prop.color5=#12
Prop.color6=#12345
Prop.border=1,2,3,4
Prop.scaledInteger={scaledInteger}123
Prop.null=null
Prop.false=false
Prop.true=true
@var1=123
Prop.var=@var1
Prop.ref=$Prop.string
Prop.lazy=lazy(Prop.string)
Prop.colorFunc1=rgb(12,34,56)
Prop.colorFunc2=rgba(12,34,56,78)
Prop.colorFunc3=hsl(12,34,56)
Prop.colorFunc4=hsla(12,34,56,78)
Prop.colorFunc5=lighten(#fe1289,20%)
Prop.colorFunc6=darken(#fe1289,20%)
Prop.colorFunc7=lighten($Prop.colorFunc4,20%,relative autoInverse)
Prop.colorFunc8=lighten(Prop.colorFunc4,20%,lazy)

View File

@@ -22,6 +22,7 @@ include( "flatlaf-swingx" )
include( "flatlaf-jide-oss" )
include( "flatlaf-demo" )
include( "flatlaf-testing" )
include( "flatlaf-theme-editor" )
pluginManagement {
plugins {