blob: 49882f6326b1241989e6d25d4dd5ee56b20bc432 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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 org.netbeans.swing.laf.flatlaf.ui;
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.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Polygon;
import java.awt.Rectangle;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.UIManager;
import org.netbeans.swing.laf.flatlaf.HiDPIUtils;
import org.netbeans.swing.tabcontrol.TabDisplayer;
import org.netbeans.swing.tabcontrol.plaf.AbstractTabCellRenderer;
import org.netbeans.swing.tabcontrol.plaf.TabControlButton;
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 hoverBackground = UIManager.getColor("EditorTab.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 attentionForeground = Utils.getUIColor( "EditorTab.attentionForeground", foreground ); // NOI18N
private static final Color underlineColor = UIManager.getColor("EditorTab.underlineColor"); // NOI18N
private static final Color inactiveUnderlineColor = UIManager.getColor("EditorTab.inactiveUnderlineColor"); // NOI18N
private static final Color tabSeparatorColor = UIManager.getColor("EditorTab.tabSeparatorColor"); // NOI18N
private static final Color contentBorderColor = UIManager.getColor("TabbedContainer.editor.contentBorderColor"); // NOI18N
private static final Insets tabInsets = UIScale.scale(UIManager.getInsets("EditorTab.tabInsets")); // NOI18N
private static final int underlineHeight = UIScale.scale(UIManager.getInt("EditorTab.underlineHeight")); // NOI18N
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();
boolean nextTabSelected;
public FlatEditorTabCellRenderer() {
super(painter, new Dimension(tabInsets.left + tabInsets.right, tabInsets.top + tabInsets.bottom));
}
@Override
public Dimension getPadding() {
Dimension d = super.getPadding();
if (isShowCloseButton() && !Boolean.getBoolean("nb.tabs.suppressCloseButton")) { // NOI18N
d.width += findCloseIcon().getIconWidth() + UIScale.scale(CLOSE_ICON_RIGHT_PAD);
}
return d;
}
@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();
Insets ins = getInsets();
int availH = getHeight() - (ins.top + ins.bottom);
return (availH <= txtH) ? -fm.getDescent() : -1;
}
@Override
protected int stateChanged(int oldState, int newState) {
int result = super.stateChanged(oldState, newState);
// set text color
setForeground(colorForState(foreground, activeForeground, selectedForeground,
hoverForeground, attentionForeground));
return result;
}
private Color colorForState(Color normal, Color active, Color selected, Color hover, Color attention) {
return isAttention() ? attention
: isArmed() ? hover
: isSelected() ? selected
: isActive() ? active
: normal;
}
private Icon findCloseIcon() {
return FlatTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON,
inCloseButton()
? (isPressed() ? TabControlButton.STATE_PRESSED : TabControlButton.STATE_ROLLOVER)
: TabControlButton.STATE_DEFAULT);
}
private static class FlatTabPainter implements TabPainter {
@Override
public Insets getBorderInsets(Component c) {
return tabInsets;
}
@Override
public void getCloseButtonRectangle(JComponent jc, Rectangle rect, Rectangle bounds) {
FlatEditorTabCellRenderer ren = (FlatEditorTabCellRenderer) jc;
if (!ren.isShowCloseButton()) {
rect.x = -100;
rect.y = -100;
rect.width = 0;
rect.height = 0;
return;
}
Icon icon = ren.findCloseIcon();
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.width = iconWidth;
rect.height = iconHeight;
}
@Override
public Polygon getInteriorPolygon(Component c) {
int x = 0;
int y = 0;
int width = c.getWidth();
int height = c.getHeight();
//just a plain rectangle
Polygon p = new Polygon();
p.addPoint(x, y);
p.addPoint(x + width, y);
p.addPoint(x + width, y + height);
p.addPoint(x, y + height);
return p;
}
@Override
public boolean isBorderOpaque() {
return true;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
// not using borders
}
@Override
public void paintInterior(Graphics g, Component c) {
// Paint the whole tab background at scale 1x (on HiDPI screens).
// Necessary so that it aligns nicely at bottom left and right edges
// with the content border, which is also painted at scale 1x.
HiDPIUtils.paintAtScale1x(g, 0, 0, c.getWidth(), c.getHeight(),
(gd, width, height, scale) -> {
paintInteriorAtScale1x(gd, c, width, height, scale);
});
// paint close button
FlatEditorTabCellRenderer ren = (FlatEditorTabCellRenderer) c;
if (!ren.isClipLeft() && !ren.isClipRight())
paintCloseButton(g, ren);
}
private void paintInteriorAtScale1x(Graphics2D g, Component c, int width, int height, double scale) {
FlatEditorTabCellRenderer ren = (FlatEditorTabCellRenderer) c;
boolean selected = ren.isSelected();
// get background color
Color bg = ren.colorForState(
background, activeBackground, selectedBackground,
hoverBackground, attentionBackground);
boolean showSeparator = showTabSeparators && !selected && !ren.nextTabSelected;
// do not round tab separator width to get nice small lines at 125%, 150% and 175%
int tabSeparatorWidth = showSeparator ? (int) (1 * scale) : 0;
// paint background
g.setColor(bg);
g.fillRect(0, 0, width - (bg != background ? tabSeparatorWidth : 0), height);
if (selected && underlineHeight > 0) {
// paint underline if tab is selected
int underlineHeight = (int) Math.round(FlatEditorTabCellRenderer.underlineHeight * scale);
g.setColor(ren.isActive() ? underlineColor : inactiveUnderlineColor);
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 (showSeparator) {
int offset = (int) (4 * scale);
g.setColor(tabSeparatorColor);
g.fillRect(width - tabSeparatorWidth, offset, tabSeparatorWidth, height - (offset * 2) - 1);
}
}
private void paintCloseButton(Graphics g, FlatEditorTabCellRenderer ren) {
//Get the close button bounds, more or less
Rectangle r = new Rectangle();
getCloseButtonRectangle(ren, r, new Rectangle(0, 0,
ren.getWidth(),
ren.getHeight()));
if (!g.hitClip(r.x, r.y, r.width, r.height)) {
return;
}
//paint close button
Icon icon = ren.findCloseIcon();
icon.paintIcon(ren, g, r.x, r.y);
}
@Override
public boolean supportsCloseButton(JComponent renderer) {
return renderer instanceof TabDisplayer ?
((TabDisplayer) renderer).isShowCloseButton() : true;
}
}
}