This is an automated email from the ASF dual-hosted git repository. ebakke pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push: new 1e920a3a71 FlatLAF improvements and new configuration properties See the Pull Request for screenshots and links to related discussion/PRs. 1e920a3a71 is described below commit 1e920a3a713937430233e466c6afaa4813d06b05 Author: Eirik Bakke <eba...@ultorg.com> AuthorDate: Tue Jul 5 13:53:43 2022 -0400 FlatLAF improvements and new configuration properties See the Pull Request for screenshots and links to related discussion/PRs. Visual improvements, annotated in screenshot in Pull Request: 1) Align and connect borders between tabs and the container panel below, including on fractional HiDPI scalings (e.g. 150%). Some visual artifacts still exist, e.g. on 125% scaling, but they are less common, and not very visible. 2) Remove an unnecessary border around the editor area. 3) Make sure the "X" in editor tabs are closer to the label than to the separator line. 4) Add some top/bottom margin in the toolbar 5) On FlatLAF Dark, make the tab background color connect with the background of the Project/Files/Services and Navigator panes. 6) Slightly decrease the height of regular tab components. Other visual improvements: * On FlatLAF Dark and Light, make the tab hover effect more subtle. Avoid showing a hover effect on the already-selected tab. Use the blueish active tab area background as a base for this color, rather than plain gray. * Fix positioning of "spinner" icon in sidebar tabs (see illustration in PR 2967, which did the same on the Windows LAF). * Properly clip editor tab borders when tab is partially invisible due to tab scrolling. New configuration settings for FlatLAF.properties * EditorTab.selectedBackgroundBottomGradient (Not used in the current theme, but was tested.) * Add ViewTab/EditorTab.unscaledBorders (Not used in the current FlatLAF theme, but is used in the Windows LAF.) * EditorTab/ViewTab.selectedHoverBackground/selectedHoverForeground (Tweaks in these commits make use of selectedHoverBackground.) * ViewTab/EditorTab.showSelectedTabBorder (Used to explicitly enable borders around the selected tab.) --- .../netbeans/swing/laf/flatlaf/DPISafeBorder.java | 9 +- .../swing/laf/flatlaf/FlatDarkLaf.properties | 11 ++- .../netbeans/swing/laf/flatlaf/FlatLFCustoms.java | 4 +- .../netbeans/swing/laf/flatlaf/FlatLaf.properties | 11 ++- .../swing/laf/flatlaf/FlatLightLaf.properties | 7 +- .../org/netbeans/swing/laf/flatlaf/HiDPIUtils.java | 29 ++++-- .../laf/flatlaf/ui/FlatEditorTabCellRenderer.java | 102 ++++++++++++++------ .../laf/flatlaf/ui/FlatEditorTabDisplayerUI.java | 3 +- .../laf/flatlaf/ui/FlatViewTabDisplayerUI.java | 103 ++++++++++++++------- .../org/netbeans/swing/laf/flatlaf/ui/Utils.java | 10 ++ 10 files changed, 212 insertions(+), 77 deletions(-) diff --git a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/DPISafeBorder.java b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/DPISafeBorder.java index 1cb8de6182..1b6c387451 100644 --- a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/DPISafeBorder.java +++ b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/DPISafeBorder.java @@ -57,7 +57,14 @@ final class DPISafeBorder implements Border { @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { - HiDPIUtils.paintAtScale1x(g, x, y, width, height, this::paintBorderAtScale1x); + /* Detect borders which may need to visually connect with a prior adjacent component. This + applies in particular to view/editor tabs connecting to their tab containers underneath. + Round the starting position down towards the previous component to try to avoid a gap on + fractional HiDPI scalings (e.g. 150%). */ + boolean roundXdown = insets.left == 0; + boolean roundYdown = insets.top == 0; + HiDPIUtils.paintAtScale1x( + g, x, y, width, height, roundXdown, roundYdown, this::paintBorderAtScale1x); } private void paintBorderAtScale1x(Graphics2D g, int deviceWidth, int deviceHeight, double scale) { diff --git a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatDarkLaf.properties b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatDarkLaf.properties index 42d2af0991..374489a103 100644 --- a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatDarkLaf.properties +++ b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatDarkLaf.properties @@ -22,6 +22,10 @@ nb.preferred.color.profile=FlatLaf Dark nb.errorForeground=#DB5860 nb.warningForeground=@foreground +# Keep the tree background at @background rather than the very slightly lighter +# @componentBackground, to make the Projects/Files/Services and Navigator backgrounds connect +# seamlessly with the background of their tab on top. +Tree.background = @background #---- EditorView ---- @@ -36,12 +40,14 @@ EditorTab.activeBackground=shade($TabbedPane.underlineColor,75%) EditorTab.activeForeground=darken(@foreground,10%) EditorTab.selectedBackground=@background EditorTab.selectedForeground=@foreground -EditorTab.hoverBackground=lighten(@componentBackground,5%) +# Add the hover effect only for unselected tabs. +EditorTab.hoverBackground=lighten($EditorTab.activeBackground,7%) +EditorTab.selectedHoverBackground=$EditorTab.selectedBackground EditorTab.hoverForeground=@foreground EditorTab.attentionBackground=#E6C840 EditorTab.attentionForeground=#000 EditorTab.underlineColor=$TabbedPane.underlineColor -EditorTab.inactiveUnderlineColor=darken(@foreground,40%) +EditorTab.inactiveUnderlineColor=#00000000 EditorTab.tabSeparatorColor=darken($Component.borderColor,15%) #---- ViewTab ---- @@ -53,6 +59,7 @@ ViewTab.activeForeground=$EditorTab.activeForeground ViewTab.selectedBackground=$EditorTab.selectedBackground ViewTab.selectedForeground=$EditorTab.selectedForeground ViewTab.hoverBackground=$EditorTab.hoverBackground +ViewTab.selectedHoverBackground=$EditorTab.selectedHoverBackground ViewTab.hoverForeground=$EditorTab.hoverForeground ViewTab.attentionBackground=$EditorTab.attentionBackground ViewTab.attentionForeground=$EditorTab.attentionForeground diff --git a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLFCustoms.java b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLFCustoms.java index 0a4c510cff..5cf17babcb 100644 --- a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLFCustoms.java +++ b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLFCustoms.java @@ -82,8 +82,8 @@ public class FlatLFCustoms extends LFCustoms { VIEW_TAB_DISPLAYER_UI, "org.netbeans.swing.laf.flatlaf.ui.FlatViewTabDisplayerUI", // NOI18N SLIDING_BUTTON_UI, "org.netbeans.swing.laf.flatlaf.ui.FlatSlidingButtonUI", // NOI18N - EDITOR_TABSCOMPONENT_BORDER, DPISafeBorder.matte(0, 1, 1, 1, editorContentBorderColor), - EDITOR_TOOLBAR_BORDER, DPISafeBorder.matte(0, 0, 1, 0, editorContentBorderColor), + EDITOR_TABSCOMPONENT_BORDER, BorderFactory.createEmptyBorder(), + EDITOR_TOOLBAR_BORDER, new CompoundBorder(DPISafeBorder.matte(0, 0, 1, 0, editorContentBorderColor), BorderFactory.createEmptyBorder(1, 0, 1, 0)), EDITOR_TAB_CONTENT_BORDER, DPISafeBorder.matte(0, 1, 1, 1, editorContentBorderColor), VIEW_TAB_CONTENT_BORDER, DPISafeBorder.matte(0, 1, 1, 1, UIManager.getColor("TabbedContainer.view.contentBorderColor")), // NOI18N diff --git a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLaf.properties b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLaf.properties index a358f9d36c..be48cc194d 100644 --- a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLaf.properties +++ b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLaf.properties @@ -28,7 +28,12 @@ controlShadow=$Component.borderColor # hide grip in divider SplitPaneDivider.style = plain -TabbedPane.inactiveUnderlineColor = $EditorTab.inactiveUnderlineColor +#---- TabbedPane ---- + +# Decrease a bit from the default of 32. For comparison, a selected tab on the standard Swing +# Windows LAF is 22 pixels tall. +TabbedPane.tabHeight=30 +TabbedPane.inactiveUnderlineColor = $TabbedContainer.editor.contentBorderColor #---- TabbedContainer ---- @@ -39,11 +44,12 @@ TabbedContainer.view.contentBorderColor=$Component.borderColor #---- EditorTab ---- -EditorTab.tabInsets=5,6,7,8 +EditorTab.tabInsets=5,7,7,6 EditorTab.underlineHeight=3 EditorTab.underlineAtTop=true EditorTab.tabSeparatorColor=$Component.borderColor EditorTab.showTabSeparators=true +EditorTab.showSelectedTabBorder=true #---- ViewTab ---- @@ -52,6 +58,7 @@ ViewTab.underlineHeight=3 ViewTab.underlineAtTop=true ViewTab.tabSeparatorColor=$Component.borderColor ViewTab.showTabSeparators=true +ViewTab.showSelectedTabBorder=true #---- Multi-tabs ---- diff --git a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLightLaf.properties b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLightLaf.properties index 51a7791da4..234ece4f51 100644 --- a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLightLaf.properties +++ b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLightLaf.properties @@ -30,12 +30,14 @@ EditorTab.activeBackground=mix($EditorTab.background, $TabbedPane.underlineColor EditorTab.activeForeground=lighten(@foreground,25%) EditorTab.selectedBackground=@background EditorTab.selectedForeground=@foreground -EditorTab.hoverBackground=@componentBackground +# Add the hover effect only for unselected tabs. +EditorTab.hoverBackground=lighten($EditorTab.activeBackground,7%) +EditorTab.selectedHoverBackground=$EditorTab.selectedBackground EditorTab.hoverForeground=@foreground EditorTab.attentionBackground=#E6C840 EditorTab.attentionForeground=#000 EditorTab.underlineColor=$TabbedPane.underlineColor -EditorTab.inactiveUnderlineColor=lighten(@foreground,60%) +EditorTab.inactiveUnderlineColor=#00000000 #---- ViewTab ---- @@ -47,6 +49,7 @@ ViewTab.activeForeground=$EditorTab.activeForeground ViewTab.selectedBackground=$EditorTab.selectedBackground ViewTab.selectedForeground=$EditorTab.selectedForeground ViewTab.hoverBackground=$EditorTab.hoverBackground +ViewTab.selectedHoverBackground=$EditorTab.selectedHoverBackground ViewTab.hoverForeground=$EditorTab.hoverForeground ViewTab.attentionBackground=$EditorTab.attentionBackground ViewTab.attentionForeground=$EditorTab.attentionForeground diff --git a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/HiDPIUtils.java b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/HiDPIUtils.java index ba5bb0ed3f..16546a407f 100644 --- a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/HiDPIUtils.java +++ b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/HiDPIUtils.java @@ -38,6 +38,18 @@ public class HiDPIUtils { * {@link org.netbeans.swing.plaf.windows8.DPISafeBorder}. */ public static void paintAtScale1x(Graphics g0, int x, int y, int width, int height, HiDPIPainter painter) { + paintAtScale1x(g0, x, y, width, height, false, false, painter); + } + + /** + * @param roundXdown if true, round the starting X position down when converting to device + * pixels, otherwise round to the nearest device pixel position + * @param roundYdown if true, round the starting Y position down when converting to device + * pixels, otherwise round to the nearest device pixel position + */ + static void paintAtScale1x(Graphics g0, int x, int y, int width, int height, + boolean roundXdown, boolean roundYdown, HiDPIPainter painter) + { Graphics2D g = (Graphics2D) g0; final AffineTransform oldTransform = g.getTransform(); g.translate(x, y); @@ -54,13 +66,16 @@ public class HiDPIUtils { { // HiDPI scaling is active. double scale = tx.getScaleX(); - /* Round the starting (top-left) position up and the end (bottom-right) position down, - to ensure we are painting the border in an area that will not be painted over by an - adjacent component. */ - int deviceX = (int) Math.ceil(tx.getTranslateX()); - int deviceY = (int) Math.ceil(tx.getTranslateY()); - int deviceXend = (int) (tx.getTranslateX() + width * scale); - int deviceYend = (int) (tx.getTranslateY() + height * scale); + int deviceX = (int) (roundXdown + ? Math.floor(tx.getTranslateX()) + : Math.round(tx.getTranslateX())); + int deviceY = (int) (roundYdown + ? Math.floor(tx.getTranslateY()) + : Math.round(tx.getTranslateY())); + /* Round the the end (bottom-right) position down, to ensure we are painting the + border in an area that will not be painted over by an adjacent component. */ + int deviceXend = (int) Math.floor(tx.getTranslateX() + width * scale); + int deviceYend = (int) Math.floor(tx.getTranslateY() + height * scale); int deviceWidth = deviceXend - deviceX; int deviceHeight = deviceYend - deviceY; /* Deactivate the HiDPI scaling transform so we can do paint operations in the device diff --git a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatEditorTabCellRenderer.java b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatEditorTabCellRenderer.java index 3676ab1d24..3caaf98c82 100644 --- a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatEditorTabCellRenderer.java +++ b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatEditorTabCellRenderer.java @@ -23,6 +23,7 @@ import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.FontMetrics; +import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; @@ -41,19 +42,19 @@ import org.netbeans.swing.tabcontrol.plaf.TabPainter; * FlatLaf implementation of tab renderer */ public class FlatEditorTabCellRenderer extends AbstractTabCellRenderer { - - private static final int CLOSE_ICON_RIGHT_PAD = 2; - private static final Color background = UIManager.getColor("EditorTab.background"); // NOI18N private static final Color activeBackground = Utils.getUIColor("EditorTab.activeBackground", background); // NOI18N private static final Color selectedBackground = Utils.getUIColor("EditorTab.selectedBackground", activeBackground); // NOI18N + private static final Color selectedBackgroundBottomGradient = Utils.getUIColor("EditorTab.selectedBackgroundBottomGradient", selectedBackground); // NOI18N private static final Color hoverBackground = UIManager.getColor("EditorTab.hoverBackground"); // NOI18N + private static final Color selectedHoverBackground = Utils.getUIColor("EditorTab.selectedHoverBackground", hoverBackground); // NOI18N private static final Color attentionBackground = UIManager.getColor("EditorTab.attentionBackground"); // NOI18N private static final Color foreground = Utils.getUIColor( "EditorTab.foreground", "TabbedPane.foreground" ); // NOI18N private static final Color activeForeground = Utils.getUIColor( "EditorTab.activeForeground", foreground ); // NOI18N private static final Color selectedForeground = Utils.getUIColor( "EditorTab.selectedForeground", activeForeground ); // NOI18N private static final Color hoverForeground = Utils.getUIColor( "EditorTab.hoverForeground", foreground ); // NOI18N + private static final Color selectedHoverForeground = Utils.getUIColor( "EditorTab.selectedHoverForeground", hoverForeground ); // NOI18N private static final Color attentionForeground = Utils.getUIColor( "EditorTab.attentionForeground", foreground ); // NOI18N private static final Color underlineColor = UIManager.getColor("EditorTab.underlineColor"); // NOI18N @@ -66,14 +67,29 @@ public class FlatEditorTabCellRenderer extends AbstractTabCellRenderer { private static final boolean underlineAtTop = UIManager.getBoolean("EditorTab.underlineAtTop"); // NOI18N private static boolean showTabSeparators = UIManager.getBoolean("EditorTab.showTabSeparators"); // NOI18N - private static final FlatTabPainter painter = new FlatTabPainter(); + /** + * Margin on the right of the close button. Note that {@code tabInsets.right} denotes the space + * between the caption text and the "close" icon. Here, we set the right margin to the same + * value as the left margin (before the tab's caption), minus 2 pixels to account for the fact + * that the close button has some margin of its own between the "X" and the red box that is + * shown only on hover. + */ + private static final int CLOSE_ICON_RIGHT_PAD = tabInsets.left - 2; + + private static final boolean showSelectedTabBorder = Utils.getUIBoolean("EditorTab.showSelectedTabBorder", underlineAtTop); // NOI18N + private static final boolean unscaledBorders = Utils.getUIBoolean("EditorTab.unscaledBorders", false); // NOI18N + + private static final FlatTabPainter leftClipPainter = new FlatTabPainter(true, false); + private static final FlatTabPainter noClipPainter = new FlatTabPainter(false, false); + private static final FlatTabPainter rightClipPainter = new FlatTabPainter(false, true); boolean firstTab; boolean lastTab; boolean nextTabSelected; public FlatEditorTabCellRenderer() { - super(painter, new Dimension(tabInsets.left + tabInsets.right, tabInsets.top + tabInsets.bottom)); + super(leftClipPainter, noClipPainter, rightClipPainter, + new Dimension(tabInsets.left + tabInsets.right, tabInsets.top + tabInsets.bottom)); } @Override @@ -111,14 +127,16 @@ public class FlatEditorTabCellRenderer extends AbstractTabCellRenderer { // set text color setForeground(colorForState(foreground, activeForeground, selectedForeground, - hoverForeground, attentionForeground)); + selectedHoverForeground, hoverForeground, attentionForeground)); return result; } - private Color colorForState(Color normal, Color active, Color selected, Color hover, Color attention) { + private Color colorForState(Color normal, Color active, Color selected, + Color selectedHover, Color unselectedHover, Color attention) + { return isAttention() ? attention - : isArmed() ? hover + : isArmed() ? (isSelected() ? selectedHover : unselectedHover) : isSelected() ? selected : isActive() ? active : normal; @@ -132,6 +150,13 @@ public class FlatEditorTabCellRenderer extends AbstractTabCellRenderer { } private static class FlatTabPainter implements TabPainter { + final boolean leftClip; + final boolean rightClip; + + public FlatTabPainter(boolean leftClip, boolean rightClip) { + this.leftClip = leftClip; + this.rightClip = rightClip; + } @Override public Insets getBorderInsets(Component c) { @@ -153,7 +178,7 @@ public class FlatEditorTabCellRenderer extends AbstractTabCellRenderer { int iconWidth = icon.getIconWidth(); int iconHeight = icon.getIconHeight(); rect.x = bounds.x + bounds.width - iconWidth - UIScale.scale(CLOSE_ICON_RIGHT_PAD); - rect.y = bounds.y + (Math.max(0, bounds.height / 2 - iconHeight / 2)) - 1; + rect.y = bounds.y + Math.max(0, (bounds.height - iconHeight) / 2) - 1; rect.width = iconWidth; rect.height = iconHeight; } @@ -200,6 +225,19 @@ public class FlatEditorTabCellRenderer extends AbstractTabCellRenderer { paintCloseButton(g, ren); } + private static void fillGradientRect(Graphics2D g, int x, int y, int width, int height, + Color color, Color bottomGradient, int gradientOffset) + { + if (bottomGradient.equals(color)) { + g.setColor(color); + } else { + g.setPaint(new GradientPaint( + 0, y + gradientOffset, color, + 0, y + height - gradientOffset, bottomGradient, false)); + } + g.fillRect(x, y, width, height); + } + private void paintInteriorAtScale1x(Graphics2D g, Component c, int width, int height, double scale) { FlatEditorTabCellRenderer ren = (FlatEditorTabCellRenderer) c; boolean selected = ren.isSelected(); @@ -207,35 +245,43 @@ public class FlatEditorTabCellRenderer extends AbstractTabCellRenderer { // get background color Color bg = ren.colorForState( background, activeBackground, selectedBackground, - hoverBackground, attentionBackground); + selectedHoverBackground, hoverBackground, attentionBackground); boolean showSeparator = showTabSeparators && !selected && !ren.nextTabSelected - && !ren.lastTab; + && !ren.lastTab && !rightClip; - // do not round tab separator width to get nice small lines at 125%, 150% and 175% - int tabSeparatorWidth = showSeparator ? (int) (1 * scale) : 0; + int contentBorderWidth = unscaledBorders ? 1 : HiDPIUtils.deviceBorderWidth(scale, 1); + int tabSeparatorWidth = showSeparator ? contentBorderWidth : 0; + int underlineHeight = (int) Math.round(FlatEditorTabCellRenderer.underlineHeight * scale); - // paint background - g.setColor(bg); - g.fillRect(0, 0, width - (bg != background ? tabSeparatorWidth : 0), height); + fillGradientRect(g, 0, 0, width - (bg != background ? tabSeparatorWidth : 0), height, bg, + selected && !selectedBackground.equals(selectedBackgroundBottomGradient) + ? selectedBackgroundBottomGradient : bg, + (underlineAtTop ? underlineHeight : 0)); - if (selected && underlineHeight > 0) { - // paint underline if tab is selected - int underlineHeight = (int) Math.round(FlatEditorTabCellRenderer.underlineHeight * scale); - if (underlineAtTop) { + if (selected) { + if (showSelectedTabBorder) { g.setColor(contentBorderColor); - int borderWidth = (int) (1 * scale); - g.fillRect(0, 0, borderWidth, height); - g.fillRect(width - tabSeparatorWidth - borderWidth, 0, borderWidth, height); - g.setColor(ren.isActive() ? underlineColor : inactiveUnderlineColor); - g.fillRect(0, 0, width - tabSeparatorWidth, underlineHeight); - } else { + g.fillRect(0, 0, width - tabSeparatorWidth, contentBorderWidth); // Top + if (!leftClip) { + g.fillRect(0, 0, contentBorderWidth, height); // Left + } + if (!rightClip) { + g.fillRect(width - tabSeparatorWidth - contentBorderWidth, 0, contentBorderWidth, height); // Right + } + } + + if (underlineHeight > 0) { + // paint underline if tab is selected g.setColor(ren.isActive() ? underlineColor : inactiveUnderlineColor); - g.fillRect(0, height - underlineHeight, width - tabSeparatorWidth, underlineHeight); + if (underlineAtTop) { + g.fillRect(0, 0, width - tabSeparatorWidth, underlineHeight); + } else { + g.fillRect(0, height - underlineHeight, width - tabSeparatorWidth, underlineHeight); + } } } else { // paint bottom border - int contentBorderWidth = HiDPIUtils.deviceBorderWidth(scale, 1); g.setColor(contentBorderColor); g.fillRect(0, height - contentBorderWidth, width, contentBorderWidth); } diff --git a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatEditorTabDisplayerUI.java b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatEditorTabDisplayerUI.java index d4219aea5b..56475a806d 100644 --- a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatEditorTabDisplayerUI.java +++ b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatEditorTabDisplayerUI.java @@ -48,6 +48,7 @@ public class FlatEditorTabDisplayerUI extends BasicScrollingTabDisplayerUI { private final Color background = UIManager.getColor("EditorTab.background"); // NOI18N private final Color activeBackground = Utils.getUIColor("EditorTab.activeBackground", background); // NOI18N private final Color contentBorderColor = UIManager.getColor("TabbedContainer.editor.contentBorderColor"); // NOI18N + private final boolean unscaledBorders = Utils.getUIBoolean("EditorTab.unscaledBorders", false); // NOI18N private final Insets tabInsets = UIScale.scale(UIManager.getInsets("EditorTab.tabInsets")); // NOI18N public FlatEditorTabDisplayerUI(TabDisplayer displayer) { @@ -118,7 +119,7 @@ public class FlatEditorTabDisplayerUI extends BasicScrollingTabDisplayerUI { g.fillRect (0, 0, width, height); // paint bottom border - int contentBorderWidth = HiDPIUtils.deviceBorderWidth(scale, 1); + int contentBorderWidth = unscaledBorders ? 1 : HiDPIUtils.deviceBorderWidth(scale, 1); g.setColor(contentBorderColor); g.fillRect(0, height - contentBorderWidth, width, contentBorderWidth); } diff --git a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatViewTabDisplayerUI.java b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatViewTabDisplayerUI.java index a24a5ad552..b7c656e0ab 100644 --- a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatViewTabDisplayerUI.java +++ b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatViewTabDisplayerUI.java @@ -62,12 +62,14 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { activeBackground, // background of tabs and tabs area if view group is active; optional; defaults to foreground selectedBackground, // background of tab if tab is selected in active view group; optional; defaults to activeBackground hoverBackground, // background of tab if mouse is over tab + selectedHoverBackground, // if defined, use this color instead of hoverBackground for selected tabs attentionBackground, // background of tab if tab is in attension mode foreground, // text color if view group is inactive; optional; defaults to TabbedPane.foreground activeForeground, // text color if view group is active; optional; defaults to foreground selectedForeground, // text color if tab is selected in active view group; optional; defaults to activeForeground hoverForeground, // text color if mouse is over tab; optional; defaults to foreground + selectedHoverForeground, // if defined, use this color instead of hoverForeground for selected tabs attentionForeground, // text color if tab is in attension mode; optional; defaults to foreground underlineColor, // underline color of selected active tabs @@ -80,6 +82,9 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { private static boolean underlineAtTop; // paint "underline" at top of tab private static boolean showTabSeparators; // paint tab separators + private static boolean showSelectedTabBorder; // Paint a border around the selected tab + private static boolean unscaledBorders; // Leave the thickness of borders unaffected by HiDPI scaling + private Font font; public FlatViewTabDisplayerUI(TabDisplayer displayer) { @@ -133,14 +138,15 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { } } - // paint busy icon + final Icon busyIcon; + final int busyWidth; if (isTabBusy(index)) { - Icon busyIcon = BusyTabsSupport.getDefault().getBusyIcon(isSelected(index)); - availTxtWidth -= busyIcon.getIconWidth() - UIScale.scale(3) - txtLeftPad; - busyIcon.paintIcon(displayer, g, x + txtLeftPad, y + (height - busyIcon.getIconHeight()) / 2); - int busyWidth = busyIcon.getIconWidth() + UIScale.scale(3); - x += busyWidth; - width -= busyWidth; + busyIcon = BusyTabsSupport.getDefault().getBusyIcon(isSelected(index)); + busyWidth = busyIcon.getIconWidth() + UIScale.scale(3); + availTxtWidth -= busyWidth; + } else { + busyIcon = null; + busyWidth = 0; } // make sure that as much text as possible is shown (and avoid empty tabs) @@ -150,7 +156,7 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { : fm.stringWidth(text); if (realTxtWidth > availTxtWidth) { // add left and right insets to available width - int left = Math.min(txtLeftPad - 1, realTxtWidth - availTxtWidth); + int left = Math.min(txtLeftPad - 2, realTxtWidth - availTxtWidth); availTxtWidth += left + txtRightPad; txtLeftPad -= left; @@ -170,9 +176,15 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { } } + if (busyIcon != null) { + busyIcon.paintIcon(displayer, g, x + txtLeftPad, y + (height - busyIcon.getIconHeight()) / 2); + x += busyWidth; + width -= busyWidth; + } + // text color Color c = colorForState(index, foreground, activeForeground, selectedForeground, - hoverForeground, attentionForeground); + selectedHoverForeground, hoverForeground, attentionForeground); // paint text int txtX = x + txtLeftPad; @@ -192,7 +204,8 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { @Override protected void paintTabBorder(Graphics g, int index, int x, int y, int width, int height) { - // not using borders + /* In the showSelectedTabBorder case, we draw borders as part of paintTabBackground, for + consistency with FlatEditorTabCellRenderer. */ } @Override @@ -206,41 +219,60 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { }); } + private boolean shouldShowSeparator(int index) { + if (!showTabSeparators) { + return false; + } + /* Don't show separators around the selected tab, if there's already a border or color + contrast at its sides. */ + boolean selectedTabHasContrastWithAdjacentTabs = + showSelectedTabBorder || !selectedBackground.equals(activeBackground); + boolean separatorIsAdjacentToSelectedTab = isSelected(index) || isSelected(index + 1); + if (selectedTabHasContrastWithAdjacentTabs && separatorIsAdjacentToSelectedTab) { + return false; + } + // Show separators _between_ tabs (not after the last one), like for editor tabs. + return index >= 0 && index < getDataModel().size() - 1; + } + private void paintTabBackgroundAtScale1x(Graphics2D g, int index, int width, int height, double scale) { - // do not round tab separator width to get nice small lines at 125%, 150% and 175% - int tabSeparatorWidth = (showTabSeparators && index >= 0) ? (int) (1 * scale) : 0; + Color bg = colorForState(index, background, activeBackground, selectedBackground, + selectedHoverBackground, hoverBackground, attentionBackground); + + boolean showSeparator = shouldShowSeparator(index); + + int contentBorderWidth = unscaledBorders ? 1 : HiDPIUtils.deviceBorderWidth(scale, 1); + int tabSeparatorWidth = showSeparator ? contentBorderWidth : 0; // paint background - Color bg = colorForState(index, background, activeBackground, selectedBackground, - hoverBackground, attentionBackground); g.setColor(bg); g.fillRect(0, 0, width - (bg != background ? tabSeparatorWidth : 0), height); - if (isSelected(index) && underlineHeight > 0) { - // paint underline if tab is selected - int underlineHeight = (int) Math.round(this.underlineHeight * scale); - g.setColor(isActive() ? underlineColor : inactiveUnderlineColor); - if (underlineAtTop) { + if (isSelected(index)) { + if (showSelectedTabBorder) { g.setColor(contentBorderColor); - int borderWidth = (int) (1 * scale); - g.fillRect(0, 0, borderWidth, height); - g.fillRect(width - tabSeparatorWidth - borderWidth, 0, borderWidth, height); - g.setColor(isActive() ? underlineColor : inactiveUnderlineColor); - g.fillRect(0, 0, width - tabSeparatorWidth, underlineHeight); - } else { + g.fillRect(0, 0, width - tabSeparatorWidth, contentBorderWidth); // Top + g.fillRect(0, 0, contentBorderWidth, height); // Left + g.fillRect(width - tabSeparatorWidth - contentBorderWidth, 0, contentBorderWidth, height); // Right + } + + if (underlineHeight > 0) { + // paint underline if tab is selected + int underlineHeight = (int) Math.round(this.underlineHeight * scale); g.setColor(isActive() ? underlineColor : inactiveUnderlineColor); - g.fillRect(0, height - underlineHeight, width - tabSeparatorWidth, underlineHeight); + if (underlineAtTop) { + g.fillRect(0, 0, width - tabSeparatorWidth, underlineHeight); + } else { + g.fillRect(0, height - underlineHeight, width - tabSeparatorWidth, underlineHeight); + } } } else { // paint bottom border - int contentBorderWidth = HiDPIUtils.deviceBorderWidth(scale, 1); g.setColor(contentBorderColor); g.fillRect(0, height - contentBorderWidth, width, contentBorderWidth); } - if (showTabSeparators && index >= 0 && - ((!isSelected(index) && index < getDataModel().size() - 1 && !isSelected(index + 1)) - || selectedBackground.equals(activeBackground))) { + if (showSeparator) { int offset = (int) (4 * scale); g.setColor(tabSeparatorColor); g.fillRect(width - tabSeparatorWidth, offset, tabSeparatorWidth, height - (offset * 2) - 1); @@ -256,9 +288,11 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { super.paintDisplayerBackground(g, c); } - private Color colorForState(int index, Color normal, Color active, Color selected, Color hover, Color attention) { + private Color colorForState(int index, Color normal, Color active, Color selected, + Color selectedHover, Color unselectedHover, Color attention) + { return isAttention(index) ? attention - : isMouseOver(index) ? hover + : isMouseOver(index) ? (isSelected(index) ? selectedHover : unselectedHover) : isSelected(index) ? selected : isActive() ? active : normal; @@ -294,12 +328,14 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { activeBackground = Utils.getUIColor("ViewTab.activeBackground", background); // NOI18N selectedBackground = Utils.getUIColor("ViewTab.selectedBackground", activeBackground); // NOI18N hoverBackground = UIManager.getColor("ViewTab.hoverBackground"); // NOI18N + selectedHoverBackground = Utils.getUIColor("ViewTab.selectedHoverBackground", hoverBackground); // NOI18N attentionBackground = UIManager.getColor("ViewTab.attentionBackground"); // NOI18N foreground = Utils.getUIColor("ViewTab.foreground", "TabbedPane.foreground"); // NOI18N activeForeground = Utils.getUIColor("ViewTab.activeForeground", foreground); // NOI18N selectedForeground = Utils.getUIColor("ViewTab.selectedForeground", activeForeground); // NOI18N hoverForeground = Utils.getUIColor("ViewTab.hoverForeground", foreground); // NOI18N + selectedHoverForeground = Utils.getUIColor("ViewTab.selectedHoverForeground", hoverForeground); // NOI18N attentionForeground = Utils.getUIColor("ViewTab.attentionForeground", foreground); // NOI18N underlineColor = UIManager.getColor("ViewTab.underlineColor"); // NOI18N @@ -316,6 +352,9 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { tabInsets = UIScale.scale(tabInsets); underlineHeight = UIScale.scale(underlineHeight); + showSelectedTabBorder = Utils.getUIBoolean("ViewTab.showSelectedTabBorder", underlineAtTop); // NOI18N + unscaledBorders = Utils.getUIBoolean("ViewTab.unscaledBorders", false); // NOI18N + colorsReady = true; } } diff --git a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/Utils.java b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/Utils.java index c471a8bbcc..3aab25934b 100644 --- a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/Utils.java +++ b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/Utils.java @@ -37,6 +37,16 @@ class Utils { return (color != null) ? color : UIManager.getColor(defaultKey); } + static int getUIInt(String key, int defaultValue) { + Object value = UIManager.get(key); + return (value instanceof Integer) ? ((Integer) value) : defaultValue; + } + + static boolean getUIBoolean(String key, boolean defaultValue) { + Object value = UIManager.get(key); + return (value instanceof Boolean) ? ((Boolean) value) : defaultValue; + } + /** * Sets rendering hints used for painting. */ --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists