Compare commits

..

20 Commits
3.5.2 ... 3.5.4

Author SHA1 Message Date
Karl Tauber
3ba9fc6c1c release 3.5.4 2024-12-09 00:44:57 +01:00
Karl Tauber
0a9ecd66a9 Linux: fixed NPE when using FlatLaf window decorations and switching theme (issue #933; regression in 3.5.3)
caused by fix for #907; commit d471f08b15
2024-12-09 00:43:44 +01:00
Karl Tauber
6991d6729e Merge PR #931: Fixing NPE when using HTML text on a component with null font
(cherry picked from commit 41332de275)
2024-12-08 23:01:00 +01:00
Karl Tauber
1462636e97 release 3.5.3 2024-12-06 12:42:15 +01:00
Karl Tauber
7e59a7f4af FlatPropertiesLaf: support macOS themes as base themes 2024-12-04 23:56:01 +01:00
Karl Tauber
e9a21848bc ComboBox: do not paint arrow button background if it is hidden (issue #915) 2024-12-04 19:24:10 +01:00
Karl Tauber
1dcb251ecb FlatLaf window decorations: fixed sometimes broken window moving with SplitPane in window title area in "full window content" mode (issue #926) 2024-12-04 18:21:06 +01:00
Karl Tauber
3f33543cee Linux: fixed slightly different font size (or letter width) used to paint HTML text when default font family is _Cantarell_ (e.g. on Fedora) (issue #912)
the removed use of floating point font size is similar to what is done in JDK for GTK Look and Feel:
- https://bugs.openjdk.org/browse/JDK-6979979
- 306f12db9e
2024-12-04 17:18:33 +01:00
Karl Tauber
84bd2088f2 FlatSystemProperties: javadoc fixes 2024-12-04 13:06:37 +01:00
Karl Tauber
4f4a3132c5 UIScale:
- do not use "defaultFont" if current Laf is not FlatLaf
- support custom font size divider to calculate user scale factor from font size

(for special use in JFormDesigner)
2024-12-04 13:06:11 +01:00
Karl Tauber
e064c934cb Windows: fixed detection of Windows 11 if custom exe launcher does not specify Windows 10+ compatibility in application manifest (issue #916)
Windows binaries built and signed locally in clean workspace
2024-11-28 14:12:56 +01:00
Karl Tauber
16fc3cabf2 Popup: fixed NPE if GraphicsConfiguration is null on Windows (issue #921)
also check for null GraphicsConfiguration in other classes
2024-11-27 19:27:47 +01:00
Karl Tauber
7e002ff6c2 Theme Editor: fixed using color picker on secondary screen 2024-11-27 19:02:13 +01:00
Karl Tauber
323c0c62c3 update to Gradle 8.11.1 2024-11-20 20:11:34 +01:00
Karl Tauber
ff5bd301cc Popup: on Windows 10, fixed misplaced popup drop shadow (issue #911; regression in 3.5 since commit a311bac89b) 2024-11-19 23:25:50 +01:00
Karl Tauber
c37712b0f0 Windows: fixed wrong layout in maximized frame after changing screen scale factor (issue #904)
Windows binaries built and signed locally in clean workspace
2024-11-17 19:41:54 +01:00
Karl Tauber
ee9e238592 Windows: fixed possible deadlock with TabbedPane in window title area in "full window content" mode (issue #909) 2024-11-14 19:34:31 +01:00
Karl Tauber
da5d6fa157 update to Gradle 8.11 2024-11-14 18:39:37 +01:00
Karl Tauber
d471f08b15 Linux: fixed continuous cursor toggling between resize and standard cursor when resizing window with FlatLaf window decorations (issue #907) 2024-11-14 18:34:55 +01:00
Karl Tauber
b97424f767 HTML: fixed wrong rendering if HTML text contains <style> tag with attributes (e.g. <style type='text/css'>) (issue #905; regression in 3.5) 2024-11-10 13:28:02 +01:00
32 changed files with 620 additions and 96 deletions

View File

@@ -1,6 +1,45 @@
FlatLaf Change Log FlatLaf Change Log
================== ==================
## 3.5.4
#### Fixed bugs
- HTML: Fixed NPE when using HTML text on a component with `null` font. (issue
#930; PR #931; regression in 3.5)
- Linux: Fixed NPE when using FlatLaf window decorations and switching theme.
(issue #933; regression in 3.5.3)
## 3.5.3
#### Fixed bugs
- HTML: Fixed wrong rendering if HTML text contains `<style>` tag with
attributes (e.g. `<style type='text/css'>`). (issue #905; regression in 3.5.1)
- FlatLaf window decorations:
- Windows: Fixed possible deadlock with TabbedPane in window title area in
"full window content" mode. (issue #909)
- Windows: Fixed wrong layout in maximized frame after changing screen scale
factor. (issue #904)
- Linux: Fixed continuous cursor toggling between resize and standard cursor
when resizing window. (issue #907)
- Fixed sometimes broken window moving with SplitPane in window title area in
"full window content" mode. (issue #926)
- Popup: On Windows 10, fixed misplaced popup drop shadow. (issue #911;
regression in 3.5)
- Popup: Fixed NPE if `GraphicsConfiguration` is `null` on Windows. (issue #921)
- Theme Editor: Fixed using color picker on secondary screen.
- Fixed detection of Windows 11 if custom exe launcher does not specify Windows
10+ compatibility in application manifest. (issue #916)
- Linux: Fixed slightly different font size (or letter width) used to paint HTML
text when default font family is _Cantarell_ (e.g. on Fedora). (issue #912)
#### Other Changes
- Class `FlatPropertiesLaf` now supports FlatLaf macOS themes as base themes.
## 3.5.2 ## 3.5.2
#### Fixed bugs #### Fixed bugs

View File

@@ -23,13 +23,15 @@ import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
/** /**
* A Flat LaF that is able to load UI defaults from properties passed to the constructor. * A Flat LaF that is able to load UI defaults from properties passed to the constructor.
* <p> * <p>
* Specify the base theme in the properties with {@code @baseTheme=<baseTheme>}. * Specify the base theme in the properties with {@code @baseTheme=<baseTheme>}.
* Allowed values for {@code <baseTheme>} are {@code light} (the default), {@code dark}, * Allowed values for {@code <baseTheme>} are {@code light} (the default), {@code dark},
* {@code intellij} or {@code darcula}. * {@code intellij}, {@code darcula}, {@code maclight} or {@code macdark}.
* <p> * <p>
* The properties are applied after loading the base theme and may overwrite base properties. * The properties are applied after loading the base theme and may overwrite base properties.
* All features of FlatLaf properties files are available. * All features of FlatLaf properties files are available.
@@ -71,7 +73,8 @@ public class FlatPropertiesLaf
this.properties = properties; this.properties = properties;
baseTheme = properties.getProperty( "@baseTheme", "light" ); baseTheme = properties.getProperty( "@baseTheme", "light" );
dark = "dark".equalsIgnoreCase( baseTheme ) || "darcula".equalsIgnoreCase( baseTheme ); dark = "dark".equalsIgnoreCase( baseTheme ) || "darcula".equalsIgnoreCase( baseTheme ) ||
"macdark".equalsIgnoreCase( baseTheme );
} }
@Override @Override
@@ -116,6 +119,16 @@ public class FlatPropertiesLaf
lafClasses.add( FlatDarkLaf.class ); lafClasses.add( FlatDarkLaf.class );
lafClasses.add( FlatDarculaLaf.class ); lafClasses.add( FlatDarculaLaf.class );
break; break;
case "maclight":
lafClasses.add( FlatLightLaf.class );
lafClasses.add( FlatMacLightLaf.class );
break;
case "macdark":
lafClasses.add( FlatDarkLaf.class );
lafClasses.add( FlatMacDarkLaf.class );
break;
} }
return lafClasses; return lafClasses;
} }

View File

@@ -138,7 +138,7 @@ public interface FlatSystemProperties
/** /**
* Specifies whether native rounded popup borders should be used (if supported by operating system). * Specifies whether native rounded popup borders should be used (if supported by operating system).
* <p> * <p>
* (requires Window 11 or macOS) * (requires Windows 11 or macOS)
* <p> * <p>
* <strong>Allowed Values</strong> {@code false} and {@code true}<br> * <strong>Allowed Values</strong> {@code false} and {@code true}<br>
* <strong>Default</strong> {@code true}; except on macOS 14.4+ where it is {@code false} * <strong>Default</strong> {@code true}; except on macOS 14.4+ where it is {@code false}

View File

@@ -111,7 +111,7 @@ class LinuxFontPolicy
if( logicalFamily != null ) if( logicalFamily != null )
family = logicalFamily; family = logicalFamily;
return createFontEx( family, style, size, dsize ); return createFontEx( family, style, size );
} }
/** /**
@@ -121,9 +121,9 @@ class LinuxFontPolicy
* E.g. family 'URW Bookman Light' is not found, but 'URW Bookman' is found. * E.g. family 'URW Bookman Light' is not found, but 'URW Bookman' is found.
* If still not found, then font of family 'Dialog' is returned. * If still not found, then font of family 'Dialog' is returned.
*/ */
private static Font createFontEx( String family, int style, int size, double dsize ) { private static Font createFontEx( String family, int style, int size ) {
for(;;) { for(;;) {
Font font = createFont( family, style, size, dsize ); Font font = FlatLaf.createCompositeFont( family, style, size );
if( Font.DIALOG.equals( family ) ) if( Font.DIALOG.equals( family ) )
return font; return font;
@@ -135,7 +135,7 @@ class LinuxFontPolicy
// - character width is zero (e.g. font Cantarell; Fedora; Oracle Java 8) // - character width is zero (e.g. font Cantarell; Fedora; Oracle Java 8)
FontMetrics fm = StyleContext.getDefaultStyleContext().getFontMetrics( font ); FontMetrics fm = StyleContext.getDefaultStyleContext().getFontMetrics( font );
if( fm.getHeight() > size * 2 || fm.stringWidth( "a" ) == 0 ) if( fm.getHeight() > size * 2 || fm.stringWidth( "a" ) == 0 )
return createFont( Font.DIALOG, style, size, dsize ); return FlatLaf.createCompositeFont( Font.DIALOG, style, size );
return font; return font;
} }
@@ -143,7 +143,7 @@ class LinuxFontPolicy
// find last word in family // find last word in family
int index = family.lastIndexOf( ' ' ); int index = family.lastIndexOf( ' ' );
if( index < 0 ) if( index < 0 )
return createFont( Font.DIALOG, style, size, dsize ); return FlatLaf.createCompositeFont( Font.DIALOG, style, size );
// check whether last work contains some font weight (e.g. Ultra-Bold or Heavy) // check whether last work contains some font weight (e.g. Ultra-Bold or Heavy)
String lastWord = family.substring( index + 1 ).toLowerCase( Locale.ENGLISH ); String lastWord = family.substring( index + 1 ).toLowerCase( Locale.ENGLISH );
@@ -155,15 +155,6 @@ class LinuxFontPolicy
} }
} }
private static Font createFont( String family, int style, int size, double dsize ) {
Font font = FlatLaf.createCompositeFont( family, style, size );
// set font size in floating points
font = font.deriveFont( style, (float) dsize );
return font;
}
private static double getGnomeFontScale() { private static double getGnomeFontScale() {
// do not scale font here if JRE scales // do not scale font here if JRE scales
if( isSystemScaling() ) if( isSystemScaling() )
@@ -257,7 +248,7 @@ class LinuxFontPolicy
if( size < 1 ) if( size < 1 )
size = 1; size = 1;
return createFont( family, style, size, dsize ); return FlatLaf.createCompositeFont( family, style, size );
} }
@SuppressWarnings( "MixedMutabilityReturnType" ) // Error Prone @SuppressWarnings( "MixedMutabilityReturnType" ) // Error Prone

View File

@@ -585,7 +585,7 @@ public class FlatComboBoxUI
FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc ); FlatUIUtils.paintComponentBackground( g2, 0, 0, width, height, focusWidth, arc );
// paint arrow button background // paint arrow button background
if( enabled && !isCellRenderer ) { if( enabled && !isCellRenderer && arrowButton.isVisible() ) {
Color buttonColor = paintButton Color buttonColor = paintButton
? buttonEditableBackground ? buttonEditableBackground
: (buttonFocusedBackground != null || focusedBackground != null) && isPermanentFocusOwner( comboBox ) : (buttonFocusedBackground != null || focusedBackground != null) && isPermanentFocusOwner( comboBox )
@@ -612,7 +612,7 @@ public class FlatComboBoxUI
} }
// paint vertical line between value and arrow button // paint vertical line between value and arrow button
if( paintButton ) { if( paintButton && arrowButton.isVisible() ) {
Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor; Color separatorColor = enabled ? buttonSeparatorColor : buttonDisabledSeparatorColor;
if( separatorColor != null && buttonSeparatorWidth > 0 ) { if( separatorColor != null && buttonSeparatorWidth > 0 ) {
g2.setColor( separatorColor ); g2.setColor( separatorColor );

View File

@@ -17,12 +17,13 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Color; import java.awt.Color;
import java.awt.Font;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer;
import javax.swing.AbstractButton; import javax.swing.AbstractButton;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JLabel; import javax.swing.JLabel;
@@ -74,9 +75,9 @@ public class FlatHTML
for( int i = 1; i <= 7; i++ ) for( int i = 1; i <= 7; i++ )
System.out.println( i+": "+ styleSheet.getPointSize( i ) ); System.out.println( i+": "+ styleSheet.getPointSize( i ) );
debug*/ debug*/
int fontBaseSize = c.getFont().getSize(); Font font = c.getFont();
if( styleSheet.getPointSize( 7 ) != 36f || if( styleSheet.getPointSize( 7 ) != 36f ||
styleSheet.getPointSize( 4 ) == fontBaseSize ) font == null || styleSheet.getPointSize( 4 ) == font.getSize() )
return; return;
// check whether view uses "absolute-size" keywords (e.g. "x-large") for font-size // check whether view uses "absolute-size" keywords (e.g. "x-large") for font-size
@@ -93,23 +94,22 @@ debug*/
text = ((JToolTip)c).getTipText(); text = ((JToolTip)c).getTipText();
else else
return; return;
if( text == null ) if( text == null || !BasicHTML.isHTMLString( text ) )
return; return;
// BASE_SIZE rule is parsed in javax.swing.text.html.StyleSheet.addRule() // BASE_SIZE rule is parsed in javax.swing.text.html.StyleSheet.addRule()
String style = "<style>BASE_SIZE " + c.getFont().getSize() + "</style>"; String style = "<style>BASE_SIZE " + font.getSize() + "</style>";
String openTag = ""; String openTag = "";
String closeTag = ""; String closeTag = "";
String lowerText = text.toLowerCase( Locale.ENGLISH );
int headIndex; int headIndex;
int styleIndex; int styleIndex;
int insertIndex; int insertIndex;
if( (headIndex = lowerText.indexOf( "<head>" )) >= 0 ) { if( (headIndex = indexOfTag( text, "head", true )) >= 0 ) {
// there is a <head> tag --> insert after <head> tag // there is a <head> tag --> insert after <head> tag
insertIndex = headIndex + "<head>".length(); insertIndex = headIndex;
} else if( (styleIndex = lowerText.indexOf( "<style>" )) >= 0 ) { } else if( (styleIndex = indexOfTag( text, "style", false )) >= 0 ) {
// there is a <style> tag --> insert before <style> tag // there is a <style> tag --> insert before <style> tag
insertIndex = styleIndex; insertIndex = styleIndex;
} else { } else {
@@ -124,6 +124,41 @@ debug*/
+ text.substring( insertIndex ); + text.substring( insertIndex );
BasicHTML.updateRenderer( c, newText ); BasicHTML.updateRenderer( c, newText );
// for unit tests
if( testUpdateRenderer != null )
testUpdateRenderer.accept( c, newText );
}
// for unit tests
static BiConsumer<JComponent, String> testUpdateRenderer;
/**
* Returns start or end index of a HTML tag.
* Checks only for leading '<' character and (case-ignore) tag name.
*/
private static int indexOfTag( String html, String tag, boolean endIndex ) {
int tagLength = tag.length();
int maxLength = html.length() - tagLength - 2;
char lastTagChar = tag.charAt( tagLength - 1 );
for( int i = "<html>".length(); i < maxLength; i++ ) {
// check for leading '<' and last tag name character
if( html.charAt( i ) == '<' && Character.toLowerCase( html.charAt( i + tagLength ) ) == lastTagChar ) {
// compare tag characters from last to first
for( int j = tagLength - 2; j >= 0; j-- ) {
if( Character.toLowerCase( html.charAt( i + 1 + j ) ) != tag.charAt( j ) )
break; // not equal
if( j == 0 ) {
// tag found
return endIndex ? html.indexOf( '>', i + tagLength ) + 1 : i;
}
}
}
}
return -1;
} }
private static final Set<String> absoluteSizeKeywordsSet = new HashSet<>( Arrays.asList( private static final Set<String> absoluteSizeKeywordsSet = new HashSet<>( Arrays.asList(

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.GraphicsConfiguration;
import java.awt.Point; import java.awt.Point;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
@@ -96,7 +97,11 @@ class FlatNativeLinuxLibrary
} }
private static Point scale( Window window, Point pt ) { private static Point scale( Window window, Point pt ) {
AffineTransform transform = window.getGraphicsConfiguration().getDefaultTransform(); GraphicsConfiguration gc = window.getGraphicsConfiguration();
if( gc == null )
return pt;
AffineTransform transform = gc.getDefaultTransform();
int x = (int) Math.round( pt.x * transform.getScaleX() ); int x = (int) Math.round( pt.x * transform.getScaleX() );
int y = (int) Math.round( pt.y * transform.getScaleY() ); int y = (int) Math.round( pt.y * transform.getScaleY() );
return new Point( x, y ); return new Point( x, y );

View File

@@ -138,7 +138,8 @@ public class FlatPopupFactory
// create drop shadow popup // create drop shadow popup
Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight ); Popup popupForScreenOfOwner = getPopupForScreenOfOwner( owner, contents, x, y, forceHeavyWeight );
return owner.getGraphicsConfiguration().isTranslucencyCapable() GraphicsConfiguration gc = owner.getGraphicsConfiguration();
return (gc != null && gc.isTranslucencyCapable())
? new DropShadowPopup( popupForScreenOfOwner, owner, contents ) ? new DropShadowPopup( popupForScreenOfOwner, owner, contents )
: new NonFlashingPopup( popupForScreenOfOwner, owner, contents ); : new NonFlashingPopup( popupForScreenOfOwner, owner, contents );
} }
@@ -695,8 +696,6 @@ public class FlatPopupFactory
Container contentPane = ((JWindow)popupWindow).getContentPane(); Container contentPane = ((JWindow)popupWindow).getContentPane();
contentPane.removeAll(); contentPane.removeAll();
contentPane.add( contents, BorderLayout.CENTER ); contentPane.add( contents, BorderLayout.CENTER );
popupWindow.invalidate();
popupWindow.validate();
popupWindow.pack(); popupWindow.pack();
// update client property on contents // update client property on contents
@@ -957,12 +956,13 @@ public class FlatPopupFactory
int w = prefSize.width + insets.left + insets.right; int w = prefSize.width + insets.left + insets.right;
int h = prefSize.height + insets.top + insets.bottom; int h = prefSize.height + insets.top + insets.bottom;
dropShadowPanel2.setPreferredSize( new Dimension( w, h ) ); dropShadowPanel2.setPreferredSize( new Dimension( w, h ) );
dropShadowPanel2.invalidate();
dropShadowWindow.pack();
// update drop shadow popup window location and size // update drop shadow popup window location
int x = popupWindow.getX() - insets.left; int x = popupWindow.getX() - insets.left;
int y = popupWindow.getY() - insets.top; int y = popupWindow.getY() - insets.top;
dropShadowWindow.setBounds( x, y, w, h ); dropShadowWindow.setLocation( x, y );
dropShadowWindow.pack();
} }
} }
} }

View File

@@ -239,11 +239,13 @@ public class FlatPopupMenuUI
if( gc == null && popupMenu.getInvoker() != null ) if( gc == null && popupMenu.getInvoker() != null )
gc = popupMenu.getInvoker().getGraphicsConfiguration(); gc = popupMenu.getInvoker().getGraphicsConfiguration();
// compute screen height if( gc == null )
return new Rectangle( Toolkit.getDefaultToolkit().getScreenSize() );
// compute screen bounds
// (always subtract screen insets because there is no API to detect whether // (always subtract screen insets because there is no API to detect whether
// the popup can overlap the taskbar; see JPopupMenu.canPopupOverlapTaskBar()) // the popup can overlap the taskbar; see JPopupMenu.canPopupOverlapTaskBar())
Toolkit toolkit = Toolkit.getDefaultToolkit(); Rectangle screenBounds = gc.getBounds();
Rectangle screenBounds = (gc != null) ? gc.getBounds() : new Rectangle( toolkit.getScreenSize() );
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc ); Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets( gc );
return FlatUIUtils.subtractInsets( screenBounds, screenInsets ); return FlatUIUtils.subtractInsets( screenBounds, screenInsets );
} }

View File

@@ -2306,8 +2306,23 @@ debug*/
/** @since 3.4 */ /** @since 3.4 */
@Override @Override
public Boolean isTitleBarCaptionAt( int x, int y ) { public Boolean isTitleBarCaptionAt( int x, int y ) {
if( tabForCoordinate( tabPane, x, y ) >= 0 ) // Note: not using tabForCoordinate() here because this may validate layout and cause dead lock
return false;
if( moreTabsButton != null ) {
// convert x,y from JTabbedPane coordinate space to ScrollableTabPanel coordinate space
Point viewPosition = tabViewport.getViewPosition();
x = x - tabViewport.getX() + viewPosition.x;
y = y - tabViewport.getY() + viewPosition.y;
// check whether point is within viewport
if( !tabViewport.getViewRect().contains( x, y ) )
return null; // check children
}
for( int i = 0; i < rects.length; i++ ) {
if( rects[i].contains( x, y ) )
return false;
}
return null; // check children return null; // check children
} }

View File

@@ -824,7 +824,8 @@ public class FlatTitlePane
Rectangle oldMaximizedBounds = frame.getMaximizedBounds(); Rectangle oldMaximizedBounds = frame.getMaximizedBounds();
if( !hasNativeCustomDecoration() && if( !hasNativeCustomDecoration() &&
(oldMaximizedBounds == null || (oldMaximizedBounds == null ||
Objects.equals( oldMaximizedBounds, rootPane.getClientProperty( "_flatlaf.maximizedBounds" ) )) ) Objects.equals( oldMaximizedBounds, rootPane.getClientProperty( "_flatlaf.maximizedBounds" ) )) &&
window.getGraphicsConfiguration() != null )
{ {
GraphicsConfiguration gc = window.getGraphicsConfiguration(); GraphicsConfiguration gc = window.getGraphicsConfiguration();
@@ -1058,10 +1059,11 @@ public class FlatTitlePane
* <p> * <p>
* Note: * Note:
* <ul> * <ul>
* <li>This method is invoked often when mouse is moved over title bar * <li>This method is invoked often when mouse is moved over window title bar area
* and should therefore return quickly. * and should therefore return quickly.
* <li>This method is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread) * <li>This method is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread)
* while processing Windows messages. * while processing Windows messages.
* It <b>must not</b> change any component property or layout because this could cause a dead lock.
* </ul> * </ul>
*/ */
private boolean captionHitTest( Point pt ) { private boolean captionHitTest( Point pt ) {
@@ -1107,8 +1109,18 @@ public class FlatTitlePane
// if component is not fully layouted, do not invoke function // if component is not fully layouted, do not invoke function
// because it is too dangerous that the function tries to layout the component, // because it is too dangerous that the function tries to layout the component,
// which could cause a dead lock // which could cause a dead lock
if( !c.isValid() ) if( !c.isValid() ) {
// revalidate if necessary so that it is valid when invoked again later
EventQueue.invokeLater( () -> {
Window w = SwingUtilities.windowForComponent( c );
if( w != null )
w.revalidate();
else
c.revalidate();
} );
return false; // assume that this is not a caption because the component has mouse listeners return false; // assume that this is not a caption because the component has mouse listeners
}
if( caption instanceof Function ) { if( caption instanceof Function ) {
// check client property function value // check client property function value
@@ -1578,6 +1590,15 @@ debug*/
* Useful for components that do not use mouse input on whole component bounds. * Useful for components that do not use mouse input on whole component bounds.
* E.g. a tabbed pane with a few tabs has some empty space beside the tabs * E.g. a tabbed pane with a few tabs has some empty space beside the tabs
* that can be used to move the window. * that can be used to move the window.
* <p>
* Note:
* <ul>
* <li>This method is invoked often when mouse is moved over window title bar area
* and should therefore return quickly.
* <li>This method is invoked on 'AWT-Windows' thread (not 'AWT-EventQueue' thread)
* while processing Windows messages.
* It <b>must not</b> change any component property or layout because this could cause a dead lock.
* </ul>
* *
* @return {@code true} if the component is not interested in mouse input at the given location * @return {@code true} if the component is not interested in mouse input at the given location
* {@code false} if the component wants process mouse input at the given location * {@code false} if the component wants process mouse input at the given location

View File

@@ -42,6 +42,7 @@ import javax.swing.DesktopManager;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JInternalFrame; import javax.swing.JInternalFrame;
import javax.swing.JLayeredPane; import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JRootPane; import javax.swing.JRootPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
@@ -191,7 +192,7 @@ public abstract class FlatWindowResizer
protected abstract Dimension getWindowMinimumSize(); protected abstract Dimension getWindowMinimumSize();
protected abstract Dimension getWindowMaximumSize(); protected abstract Dimension getWindowMaximumSize();
protected void beginResizing( int direction ) {} protected void beginResizing( int resizeDir ) {}
protected void endResizing() {} protected void endResizing() {}
//---- interface PropertyChangeListener ---- //---- interface PropertyChangeListener ----
@@ -234,17 +235,47 @@ public abstract class FlatWindowResizer
{ {
protected Window window; protected Window window;
private final JComponent centerComp;
private final boolean limitResizeToScreenBounds; private final boolean limitResizeToScreenBounds;
public WindowResizer( JRootPane rootPane ) { public WindowResizer( JRootPane rootPane ) {
super( rootPane ); super( rootPane );
// Transparent "center" component that is made visible only while resizing window.
// It uses same cursor as the area where resize dragging started.
// This ensures that the cursor shape stays stable while dragging mouse
// into the window to make window smaller. Otherwise it would toggling between
// resize and standard cursor because the component layout is not updated
// fast enough and the mouse cursor is always updated from the component
// at the mouse location.
centerComp = new JPanel();
centerComp.setOpaque( false );
centerComp.setVisible( false );
Container cont = rootPane.getLayeredPane();
cont.add( centerComp, WINDOW_RESIZER_LAYER, 4 );
// On Linux, limit window resizing to screen bounds because otherwise // On Linux, limit window resizing to screen bounds because otherwise
// there would be a strange effect when the mouse is moved over a sidebar // there would be a strange effect when the mouse is moved over a sidebar
// while resizing and the opposite window side is also resized. // while resizing and the opposite window side is also resized.
limitResizeToScreenBounds = SystemInfo.isLinux; limitResizeToScreenBounds = SystemInfo.isLinux;
} }
@Override
public void uninstall() {
Container cont = topDragComp.getParent();
cont.remove( centerComp );
super.uninstall();
}
@Override
public void doLayout() {
super.doLayout();
if( centerComp != null && centerComp.isVisible() )
centerComp.setBounds( 0, 0, resizeComp.getWidth(), resizeComp.getHeight() );
}
@Override @Override
protected void addNotify() { protected void addNotify() {
Container parent = resizeComp.getParent(); Container parent = resizeComp.getParent();
@@ -299,20 +330,17 @@ public abstract class FlatWindowResizer
@Override @Override
protected boolean limitToParentBounds() { protected boolean limitToParentBounds() {
return limitResizeToScreenBounds && window != null; return limitResizeToScreenBounds && window != null && window.getGraphicsConfiguration() != null;
} }
@Override @Override
protected Rectangle getParentBounds() { protected Rectangle getParentBounds() {
if( limitResizeToScreenBounds && window != null ) { GraphicsConfiguration gc = window.getGraphicsConfiguration();
GraphicsConfiguration gc = window.getGraphicsConfiguration(); Rectangle bounds = gc.getBounds();
Rectangle bounds = gc.getBounds(); Insets insets = window.getToolkit().getScreenInsets( gc );
Insets insets = window.getToolkit().getScreenInsets( gc ); return new Rectangle( bounds.x + insets.left, bounds.y + insets.top,
return new Rectangle( bounds.x + insets.left, bounds.y + insets.top, bounds.width - insets.left - insets.right,
bounds.width - insets.left - insets.right, bounds.height - insets.top - insets.bottom );
bounds.height - insets.top - insets.bottom );
}
return null;
} }
@Override @Override
@@ -346,6 +374,19 @@ public abstract class FlatWindowResizer
public void windowStateChanged( WindowEvent e ) { public void windowStateChanged( WindowEvent e ) {
updateVisibility(); updateVisibility();
} }
@Override
protected void beginResizing( int resizeDir ) {
centerComp.setBounds( 0, 0, resizeComp.getWidth(), resizeComp.getHeight() );
centerComp.setCursor( getPredefinedCursor( resizeDir ) );
centerComp.setVisible( true );
}
@Override
protected void endResizing() {
centerComp.setVisible( false );
centerComp.setCursor( null );
}
} }
//---- class InternalFrameResizer ----------------------------------------- //---- class InternalFrameResizer -----------------------------------------
@@ -427,7 +468,18 @@ public abstract class FlatWindowResizer
} }
@Override @Override
protected void beginResizing( int direction ) { protected void beginResizing( int resizeDir ) {
int direction = 0;
switch( resizeDir ) {
case N_RESIZE_CURSOR: direction = NORTH; break;
case S_RESIZE_CURSOR: direction = SOUTH; break;
case W_RESIZE_CURSOR: direction = WEST; break;
case E_RESIZE_CURSOR: direction = EAST; break;
case NW_RESIZE_CURSOR: direction = NORTH_WEST; break;
case NE_RESIZE_CURSOR: direction = NORTH_EAST; break;
case SW_RESIZE_CURSOR: direction = SOUTH_WEST; break;
case SE_RESIZE_CURSOR: direction = SOUTH_EAST; break;
}
desktopManager.get().beginResizingFrame( getFrame(), direction ); desktopManager.get().beginResizingFrame( getFrame(), direction );
} }
@@ -535,18 +587,7 @@ debug*/
dragRightOffset = windowBounds.x + windowBounds.width - xOnScreen; dragRightOffset = windowBounds.x + windowBounds.width - xOnScreen;
dragBottomOffset = windowBounds.y + windowBounds.height - yOnScreen; dragBottomOffset = windowBounds.y + windowBounds.height - yOnScreen;
int direction = 0; beginResizing( resizeDir );
switch( resizeDir ) {
case N_RESIZE_CURSOR: direction = NORTH; break;
case S_RESIZE_CURSOR: direction = SOUTH; break;
case W_RESIZE_CURSOR: direction = WEST; break;
case E_RESIZE_CURSOR: direction = EAST; break;
case NW_RESIZE_CURSOR: direction = NORTH_WEST; break;
case NE_RESIZE_CURSOR: direction = NORTH_EAST; break;
case SW_RESIZE_CURSOR: direction = SOUTH_WEST; break;
case SE_RESIZE_CURSOR: direction = SOUTH_EAST; break;
}
beginResizing( direction );
} }
@Override @Override

View File

@@ -33,6 +33,7 @@ import javax.swing.plaf.DimensionUIResource;
import javax.swing.plaf.FontUIResource; import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.InsetsUIResource; import javax.swing.plaf.InsetsUIResource;
import javax.swing.plaf.UIResource; import javax.swing.plaf.UIResource;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.FlatSystemProperties;
/** /**
@@ -188,7 +189,9 @@ public class UIScale
// because even if we are on a HiDPI display it is not sure // because even if we are on a HiDPI display it is not sure
// that a larger font size is set by the current LaF // that a larger font size is set by the current LaF
// (e.g. can avoid large icons with small text) // (e.g. can avoid large icons with small text)
Font font = UIManager.getFont( "defaultFont" ); Font font = null;
if( UIManager.getLookAndFeel() instanceof FlatLaf )
font = UIManager.getFont( "defaultFont" );
if( font == null ) if( font == null )
font = UIManager.getFont( "Label.font" ); font = UIManager.getFont( "Label.font" );
@@ -244,6 +247,16 @@ public class UIScale
} }
private static float computeScaleFactor( Font font ) { private static float computeScaleFactor( Font font ) {
String customFontSizeDivider = System.getProperty( "flatlaf.uiScale.fontSizeDivider" );
if( customFontSizeDivider != null ) {
try {
float fontSizeDivider = Math.max( Integer.parseInt( customFontSizeDivider ), 10 );
return font.getSize() / fontSizeDivider;
} catch( NumberFormatException ex ) {
// ignore
}
}
// default font size // default font size
float fontSizeDivider = 12f; float fontSizeDivider = 12f;

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2024 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.ui;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Locale;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.View;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
/**
* @author Karl Tauber
*/
public class TestFlatHTML
{
private final String body = "some <small>small</small> text";
private final String bodyInBody = "<body>" + body + "</body>";
private final String bodyPlain = "some small text";
@BeforeAll
static void setup() {
TestUtils.setup( false );
TestUtils.scaleFont( 2 );
}
@AfterAll
static void cleanup() {
TestUtils.cleanup();
}
@Test
void simple() {
testHtmlBaseSize( "<html>${BASE_SIZE_IN_HEAD}" + body + "</html>", bodyPlain );
testHtmlBaseSize( "<html>${BASE_SIZE_IN_HEAD}" + bodyInBody + "</html>", bodyPlain );
}
@Test
void htmlWithHeadTag() {
testHtmlBaseSize( "<html><head>${BASE_SIZE}<title>test</title><head>" + body + "</html>", bodyPlain );
testHtmlBaseSize( "<html><head>${BASE_SIZE}<title>test</title><head>" + bodyInBody + "</html>", bodyPlain );
testHtmlBaseSize( "<html><head id=\"abc\">${BASE_SIZE}<title>test</title><head>" + body + "</html>", bodyPlain );
testHtmlBaseSize( "<html><head id=\"abc\">${BASE_SIZE}<title>test</title><head>" + bodyInBody + "</html>", bodyPlain );
}
@Test
void htmlWithStyleTag() {
testHtmlBaseSize( "<html>${BASE_SIZE}<style>body { color: #f00; }</style>" + bodyInBody + "</html>", bodyPlain );
testHtmlBaseSize( "<html>${BASE_SIZE}<style>body { color: #f00; }</style><h1>header1</h1>" + body + "</html>", "header1\n" + bodyPlain );
testHtmlBaseSize( "<html>${BASE_SIZE}<style type='text/css'>body { color: #f00; }</style>" + bodyInBody + "</html>", bodyPlain );
testHtmlBaseSize( "<html>${BASE_SIZE}<style type='text/css'>body { color: #f00; }</style><h1>header1</h1>" + body + "</html>", "header1\n" + bodyPlain );
}
@Test
void htmlOnComponentWithNullFont() {
assertDoesNotThrow( () -> {
JLabel label = new JLabel();
label.setFont( null );
label.setText( "<html>foo<br>bar</html>" );
} );
}
private void testHtmlBaseSize( String html, String expectedPlain ) {
testHtmlBaseSizeImpl( html, expectedPlain );
testHtmlBaseSizeImpl( html.toUpperCase( Locale.ENGLISH ), expectedPlain.toUpperCase( Locale.ENGLISH ) );
}
private void testHtmlBaseSizeImpl( String html, String expectedPlain ) {
String baseSize = "<style>BASE_SIZE " + UIManager.getFont( "Label.font" ).getSize() + "</style>";
String baseSizeInHead = "<head>" + baseSize + "</head>";
String expectedHtml = html.replace( "${BASE_SIZE}", baseSize ).replace( "${BASE_SIZE_IN_HEAD}", baseSizeInHead );
html = html.replace( "${BASE_SIZE}", "" ).replace( "${BASE_SIZE_IN_HEAD}", "" );
testHtml( html, expectedHtml, expectedPlain );
}
private void testHtml( String html, String expectedHtml, String expectedPlain ) {
FlatHTML.testUpdateRenderer = (c, newHtml) -> {
assertEquals( expectedHtml, newHtml );
assertEquals( expectedPlain, getPlainText( c ) );
};
new JLabel( html );
FlatHTML.testUpdateRenderer = null;
}
private String getPlainText( JComponent c ) {
View view = (View) c.getClientProperty( BasicHTML.propertyKey );
if( view == null )
return null;
Document doc = view.getDocument();
try {
return doc.getText( 0, doc.getLength() ).trim();
} catch( BadLocationException ex ) {
ex.printStackTrace();
return null;
}
}
}

View File

@@ -6,7 +6,7 @@
# when the Demo window is activated. # when the Demo window is activated.
# base theme (light, dark, intellij or darcula) # base theme (light, dark, intellij, darcula, maclight or macdark)
@baseTheme = light @baseTheme = light
# add you theme defaults here # add you theme defaults here

View File

@@ -24,6 +24,7 @@ import java.awt.Dimension;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.awt.Font; import java.awt.Font;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.Insets; import java.awt.Insets;
import java.awt.KeyboardFocusManager; import java.awt.KeyboardFocusManager;
import java.awt.LayoutManager; import java.awt.LayoutManager;
@@ -450,15 +451,18 @@ public class FlatInspector
Dimension size = tip.getPreferredSize(); Dimension size = tip.getPreferredSize();
// position the tip in the visible area // position the tip in the visible area
Rectangle visibleRect = rootPane.getGraphicsConfiguration().getBounds(); GraphicsConfiguration gc = rootPane.getGraphicsConfiguration();
if( tx + size.width > visibleRect.x + visibleRect.width ) if( gc != null ) {
tx -= size.width + UIScale.scale( 16 ); Rectangle visibleRect = gc.getBounds();
if( ty + size.height > visibleRect.y + visibleRect.height ) if( tx + size.width > visibleRect.x + visibleRect.width )
ty -= size.height + UIScale.scale( 32 ); tx -= size.width + UIScale.scale( 16 );
if( tx < visibleRect.x ) if( ty + size.height > visibleRect.y + visibleRect.height )
tx = visibleRect.x; ty -= size.height + UIScale.scale( 32 );
if( ty < visibleRect.y ) if( tx < visibleRect.x )
ty = visibleRect.y; tx = visibleRect.x;
if( ty < visibleRect.y )
ty = visibleRect.y;
}
PopupFactory popupFactory = PopupFactory.getSharedInstance(); PopupFactory popupFactory = PopupFactory.getSharedInstance();
popup = popupFactory.getPopup( c, tip, tx, ty ); popup = popupFactory.getPopup( c, tip, tx, ty );

View File

@@ -309,6 +309,8 @@ public class FlatWindowsNativeWindowBorder
WM_ENTERSIZEMOVE = 0x0231, WM_ENTERSIZEMOVE = 0x0231,
WM_EXITSIZEMOVE = 0x0232, WM_EXITSIZEMOVE = 0x0232,
WM_DPICHANGED = 0x02E0,
WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320; WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320;
// WM_SIZE wParam // WM_SIZE wParam
@@ -501,6 +503,22 @@ public class FlatWindowsNativeWindowBorder
isMoving = true; isMoving = true;
break; break;
case WM_DPICHANGED:
LRESULT lResult = User32Ex.INSTANCE.CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam );
// if window is maximized and DPI/scaling changed, then Windows
// does not send a subsequent WM_SIZE message and Java window bounds,
// which depend on scale factor, are not updated
boolean isMaximized = User32Ex.INSTANCE.IsZoomed( hwnd );
if( isMaximized ) {
MyRECT r = new MyRECT( new Pointer( lParam.longValue() ) );
int width = r.right - r.left;
int height = r.bottom - r.top;
User32Ex.INSTANCE.CallWindowProc( defaultWndProc, hwnd, WM_SIZE, new WPARAM( SIZE_MAXIMIZED ), MAKELPARAM( width, height ) );
}
return lResult;
case WM_ERASEBKGND: case WM_ERASEBKGND:
// do not erase background while the user is moving the window, // do not erase background while the user is moving the window,
// otherwise there may be rendering artifacts on HiDPI screens with Java 9+ // otherwise there may be rendering artifacts on HiDPI screens with Java 9+
@@ -818,6 +836,13 @@ public class FlatWindowsNativeWindowBorder
return (low & 0xffff) | ((high & 0xffff) << 16); return (low & 0xffff) | ((high & 0xffff) << 16);
} }
/**
* Same implementation as MAKELPARAM(l, h) macro in winuser.h.
*/
private LPARAM MAKELPARAM( int low, int high ) {
return new LPARAM( MAKELONG( low, high ) );
}
/** /**
* Same implementation as RGB(r,g,b) macro in wingdi.h. * Same implementation as RGB(r,g,b) macro in wingdi.h.
*/ */
@@ -937,6 +962,23 @@ public class FlatWindowsNativeWindowBorder
} }
} }
//---- class MyRECT -------------------------------------------------------
@FieldOrder( { "left", "top", "right", "bottom" } )
public static class MyRECT
extends Structure
{
public int left;
public int top;
public int right;
public int bottom;
public MyRECT( Pointer pointer ) {
super( pointer );
read();
}
}
//---- class MENUITEMINFO ------------------------------------------------- //---- class MENUITEMINFO -------------------------------------------------
@FieldOrder( { "cbSize", "fMask", "fType", "fState", "wID", "hSubMenu", @FieldOrder( { "cbSize", "fMask", "fType", "fState", "wID", "hSubMenu",

View File

@@ -19,3 +19,32 @@ The DLLs were built on a GitHub server with the help of GitHub Actions. See:
[Native Libraries](https://github.com/JFormDesigner/FlatLaf/actions/workflows/natives.yml) [Native Libraries](https://github.com/JFormDesigner/FlatLaf/actions/workflows/natives.yml)
workflow. Then the produced Artifacts ZIP was downloaded, signed DLLs with workflow. Then the produced Artifacts ZIP was downloaded, signed DLLs with
FormDev Software code signing certificate and committed the DLLs to Git. FormDev Software code signing certificate and committed the DLLs to Git.
## Development
To build the library on Windows using Gradle, (parts of)
[Visual Studio Community
2022](https://visualstudio.microsoft.com/downloads/)
needs to be installed. After downloading and running `VisualStudioSetup.exe` the
**Visual Studio Installer** is installed and started. Once running, it shows the
**Workloads** tab that allows you to install additional packages. Either choose
**Desktop development with C++**, or to save some disk space switch to the
**Single Components** tab and choose following components (newest versions):
- MSVC v143 - VS 2022 C++ x64/x86 Buildtools
- MSVC v143 - VS 2022 C++ ARM64/ARM64EC Buildtools
- Windows 11 SDK
Note that the Visual Studio Installer shows many similar named components for
MSVC. Make sure to choose exactly those components listed above.
Using
[Build Tools for Visual Studio 2022](https://visualstudio.microsoft.com/downloads/#remote-tools-for-visual-studio-2022)
(installs only compiler and SDKs) instead of
[Visual Studio Community
2022](https://visualstudio.microsoft.com/downloads/)
does not work with Gradle.
[Visual Studio Code](https://code.visualstudio.com/) with **C/C++** extension
can be used for C++ code editing.

View File

@@ -288,6 +288,23 @@ LRESULT CALLBACK FlatWndProc::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, L
isMoving = true; isMoving = true;
break; break;
case WM_DPICHANGED: {
LRESULT lResult = ::CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam );
// if window is maximized and DPI/scaling changed, then Windows
// does not send a subsequent WM_SIZE message and Java window bounds,
// which depend on scale factor, are not updated
bool isMaximized = ::IsZoomed( hwnd );
if( isMaximized ) {
RECT* r = reinterpret_cast<RECT*>( lParam );
int width = r->right - r->left;
int height = r->bottom - r->top;
::CallWindowProc( defaultWndProc, hwnd, WM_SIZE, SIZE_MAXIMIZED, MAKELPARAM( width, height ) );
}
return lResult;
}
case WM_ERASEBKGND: case WM_ERASEBKGND:
// do not erase background while the user is moving the window, // do not erase background while the user is moving the window,
// otherwise there may be rendering artifacts on HiDPI screens with Java 9+ // otherwise there may be rendering artifacts on HiDPI screens with Java 9+

View File

@@ -30,12 +30,26 @@ HWND getWindowHandle( JNIEnv* env, jobject window );
//---- Utility ---------------------------------------------------------------- //---- Utility ----------------------------------------------------------------
typedef LONG (WINAPI *RtlGetVersion_Type)( OSVERSIONINFO* );
extern "C" extern "C"
JNIEXPORT jlong JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_getOSBuildNumberImpl JNIEXPORT jlong JNICALL Java_com_formdev_flatlaf_ui_FlatNativeWindowsLibrary_getOSBuildNumberImpl
( JNIEnv* env, jclass cls ) ( JNIEnv* env, jclass cls )
{ {
OSVERSIONINFO info; OSVERSIONINFO info;
info.dwOSVersionInfoSize = sizeof( info ); info.dwOSVersionInfoSize = sizeof( info );
info.dwBuildNumber = 0;
// use RtlGetVersion for the case that app manifest does not specify Windows 10+ compatibility
// https://www.codeproject.com/Articles/5336372/Windows-Version-Detection
HMODULE ntdllModule = ::GetModuleHandleA( "ntdll.dll" );
if( ntdllModule != NULL ) {
RtlGetVersion_Type pRtlGetVersion = (RtlGetVersion_Type) ::GetProcAddress( ntdllModule, "RtlGetVersion" );
if( pRtlGetVersion != NULL && pRtlGetVersion( &info ) == 0 )
return info.dwBuildNumber;
}
// fallback
if( !::GetVersionEx( &info ) ) if( !::GetVersionEx( &info ) )
return 0; return 0;
return info.dwBuildNumber; return info.dwBuildNumber;

View File

@@ -59,6 +59,11 @@ public class FlatComponentsTest
}; };
for( JSlider slider : allSliders ) for( JSlider slider : allSliders )
slider.addChangeListener( sliderChanged ); slider.addChangeListener( sliderChanged );
UIManager.addPropertyChangeListener( e -> {
if( "lookAndFeel".equals( e.getPropertyName() ) && hideArrowButtonCheckBox.isSelected() )
SwingUtilities.invokeLater( () -> hideArrowButton() );
} );
} }
private void changeProgress() { private void changeProgress() {
@@ -127,6 +132,18 @@ public class FlatComponentsTest
} }
} }
private void hideArrowButton() {
boolean hideArrowButton = hideArrowButtonCheckBox.isSelected();
for( Component c : getComponents() ) {
if( c instanceof JComboBox ) {
Component b = ((JComboBox<?>)c).getComponent( 0 );
if( b instanceof AbstractButton )
b.setVisible( !hideArrowButton );
}
}
}
private void roundRectChanged() { private void roundRectChanged() {
Boolean roundRect = roundRectCheckBox.isSelected() ? true : null; Boolean roundRect = roundRectCheckBox.isSelected() ? true : null;
@@ -380,6 +397,7 @@ public class FlatComponentsTest
magentaOutlineRadioButton = new JRadioButton(); magentaOutlineRadioButton = new JRadioButton();
magentaCyanOutlineRadioButton = new JRadioButton(); magentaCyanOutlineRadioButton = new JRadioButton();
focusPaintedCheckBox = new JCheckBox(); focusPaintedCheckBox = new JCheckBox();
hideArrowButtonCheckBox = new JCheckBox();
JLabel scrollBarLabel = new JLabel(); JLabel scrollBarLabel = new JLabel();
JScrollBar scrollBar1 = new JScrollBar(); JScrollBar scrollBar1 = new JScrollBar();
JScrollBar scrollBar4 = new JScrollBar(); JScrollBar scrollBar4 = new JScrollBar();
@@ -1234,9 +1252,10 @@ public class FlatComponentsTest
"[]" + "[]" +
"[]", "[]",
// rows // rows
"[]" + "[]0" +
"[]" + "[]0" +
"[]" + "[]0" +
"[]0" +
"[]")); "[]"));
//---- buttonTypeComboBox ---- //---- buttonTypeComboBox ----
@@ -1290,13 +1309,18 @@ public class FlatComponentsTest
magentaCyanOutlineRadioButton.addActionListener(e -> outlineChanged()); magentaCyanOutlineRadioButton.addActionListener(e -> outlineChanged());
panel4.add(magentaCyanOutlineRadioButton); panel4.add(magentaCyanOutlineRadioButton);
} }
panel5.add(panel4, "cell 0 2 1 2"); panel5.add(panel4, "cell 0 2 1 3");
//---- focusPaintedCheckBox ---- //---- focusPaintedCheckBox ----
focusPaintedCheckBox.setText("focusPainted"); focusPaintedCheckBox.setText("focusPainted");
focusPaintedCheckBox.setSelected(true); focusPaintedCheckBox.setSelected(true);
focusPaintedCheckBox.addActionListener(e -> focusPaintedChanged()); focusPaintedCheckBox.addActionListener(e -> focusPaintedChanged());
panel5.add(focusPaintedCheckBox, "cell 1 2"); panel5.add(focusPaintedCheckBox, "cell 1 2");
//---- hideArrowButtonCheckBox ----
hideArrowButtonCheckBox.setText("hide arrow button");
hideArrowButtonCheckBox.addActionListener(e -> hideArrowButton());
panel5.add(hideArrowButtonCheckBox, "cell 1 3");
} }
add(panel5, "cell 5 13 2 10,grow"); add(panel5, "cell 5 13 2 10,grow");
@@ -1703,6 +1727,7 @@ public class FlatComponentsTest
private JRadioButton magentaOutlineRadioButton; private JRadioButton magentaOutlineRadioButton;
private JRadioButton magentaCyanOutlineRadioButton; private JRadioButton magentaCyanOutlineRadioButton;
private JCheckBox focusPaintedCheckBox; private JCheckBox focusPaintedCheckBox;
private JCheckBox hideArrowButtonCheckBox;
private JSlider slider1; private JSlider slider1;
private JSlider slider6; private JSlider slider6;
private JCheckBox sliderPaintTrackCheckBox; private JCheckBox sliderPaintTrackCheckBox;

View File

@@ -1,4 +1,4 @@
JFDML JFormDesigner: "7.0.5.0.404" Java: "17.0.2" encoding: "UTF-8" JFDML JFormDesigner: "8.3" encoding: "UTF-8"
new FormModel { new FormModel {
contentType: "form/swing" contentType: "form/swing"
@@ -993,7 +993,7 @@ new FormModel {
} ) } )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$columnConstraints": "[][]" "$columnConstraints": "[][]"
"$rowConstraints": "[][][][]" "$rowConstraints": "[]0[]0[]0[]0[]"
"$layoutConstraints": "ltr,insets dialog,hidemode 3" "$layoutConstraints": "ltr,insets dialog,hidemode 3"
} ) { } ) {
name: "panel5" name: "panel5"
@@ -1092,7 +1092,7 @@ new FormModel {
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "outlineChanged", false ) ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "outlineChanged", false ) )
} ) } )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2 1 2" "value": "cell 0 2 1 3"
} ) } )
add( new FormComponent( "javax.swing.JCheckBox" ) { add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "focusPaintedCheckBox" name: "focusPaintedCheckBox"
@@ -1105,6 +1105,16 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 2" "value": "cell 1 2"
} ) } )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "hideArrowButtonCheckBox"
"text": "hide arrow button"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "hideArrowButton", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 3"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 5 13 2 10,grow" "value": "cell 5 13 2 10,grow"
} ) } )

