mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2026-02-11 22:47:13 -06:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ba9fc6c1c | ||
|
|
0a9ecd66a9 | ||
|
|
6991d6729e | ||
|
|
1462636e97 | ||
|
|
7e59a7f4af | ||
|
|
e9a21848bc | ||
|
|
1dcb251ecb | ||
|
|
3f33543cee | ||
|
|
84bd2088f2 | ||
|
|
4f4a3132c5 | ||
|
|
e064c934cb | ||
|
|
16fc3cabf2 | ||
|
|
7e002ff6c2 | ||
|
|
323c0c62c3 | ||
|
|
ff5bd301cc | ||
|
|
c37712b0f0 | ||
|
|
ee9e238592 | ||
|
|
da5d6fa157 | ||
|
|
d471f08b15 | ||
|
|
b97424f767 |
39
CHANGELOG.md
39
CHANGELOG.md
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 );
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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 );
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 );
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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+
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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"
|
||||||
} )
|
} )
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 )
|
||||||
|
|||||||
@@ -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 );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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" }
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user