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 8f79d085ec Improve vertical text alignment in FlatLAF window system tabs. 8f79d085ec is described below commit 8f79d085ece3e25423e7e106336e60809e686a8c Author: Eirik Bakke <eba...@ultorg.com> AuthorDate: Thu Jul 7 22:49:45 2022 -0400 Improve vertical text alignment in FlatLAF window system tabs. This makes the tab labels align with the tab icon and the "X" button, with any font and at any font size. The font-agnostic calculation is useful because FlatLAF uses a different UI font on each OS. Accurate vertical alignment also allows more compact tab styles if desired in the future. See the Pull Request for before/after screenshots. Details: * API change: Allow AbstractTabCellRenderer subclasses to override text position calculation. * In FlatLAF, improve the calculation of tab labels' Y position, to avoid the need for ad hoc adjustments and to work with a variety of font families and font sizes. Ensure that text in view tabs and editor tabs still line up. * In FlatLAF, ensure a minimum tab height for smaller fonts, based on the expected icon size, regardless of inset settings. This will allow the same inset settings to work for e.g. Tahoma 11 and Segoe UI 12 (the old and new default fonts on Windows), and other fonts which may be used by default for FlatLAF on other operating systems. * In FlatEditorTabDisplayerUI.getPreferredSize, use the correct graphics context for font metrics calculations. * Also avoid unnecessary rounding error in the vertical centering of tab icons and buttons, by doing "(a-b)/2" instead of "a/2-b/2". This is done for all LAFs; the difference is at most one pixel compared to the old behavior. --- .../o.n.swing.laf.flatlaf/nbproject/project.xml | 2 +- .../laf/flatlaf/ui/FlatEditorTabCellRenderer.java | 37 +++++++++-------- .../laf/flatlaf/ui/FlatEditorTabDisplayerUI.java | 15 +++---- .../swing/laf/flatlaf/ui/FlatTabControlIcon.java | 3 +- .../laf/flatlaf/ui/FlatViewTabDisplayerUI.java | 20 +++++++--- platform/o.n.swing.tabcontrol/apichanges.xml | 17 ++++++++ platform/o.n.swing.tabcontrol/manifest.mf | 2 +- .../tabcontrol/plaf/AbstractTabCellRenderer.java | 46 ++++++++++++++-------- .../plaf/AbstractViewTabDisplayerUI.java | 4 +- .../plaf/BasicScrollingTabDisplayerUI.java | 4 +- 10 files changed, 95 insertions(+), 55 deletions(-) diff --git a/platform/o.n.swing.laf.flatlaf/nbproject/project.xml b/platform/o.n.swing.laf.flatlaf/nbproject/project.xml index 08b6834a67..fc8a5ffa34 100644 --- a/platform/o.n.swing.laf.flatlaf/nbproject/project.xml +++ b/platform/o.n.swing.laf.flatlaf/nbproject/project.xml @@ -71,7 +71,7 @@ <build-prerequisite/> <compile-dependency/> <run-dependency> - <specification-version>1.63</specification-version> + <specification-version>1.74</specification-version> </run-dependency> </dependency> <dependency> 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 3caaf98c82..fc0e0f8b67 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 @@ -22,13 +22,14 @@ import com.formdev.flatlaf.util.UIScale; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; -import java.awt.FontMetrics; +import java.awt.Font; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Polygon; import java.awt.Rectangle; +import java.awt.font.FontRenderContext; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.UIManager; @@ -102,23 +103,23 @@ public class FlatEditorTabCellRenderer extends AbstractTabCellRenderer { } @Override - protected int getCaptionYAdjustment() { - // Workaround for a issue in AbstractTabCellRenderer.paintIconAndText(Graphics), - // which uses font height (which includes font descent) to calculate Y-coordinate - // when available height is equal to font height (availH <= txtH), - // but HtmlRenderer.renderString() expects Y-coordinate at baseline. - // So the text is painted vertically out of center. - // - // This seems to be no issue with other LAFs because they seem to use - // TabPainter insets differently and the available height is larger than - // the font height (availH > txtH), in which case 3 pixels are removed from - // the Y-coordinate to avoid that the text is painted vertically out of center. - - FontMetrics fm = getFontMetrics(getFont()); - int txtH = fm.getHeight(); + protected int getCaptionYPosition(Graphics g) { + Font font = getFont(); + FontRenderContext frc = (g instanceof Graphics2D) + ? ((Graphics2D) g).getFontRenderContext() + : g.getFontMetrics(font).getFontRenderContext(); + /* Don't rely on FontMetrics.getAscent() to get the ascent; it can return values much bigger + than the actual, visual size of the letters. Use the actual height of a flat-topped + upper-case letter instead. */ + double txtVisualAscent = font.createGlyphVector(frc, "H") + .getVisualBounds().getHeight(); Insets ins = getInsets(); int availH = getHeight() - (ins.top + ins.bottom); - return (availH <= txtH) ? -fm.getDescent() : -1; + final int effectiveIconYAdjustment = 2 + getIconYAdjustment(); + + // Center the visual ascent portion of the text vertically with respect to the icon. + return ins.top + (int) Math.round((availH + txtVisualAscent) / 2) + + effectiveIconYAdjustment; } @Override @@ -178,7 +179,9 @@ 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 - iconHeight) / 2) - 1; + // Ad hoc adjustment. + int yAdjustment = 2; + rect.y = bounds.y + Math.max(0, (bounds.height - iconHeight) / 2) - 1 + yAdjustment; rect.width = iconWidth; rect.height = iconHeight; } 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 56475a806d..e7c0d22e31 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 @@ -66,15 +66,12 @@ public class FlatEditorTabDisplayerUI extends BasicScrollingTabDisplayerUI { @Override public Dimension getPreferredSize(JComponent c) { - int prefHeight; - Graphics g = BasicScrollingTabDisplayerUI.getOffscreenGraphics(); - if (g != null) { - FontMetrics fm = g.getFontMetrics(displayer.getFont()); - Insets ins = getTabAreaInsets(); - prefHeight = fm.getHeight() + ins.top + ins.bottom - + tabInsets.top + tabInsets.bottom; - } else - prefHeight = UIScale.scale(28); + Graphics g = BasicScrollingTabDisplayerUI.getOffscreenGraphics(c); + FontMetrics fm = g.getFontMetrics(displayer.getFont()); + Insets ins = getTabAreaInsets(); + // Standard icons are 16 pixels tall, so always allocate space for them. + int prefHeight = Math.max(fm.getHeight(), 16) + ins.top + ins.bottom + + tabInsets.top + tabInsets.bottom; return new Dimension(displayer.getWidth(), prefHeight); } diff --git a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatTabControlIcon.java b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatTabControlIcon.java index c5064981b5..4b7eb85909 100644 --- a/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatTabControlIcon.java +++ b/platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/ui/FlatTabControlIcon.java @@ -110,7 +110,8 @@ public final class FlatTabControlIcon extends VectorIcon { } private FlatTabControlIcon(int buttonId, Integer buttonState) { - super(UIScale.scale(16), UIScale.scale(16)); + super(UIScale.scale(16), + UIScale.scale(buttonId == TabControlButton.ID_CLOSE_BUTTON ? 15 : 16)); this.buttonId = buttonId; this.buttonState = buttonState; this.userScaleFactor = UIScale.getUserScaleFactor(); 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 b7c656e0ab..0faa6b2053 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 @@ -110,7 +110,10 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { @Override public Dimension getPreferredSize(JComponent c) { FontMetrics fm = getTxtFontMetrics(); - int height = fm.getHeight() + tabInsets.top + tabInsets.bottom; + /* In FlatEditorTabDisplayerUI, we always allocate 16 pixels for the icon. Do the same here, + even though view tabs do not normally have icons, so that view tabs always line up with + editor tabs. */ + int height = Math.max(16, fm.getHeight()) + tabInsets.top + tabInsets.bottom; return new Dimension(100, height); } @@ -133,7 +136,9 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { } else { buttons.setVisible(true); availTxtWidth -= (buttonsSize.width + ICON_X_PAD); - buttons.setLocation(x + width - buttonsSize.width - ICON_X_PAD, y + ((height - buttonsSize.height) / 2) - 1); + // Ad hoc adjustment. + int yAdjustment = 2; + buttons.setLocation(x + width - buttonsSize.width - ICON_X_PAD, y + ((height - buttonsSize.height) / 2) - 1 + yAdjustment); } } } @@ -188,16 +193,19 @@ public class FlatViewTabDisplayerUI extends AbstractViewTabDisplayerUI { // paint text int txtX = x + txtLeftPad; - int txtY = y + tabInsets.top + fm.getAscent(); int availH = height - tabInsets.top - tabInsets.bottom; - if (availH > fm.getHeight()) { - txtY += (availH - fm.getHeight()) / 2; - } int style = HtmlRenderer.STYLE_TRUNCATE; if (!isSelected(index)) { // center text of unselected tabs txtX = Math.max(x + 1, x + ((width - realTxtWidth) / 2)); } + + /* Keep the txtY calculation the same as for FlatEditorTabCellRenderer, with an offset that + makes the text in view tabs and editor tabs always line up. */ + double txtVisualAscent = getTxtFont().createGlyphVector(fm.getFontRenderContext(), "H") + .getVisualBounds().getHeight(); + int txtY = tabInsets.top + (int) Math.round((availH + txtVisualAscent) / 2) + 1; + HtmlRenderer.renderString(text, g, txtX, txtY, availTxtWidth, height, getTxtFont(), c, style, true); } diff --git a/platform/o.n.swing.tabcontrol/apichanges.xml b/platform/o.n.swing.tabcontrol/apichanges.xml index 3e45572bd4..42fef25a2a 100644 --- a/platform/o.n.swing.tabcontrol/apichanges.xml +++ b/platform/o.n.swing.tabcontrol/apichanges.xml @@ -85,6 +85,23 @@ is the proper place. <changes> + <change> + <api name="tabcontrol"/> + <summary>Allow tab control renderers to override text position calculation</summary> + <version major="1" minor="74"/> + <date day="8" month="7" year="2022"/> + <author login="ebakke"/> + <compatibility addition="yes"/> + <description> + A new method <code>AbstractTabCellRenderer.getCaptionYPosition</code> + may now be overridden by subclasses, to customize the calculation of + the tab label's Y position. This allows specific implementations to + improve the calculation code without affecting other LAFs (which + may contain adjustments hard-coded to work with the old calculation.) + </description> + <class package="org.netbeans.swing.tabcontrol.plaf" name="AbstractTabCellRenderer"/> + </change> + <change id="autoscroll"> <api name="tabcontrol"/> <summary>Added ability to scroll document tabs when dragging a TopComponent diff --git a/platform/o.n.swing.tabcontrol/manifest.mf b/platform/o.n.swing.tabcontrol/manifest.mf index 0e2df427ad..de5acb779e 100644 --- a/platform/o.n.swing.tabcontrol/manifest.mf +++ b/platform/o.n.swing.tabcontrol/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module-Localizing-Bundle: org/netbeans/swing/tabcontrol/Bundle.properties OpenIDE-Module: org.netbeans.swing.tabcontrol -OpenIDE-Module-Specification-Version: 1.73 +OpenIDE-Module-Specification-Version: 1.74 AutoUpdate-Essential-Module: true diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java index 60f4a1d5c9..2037d0c00a 100644 --- a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java +++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractTabCellRenderer.java @@ -425,7 +425,9 @@ public abstract class AbstractTabCellRenderer extends JLabel paintIconAndText(g); } - /** Return non-zero to shift the text up or down by the specified number of pixels when painting. + /** + * Return non-zero to shift the text up or down by the specified number of pixels when painting. + * Used by the default implementation of {@link #getCaptionYPosition(Graphics)}. * * @return A positive or negative number of pixels */ @@ -442,12 +444,14 @@ public abstract class AbstractTabCellRenderer extends JLabel } /** - * Actually paints the icon and text (using the lightweight HTML renderer) + * Get the Y position of the caption. May be overridden. The default implementation uses an old + * formula which is retained for backwards compatibility (since subclasses may have tuned their + * value of {@link #getCaptionYAdjustment()} based on it). * * @param g The graphics context + * @return The Y position of the caption's baseline */ - protected void paintIconAndText(Graphics g) { - g.setFont(getFont()); + protected int getCaptionYPosition(Graphics g) { FontMetrics fm = g.getFontMetrics(getFont()); //Find out what height we need int txtH = fm.getHeight(); @@ -460,6 +464,23 @@ public abstract class AbstractTabCellRenderer extends JLabel } else { txtY = txtH + ins.top; } + txtY += getCaptionYAdjustment(); + return txtY; + } + + /** + * Actually paints the icon and text (using the lightweight HTML renderer) + * + * @param g The graphics context + */ + protected void paintIconAndText(Graphics g) { + g.setFont(getFont()); + FontMetrics fm = g.getFontMetrics(getFont()); + //Find out what height we need + int txtH = fm.getHeight(); + Insets ins = getInsets(); + //find out the available height + int availH = getHeight() - (ins.top + ins.bottom); int txtX; int centeringToAdd = getPixelsToAddToSelection() != 0 ? @@ -467,18 +488,11 @@ public abstract class AbstractTabCellRenderer extends JLabel Icon icon = getIcon(); //Check the icon non-null and height (see TabData.NO_ICON for why) - if (!isClipLeft() && icon != null && icon.getIconWidth() > 0 - && icon.getIconHeight() > 0) { - int iconY; - if (availH > icon.getIconHeight()) { - //add 2 to make sure icon top pixels are not cut off by outline - iconY = ins.top - + ((availH / 2) - (icon.getIconHeight() / 2)) + if (!isClipLeft() && icon != null && icon.getIconWidth() > 0 && icon.getIconHeight() > 0) { + int iconY = ins.top + + Math.max(0, (availH - icon.getIconHeight())) / 2 + //add 2 to make sure icon top pixels are not cut off by outline + 2; - } else { - //add 2 to make sure icon top pixels are not cut off by outline - iconY = ins.top + 2; - } int iconX = ins.left + centeringToAdd; iconY += getIconYAdjustment(); @@ -495,7 +509,7 @@ public abstract class AbstractTabCellRenderer extends JLabel txtX += 5; } - txtY += getCaptionYAdjustment(); + int txtY = getCaptionYPosition(g); //Get the available horizontal pixels for text int txtW = getWidth() - (txtX + ins.right); diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractViewTabDisplayerUI.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractViewTabDisplayerUI.java index e8b88732d5..6fe907cb10 100644 --- a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractViewTabDisplayerUI.java +++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AbstractViewTabDisplayerUI.java @@ -225,7 +225,7 @@ public abstract class AbstractViewTabDisplayerUI extends TabDisplayerUI { if( null != btnClose ) { Icon icon = btnClose.getIcon(); - btnClose.setBounds( width, height/2-icon.getIconHeight()/2, icon.getIconWidth(), icon.getIconHeight() ); + btnClose.setBounds( width, (height - icon.getIconHeight()) / 2, icon.getIconWidth(), icon.getIconHeight() ); width += icon.getIconWidth(); } @@ -233,7 +233,7 @@ public abstract class AbstractViewTabDisplayerUI extends TabDisplayerUI { if( 0 != width ) width += ICON_X_PAD; Icon icon = btnAutoHidePin.getIcon(); - btnAutoHidePin.setBounds( width, height/2-icon.getIconHeight()/2, icon.getIconWidth(), icon.getIconHeight() ); + btnAutoHidePin.setBounds( width, (height - icon.getIconHeight()) / 2, icon.getIconWidth(), icon.getIconHeight() ); width += icon.getIconWidth(); width += ICON_X_PAD; } diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicScrollingTabDisplayerUI.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicScrollingTabDisplayerUI.java index 1bdc632e7f..72cafe6278 100644 --- a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicScrollingTabDisplayerUI.java +++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/BasicScrollingTabDisplayerUI.java @@ -383,7 +383,7 @@ public abstract class BasicScrollingTabDisplayerUI extends BasicTabDisplayerUI { /** * Provides an offscreen graphics context so that widths based on character - * size can be calculated correctly before the component is shown. + * size can be calculated correctly before the component is shown. Never returns null. * * <p>For more accurate text measurements, clients should prefer calling * {@link #getOffscreenGraphics(JComponent)}. @@ -399,7 +399,7 @@ public abstract class BasicScrollingTabDisplayerUI extends BasicTabDisplayerUI { /** * Provides an offscreen graphics context so that widths based on character - * size can be calculated correctly before the component is shown + * size can be calculated correctly before the component is shown. Never returns null. * * @param component may be null without causing fatal errors, but should be set for accurate * text measurement (especially on displays with HiDPI scaling enabled) --------------------------------------------------------------------- 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