View File

@@ -25,6 +25,8 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.TitledBorder; import javax.swing.border.TitledBorder;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
@@ -74,6 +76,7 @@ public class FlatWindowDecorationsTest
} }
private List<Image> images; private List<Image> images;
private Timer refreshStateTimer;
FlatWindowDecorationsTest() { FlatWindowDecorationsTest() {
initComponents(); initComponents();
@@ -132,6 +135,49 @@ public class FlatWindowDecorationsTest
fullWindowContentButtonsBoundsField.setEnabled( bounds != null ); fullWindowContentButtonsBoundsField.setEnabled( bounds != null );
} ); } );
} }
if( window instanceof Frame ) {
AtomicInteger lastState = new AtomicInteger( -1 );
AtomicReference<Window> lastFullScreenWindow = new AtomicReference<>();
refreshStateTimer = new Timer( 500, e -> {
Frame frame = (Frame) window;
int state = frame.getExtendedState();
Window fullScreenWindow = window.getGraphicsConfiguration().getDevice().getFullScreenWindow();
if( state != lastState.get() || fullScreenWindow != lastFullScreenWindow.get() ) {
lastState.set( state );
lastFullScreenWindow.set( fullScreenWindow );
String s = "";
if( (state & Frame.ICONIFIED) != 0 )
s += "iconified ";
if( (state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH )
s += "maximized ";
else {
if( (state & Frame.MAXIMIZED_HORIZ) != 0 )
s += "maximized-horizontal ";
if( (state & Frame.MAXIMIZED_VERT) != 0 )
s += "maximized-vertical ";
}
if( fullScreenWindow == window )
s += "full-screen ";
if( s.isEmpty() )
s = "normal";
frameStateField.setText( s );
}
} );
refreshStateTimer.start();
}
}
@Override
public void removeNotify() {
super.removeNotify();
if( refreshStateTimer != null ) {
refreshStateTimer.stop();
refreshStateTimer = null;
}
} }
private void updateDecorationStyleRadioButtons( JRootPane rootPane ) { private void updateDecorationStyleRadioButtons( JRootPane rootPane ) {
@@ -653,6 +699,9 @@ debug*/
typeUtilityRadioButton = new JRadioButton(); typeUtilityRadioButton = new JRadioButton();
typeSmallRadioButton = new JRadioButton(); typeSmallRadioButton = new JRadioButton();
showRectanglesCheckBox = new JCheckBox(); showRectanglesCheckBox = new JCheckBox();
JPanel hSpacer1 = new JPanel(null);
JLabel frameStateLabel = new JLabel();
frameStateField = new JLabel();
menuBar = new JMenuBar(); menuBar = new JMenuBar();
JMenu fileMenu = new JMenu(); JMenu fileMenu = new JMenu();
JMenuItem newMenuItem = new JMenuItem(); JMenuItem newMenuItem = new JMenuItem();
@@ -1092,6 +1141,16 @@ debug*/
showRectanglesCheckBox.setSelected(true); showRectanglesCheckBox.setSelected(true);
showRectanglesCheckBox.addActionListener(e -> showRectangles()); showRectanglesCheckBox.addActionListener(e -> showRectangles());
add(showRectanglesCheckBox, "cell 0 3"); add(showRectanglesCheckBox, "cell 0 3");
add(hSpacer1, "cell 2 3 2 1");
//---- frameStateLabel ----
frameStateLabel.setText("Frame state:");
add(frameStateLabel, "cell 2 3 2 1,alignx right,growx 0");
//---- frameStateField ----
frameStateField.setText("n/a");
frameStateField.setFont(frameStateField.getFont().deriveFont(frameStateField.getFont().getStyle() | Font.BOLD));
add(frameStateField, "cell 2 3 2 1,alignx right,growx 0");
//======== menuBar ======== //======== menuBar ========
{ {
@@ -1341,6 +1400,7 @@ debug*/
private JRadioButton typeUtilityRadioButton; private JRadioButton typeUtilityRadioButton;
private JRadioButton typeSmallRadioButton; private JRadioButton typeSmallRadioButton;
private JCheckBox showRectanglesCheckBox; private JCheckBox showRectanglesCheckBox;
private JLabel frameStateField;
private JMenuBar menuBar; private JMenuBar menuBar;
// JFormDesigner - End of variables declaration //GEN-END:variables // JFormDesigner - End of variables declaration //GEN-END:variables
} }

View File

@@ -637,6 +637,27 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3" "value": "cell 0 3"
} ) } )
add( new FormComponent( "com.jformdesigner.designer.wrapper.HSpacer" ) {
name: "hSpacer1"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 3 2 1"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "frameStateLabel"
"text": "Frame state:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 3 2 1,alignx right,growx 0"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "frameStateField"
"text": "n/a"
"font": new com.jformdesigner.model.SwingDerivedFont( null, 1, 0, false )
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 2 3 2 1,alignx right,growx 0"
} )
}, new FormLayoutConstraints( null ) { }, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 ) "location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 960, 570 ) "size": new java.awt.Dimension( 960, 570 )

