Skip to content

Commit

Permalink
Merge branch 'windows-full-window-content'
Browse files Browse the repository at this point in the history
  • Loading branch information
DevCharly committed Feb 19, 2024
2 parents 625c0a3 + b804463 commit fbdc8d5
Show file tree
Hide file tree
Showing 25 changed files with 714 additions and 228 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ FlatLaf Change Log

#### New features and improvements

- FlatLaf window decorations (Windows 10/11 and Linux): Support "full window
content" mode, which allows you to extend the content into the window title
bar. (PR #801)
- macOS: Support larger window title bar close/minimize/zoom buttons spacing in
[full window content](https://www.formdev.com/flatlaf/macos/#full_window_content)
mode and introduced "buttons placeholder". (PR #779)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,36 @@ public interface FlatClientProperties
String COMPONENT_FOCUS_OWNER = "JComponent.focusOwner";

/**
* Specifies whether a component in an embedded menu bar should behave as caption
* Specifies whether a component shown in a window title bar area should behave as caption
* (left-click allows moving window, right-click shows window system menu).
* The component does not receive mouse pressed/released/clicked/dragged events,
* The caption component does not receive mouse pressed/released/clicked/dragged events,
* but it gets mouse entered/exited/moved events.
* <p>
* Since 3.4, this client property also supports using a function that can check
* whether a given location in the component should behave as caption.
* Useful for components that do not use mouse input on whole component bounds.
*
* <pre>{@code
* myComponent.putClientProperty( "JComponent.titleBarCaption",
* (Function<Point, Boolean>) pt -> {
* // parameter pt contains mouse location (in myComponent coordinates)
* // return true if the component is not interested in mouse input at the given location
* // return false if the component wants process mouse input at the given location
* // return null if the component children should be checked
* return ...; // check here
* } );
* }</pre>
* <b>Warning</b>:
* <ul>
* <li>This function is invoked often when mouse is moved over window title bar area
* and should therefore return quickly.
* <li>This function 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>
* <p>
* <strong>Component</strong> {@link javax.swing.JComponent}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
* <strong>Value type</strong> {@link java.lang.Boolean} or {@link java.util.function.Function}&lt;Point, Boolean&gt;
*
* @since 2.5
*/
Expand All @@ -274,7 +297,7 @@ public interface FlatClientProperties

/**
* Marks the panel as placeholder for the iconfify/maximize/close buttons
* in fullWindowContent mode.
* in fullWindowContent mode. See {@link #FULL_WINDOW_CONTENT}.
* <p>
* If fullWindowContent mode is enabled, the preferred size of the panel is equal
* to the size of the iconfify/maximize/close buttons. Otherwise is is {@code 0,0}.
Expand Down Expand Up @@ -462,6 +485,32 @@ public interface FlatClientProperties
*/
String MENU_BAR_EMBEDDED = "JRootPane.menuBarEmbedded";

/**
* Specifies whether the content pane (and the glass pane) should be extended
* into the window title bar area
* (requires enabled window decorations). Default is {@code false}.
* <p>
* On macOS, use client property {@code apple.awt.fullWindowContent}
* (see <a href="https://www.formdev.com/flatlaf/macos/#full_window_content">macOS Full window content</a>).
* <p>
* Setting this enables/disables full window content
* for the {@code JFrame} or {@code JDialog} that contains the root pane.
* <p>
* If {@code true}, the content pane (and the glass pane) is extended into
* the title bar area. The window icon and title are hidden.
* Only the iconfify/maximize/close buttons stay visible in the upper right corner
* (and overlap the content pane).
* <p>
* The user can left-click-and-drag on the title bar area to move the window,
* except when clicking on a component that processes mouse events (e.g. buttons or menus).
* <p>
* <strong>Component</strong> {@link javax.swing.JRootPane}<br>
* <strong>Value type</strong> {@link java.lang.Boolean}
*
* @since 3.4
*/
String FULL_WINDOW_CONTENT = "FlatLaf.fullWindowContent";

/**
* Contains the current bounds of the iconfify/maximize/close buttons
* (in root pane coordinates) if fullWindowContent mode is enabled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.function.Predicate;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JRootPane;
Expand Down Expand Up @@ -218,13 +219,13 @@ public static void setHasCustomDecoration( Window window, boolean hasCustomDecor
}

static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight,
List<Rectangle> hitTestSpots, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
Predicate<Point> captionHitTestCallback, Rectangle appIconBounds, Rectangle minimizeButtonBounds,
Rectangle maximizeButtonBounds, Rectangle closeButtonBounds )
{
if( !isSupported() )
return;

nativeProvider.updateTitleBarInfo( window, titleBarHeight, hitTestSpots,
nativeProvider.updateTitleBarInfo( window, titleBarHeight, captionHitTestCallback,
appIconBounds, minimizeButtonBounds, maximizeButtonBounds, closeButtonBounds );
}

Expand Down Expand Up @@ -270,7 +271,7 @@ public interface Provider
{
boolean hasCustomDecoration( Window window );
void setHasCustomDecoration( Window window, boolean hasCustomDecoration );
void updateTitleBarInfo( Window window, int titleBarHeight, List<Rectangle> hitTestSpots,
void updateTitleBarInfo( Window window, int titleBarHeight, Predicate<Point> captionHitTestCallback,
Rectangle appIconBounds, Rectangle minimizeButtonBounds, Rectangle maximizeButtonBounds,
Rectangle closeButtonBounds );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,28 @@ protected FlatTitlePane createTitlePane() {

// layer title pane under frame content layer to allow placing menu bar over title pane
protected final static Integer TITLE_PANE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 1;
private final static Integer TITLE_PANE_MOUSE_LAYER = JLayeredPane.FRAME_CONTENT_LAYER - 2;

// for fullWindowContent mode, layer title pane over frame content layer to allow placing title bar buttons over content
/** @since 3.4 */
protected final static Integer TITLE_PANE_FULL_WINDOW_CONTENT_LAYER = JLayeredPane.FRAME_CONTENT_LAYER + 1;

private Integer getLayerForTitlePane() {
return isFullWindowContent( rootPane ) ? TITLE_PANE_FULL_WINDOW_CONTENT_LAYER : TITLE_PANE_LAYER;
}

protected void setTitlePane( FlatTitlePane newTitlePane ) {
JLayeredPane layeredPane = rootPane.getLayeredPane();

if( titlePane != null )
if( titlePane != null ) {
layeredPane.remove( titlePane );
layeredPane.remove( titlePane.mouseLayer );
}

if( newTitlePane != null )
layeredPane.add( newTitlePane, TITLE_PANE_LAYER );
if( newTitlePane != null ) {
layeredPane.add( newTitlePane, getLayerForTitlePane() );
layeredPane.add( newTitlePane.mouseLayer, TITLE_PANE_MOUSE_LAYER );
}

titlePane = newTitlePane;
}
Expand Down Expand Up @@ -430,6 +443,17 @@ public void propertyChange( PropertyChangeEvent e ) {
titlePane.titleBarColorsChanged();
break;

case FlatClientProperties.FULL_WINDOW_CONTENT:
if( titlePane != null ) {
rootPane.getLayeredPane().setLayer( titlePane, getLayerForTitlePane() );
titlePane.updateIcon();
titlePane.updateVisibility();
titlePane.updateFullWindowContentButtonsBoundsProperty();
}
FullWindowContentSupport.revalidatePlaceholders( rootPane );
rootPane.revalidate();
break;

case FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS:
FullWindowContentSupport.revalidatePlaceholders( rootPane );
break;
Expand Down Expand Up @@ -471,11 +495,14 @@ public void propertyChange( PropertyChangeEvent e ) {
}
}

/** @since 3.4 */
protected static boolean isFullWindowContent( JRootPane rootPane ) {
return FlatClientProperties.clientPropertyBoolean( rootPane, FlatClientProperties.FULL_WINDOW_CONTENT, false );
}

protected static boolean isMenuBarEmbedded( JRootPane rootPane ) {
RootPaneUI ui = rootPane.getUI();
return ui instanceof FlatRootPaneUI &&
((FlatRootPaneUI)ui).titlePane != null &&
((FlatRootPaneUI)ui).titlePane.isMenuBarEmbedded();
FlatTitlePane titlePane = getTitlePane( rootPane );
return titlePane != null && titlePane.isMenuBarEmbedded();
}

/** @since 2.4 */
Expand Down Expand Up @@ -511,23 +538,21 @@ public Dimension maximumLayoutSize( Container parent ) {
private Dimension computeLayoutSize( Container parent, Function<Component, Dimension> getSizeFunc ) {
JRootPane rootPane = (JRootPane) parent;

Dimension titlePaneSize = (titlePane != null)
? getSizeFunc.apply( titlePane )
: new Dimension();
Dimension contentSize = (rootPane.getContentPane() != null)
? getSizeFunc.apply( rootPane.getContentPane() )
: rootPane.getSize();
: rootPane.getSize(); // same as in JRootPane.RootLayout.preferredLayoutSize()

int width = contentSize.width; // title pane width is not considered here
int height = titlePaneSize.height + contentSize.height;
int height = contentSize.height;
if( titlePane != null && !isFullWindowContent( rootPane ) )
height += getSizeFunc.apply( titlePane ).height;
if( titlePane == null || !titlePane.isMenuBarEmbedded() ) {
JMenuBar menuBar = rootPane.getJMenuBar();
Dimension menuBarSize = (menuBar != null && menuBar.isVisible())
? getSizeFunc.apply( menuBar )
: new Dimension();

width = Math.max( width, menuBarSize.width );
height += menuBarSize.height;
if( menuBar != null && menuBar.isVisible() ) {
Dimension menuBarSize = getSizeFunc.apply( menuBar );
width = Math.max( width, menuBarSize.width );
height += menuBarSize.height;
}
}

Insets insets = rootPane.getInsets();
Expand All @@ -552,12 +577,23 @@ public void layoutContainer( Container parent ) {
if( rootPane.getLayeredPane() != null )
rootPane.getLayeredPane().setBounds( x, y, width, height );

// title pane
// title pane (is a child of layered pane)
int nextY = 0;
if( titlePane != null ) {
int prefHeight = !isFullScreen ? titlePane.getPreferredSize().height : 0;
titlePane.setBounds( 0, 0, width, prefHeight );
nextY += prefHeight;
boolean isFullWindowContent = isFullWindowContent( rootPane );
if( isFullWindowContent && !UIManager.getBoolean( FlatTitlePane.KEY_DEBUG_SHOW_RECTANGLES ) ) {
// place title bar into top-right corner
int tw = Math.min( titlePane.getPreferredSize().width, width );
int tx = titlePane.getComponentOrientation().isLeftToRight() ? width - tw : 0;
titlePane.setBounds( tx, 0, tw, prefHeight );
} else
titlePane.setBounds( 0, 0, width, prefHeight );

titlePane.mouseLayer.setBounds( 0, 0, width, prefHeight );

if( !isFullWindowContent )
nextY += prefHeight;
}

// glass pane
Expand All @@ -568,21 +604,31 @@ public void layoutContainer( Container parent ) {
rootPane.getGlassPane().setBounds( x, y + offset, width, height - offset );
}

// menu bar
// menu bar (is a child of layered pane)
JMenuBar menuBar = rootPane.getJMenuBar();
if( menuBar != null && menuBar.isVisible() ) {
boolean embedded = !isFullScreen && titlePane != null && titlePane.isMenuBarEmbedded();
if( embedded ) {
titlePane.validate();
menuBar.setBounds( titlePane.getMenuBarBounds() );
} else {
int mx = 0;
int mw = width;
if( titlePane != null && isFullWindowContent( rootPane ) ) {
// make menu bar width smaller to avoid that it overlaps title bar buttons
int tw = Math.min( titlePane.getPreferredSize().width, width );
mw -= tw;
if( !titlePane.getComponentOrientation().isLeftToRight() )
mx = tw;
}

Dimension prefSize = menuBar.getPreferredSize();
menuBar.setBounds( 0, nextY, width, prefSize.height );
menuBar.setBounds( mx, nextY, mw, prefSize.height );
nextY += prefSize.height;
}
}

// content pane
// content pane (is a child of layered pane)
Container contentPane = rootPane.getContentPane();
if( contentPane != null )
contentPane.setBounds( 0, nextY, width, Math.max( height - nextY, 0 ) );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
*/
public class FlatSplitPaneUI
extends BasicSplitPaneUI
implements StyleableUI
implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
{
@Styleable protected String arrowType;
/** @since 3.3 */ @Styleable protected Color draggingColor;
Expand Down Expand Up @@ -227,6 +227,15 @@ private void paintDragDivider( Graphics g, int dividerLocation ) {
((FlatSplitPaneDivider)divider).paintStyle( g, x, y, width, height );
}

//---- interface FlatTitlePane.TitleBarCaptionHitTest ----

/** @since 3.4 */
@Override
public Boolean isTitleBarCaptionAt( int x, int y ) {
// necessary because BasicSplitPaneDivider adds some mouse listeners for dragging divider
return null; // check children
}

//---- class FlatSplitPaneDivider -----------------------------------------

protected class FlatSplitPaneDivider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@
*/
public class FlatTabbedPaneUI
extends BasicTabbedPaneUI
implements StyleableUI
implements StyleableUI, FlatTitlePane.TitleBarCaptionHitTest
{
// tab type
/** @since 2 */ protected static final int TAB_TYPE_UNDERLINED = 0;
Expand Down Expand Up @@ -2300,6 +2300,17 @@ private int rectsTotalHeight() {
return (rects[last].y + rects[last].height) - rects[0].y;
}

//---- interface FlatTitlePane.TitleBarCaptionHitTest ----

/** @since 3.4 */
@Override
public Boolean isTitleBarCaptionAt( int x, int y ) {
if( tabForCoordinate( tabPane, x, y ) >= 0 )
return false;

return null; // check children
}

//---- class TabCloseButton -----------------------------------------------

private static class TabCloseButton
Expand Down
Loading

0 comments on commit fbdc8d5

Please sign in to comment.