View File

@@ -23,9 +23,11 @@ import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Robot; import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.Window; import java.awt.Window;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.EventQueue; import java.awt.EventQueue;
@@ -97,16 +99,18 @@ class FlatColorPipette
// macOS: windows with opacity smaller than 0.05 does not receive // macOS: windows with opacity smaller than 0.05 does not receive
// mouse clicked/pressed/released events (but mouse moved events) // mouse clicked/pressed/released events (but mouse moved events)
setOpacity( SystemInfo.isMacOS ? 0.05f : 0.005f ); setOpacity( SystemInfo.isMacOS ? 0.05f : 0.005f );
setBounds( owner.getGraphicsConfiguration().getBounds() ); GraphicsConfiguration gc = owner.getGraphicsConfiguration();
setBounds( (gc != null) ? gc.getBounds() : new Rectangle( Toolkit.getDefaultToolkit().getScreenSize() ) );
robot = new Robot( owner.getGraphicsConfiguration().getDevice() ); robot = (gc != null) ? new Robot( gc.getDevice() ) : new Robot();
magnifier = new Magnifier( this, robot ); magnifier = new Magnifier( this, robot );
MouseAdapter mouseListener = new MouseAdapter() { MouseAdapter mouseListener = new MouseAdapter() {
@Override @Override
public void mouseMoved( MouseEvent e ) { public void mouseMoved( MouseEvent e ) {
lastX = e.getX(); // adding location of pick window is necessary for secondary screens
lastY = e.getY(); lastX = e.getX() + getX();
lastY = e.getY() + getY();
// get color at mouse location // get color at mouse location
// (temporary change opacity to zero to get correct color from robot) // (temporary change opacity to zero to get correct color from robot)
@@ -133,7 +137,7 @@ class FlatColorPipette
// --> use last hover color on macOS // --> use last hover color on macOS
color = SystemInfo.isMacOS color = SystemInfo.isMacOS
? lastHoverColor ? lastHoverColor
: robot.getPixelColor( e.getX(), e.getY() ); : robot.getPixelColor( e.getX() + getX(), e.getY() + getY() );
} }
pick( color ); pick( color );
} }

View File

@@ -241,7 +241,7 @@ class FlatThemePropertiesSupport
} }
static boolean isDark( String baseTheme ) { static boolean isDark( String baseTheme ) {
return "dark".equals( baseTheme ) || "darcula".equals( baseTheme ); return "dark".equals( baseTheme ) || "darcula".equals( baseTheme ) || "macdark".equals( baseTheme );
} }
private String getBaseTheme() { private String getBaseTheme() {

View File

@@ -14,7 +14,7 @@
# limitations under the License. # limitations under the License.
# #
flatlaf.releaseVersion = 3.5.2 flatlaf.releaseVersion = 3.5.4
flatlaf.developmentVersion = 3.6-SNAPSHOT flatlaf.developmentVersion = 3.6-SNAPSHOT
org.gradle.parallel = true org.gradle.parallel = true

View File

@@ -46,8 +46,8 @@ glazedlists = "com.glazedlists:glazedlists:1.11.0"
netbeans-api-awt = "org.netbeans.api:org-openide-awt:RELEASE112" netbeans-api-awt = "org.netbeans.api:org-openide-awt:RELEASE112"
# flatlaf-natives-jna # flatlaf-natives-jna
jna = "net.java.dev.jna:jna:5.12.1" jna = "net.java.dev.jna:jna:5.15.0"
jna-platform = "net.java.dev.jna:jna-platform:5.12.1" jna-platform = "net.java.dev.jna:jna-platform:5.15.0"
# junit # junit
junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME