帆软报表设计器源代码。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

520 lines
18 KiB

package com.fine.theme.utils;
import com.fine.theme.light.ui.CollapsibleScrollBarLayerUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.fr.design.border.FineBorderFactory;
import com.fr.design.constants.LayoutConstants;
import com.fr.design.gui.icontainer.UIScrollPane;
import com.fr.design.gui.ilable.UILabel;
import com.fr.design.mainframe.DesignerContext;
import com.fr.design.mainframe.DesignerFrame;
import com.fr.design.mainframe.theme.edit.ui.LabelUtils;
import com.fr.design.i18n.DesignSizeI18nManager;
import com.fr.stable.os.OperatingSystem;
import com.fr.value.AtomicClearableLazyValue;
import javax.swing.JLabel;
import javax.swing.JLayer;
import javax.swing.ScrollPaneConstants;
import javax.swing.UIManager;
import javax.swing.JTextArea;
import java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
import java.lang.reflect.Field;
import static com.fine.swing.ui.layout.Layouts.cell;
import static com.fine.swing.ui.layout.Layouts.column;
import static com.fine.theme.light.ui.FineButtonUI.isLeftRoundButton;
import static com.formdev.flatlaf.util.UIScale.scale;
/**
* UI绘制的一些常用方法
*
* @author vito
* @since 11.0
* Created on 2023/11/3
*/
public class FineUIUtils {
public static final String LEFT = "LEFT";
public static final String RIGHT = "RIGHT";
public static final int RETINA_SCALE_FACTOR = 2;
/**
* 判断是否支持retina,制作一些特殊效果,如HIDPI图片绘制。
* retina 是一种特殊的效果,使用4个像素点模拟一个像素点,
* 因此在其他操作系统上,即使是高分屏也不具备retina的效果。
* 甚至还有劣化的效果以及更差的性能。
*
* @since 2023.11.16
*/
private static final AtomicClearableLazyValue<Boolean> RETINA = AtomicClearableLazyValue.create(() -> {
// 经过测试win11,ubuntu等,没有retina效果
if (!OperatingSystem.isMacos()) {
return false;
}
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice device = env.getDefaultScreenDevice();
try {
Field field = device.getClass().getDeclaredField("scale");
field.setAccessible(true);
Object scale = field.get(device);
if (scale instanceof Integer && (Integer) scale == RETINA_SCALE_FACTOR) {
return true;
}
} catch (Exception ignored) {
}
return false;
});
/**
* 是否支持 retina
*
* @return 是否支持 retina
*/
public static boolean getRetina() {
return RETINA.getValue();
}
/**
* 放弃 retina 判断结果,用于清理或者切换环境
*/
public static void clearRetina() {
RETINA.drop();
}
/**
* 通过key获取UI的颜色,如果没有则使用后备key获取
*
* @param key 颜色key
* @param defaultKey 颜色后备key
* @return 颜色
*/
public static Color getUIColor(String key, String defaultKey) {
Color color = UIManager.getColor(key);
return (color != null) ? color : UIManager.getColor(defaultKey);
}
/**
* 获取key指定的int值,如果没有则使用后备key获取
*
* @param key int所在的key
* @param defaultKey 后备key
* @return 长度
*/
public static int getUIInt(String key, String defaultKey) {
Object value = UIManager.get(key);
return (value instanceof Integer) ? (Integer) value : UIManager.getInt(defaultKey);
}
/**
* 获取key指定的int值,并根据dpi进行缩放
*
* @param key int所在的key
* @param defaultKey 后备key
* @return 长度
*/
public static int getAndScaleInt(String key, String defaultKey) {
int intNum = getUIInt(key, defaultKey);
return FineUIScale.scale(intNum);
}
/**
* 获取key指定的int值,并根据dpi进行缩放
*
* @param key int所在的key
* @param defaultInt 默认值
* @return 长度
*/
public static int getAndScaleInt(String key, int defaultInt) {
int intNum = FlatUIUtils.getUIInt(key, defaultInt);
return FineUIScale.scale(intNum);
}
/**
* 通过key获取UI的边距,如果没有则使用后备key获取
*
* @param key 边距key
* @param defaultKey 边距后备key
* @return 边距
*/
public static Insets getUIInsets(String key, String defaultKey) {
Insets margin = UIManager.getInsets(key);
return (margin != null) ? margin : UIManager.getInsets(defaultKey);
}
/**
* 通过key获取UI的边距,如果没有则使用后备边距
*
* @param key 边距key
* @param defaultInsets 后备边距
* @return 边距
*/
public static Insets getUIInsets(String key, Insets defaultInsets) {
Insets margin = UIManager.getInsets(key);
return (margin != null) ? margin : defaultInsets;
}
/**
* 通过key获取UI的边距,如果没有则使用后备边距,并根据dpi进行缩放
*
* @param key 边距key
* @param defaultInsets 后备边距
* @return 根据dpi缩放后的边距
*/
public static Insets getAndScaleUIInsets(String key, Insets defaultInsets) {
Insets margin = UIManager.getInsets(key);
Insets insets = (margin != null) ? margin : defaultInsets;
return FineUIScale.scale(insets);
}
/**
* 绘制混合图像,含圆角、背景色设置
*
* @param g 图像
* @param composite 混合图像
* @param background 背景色
* @param width 宽度
* @param height 高度
* @param arc 圆角
*/
public static void paintWithComposite(Graphics g, Composite composite, Color background,
int width, int height, int arc) {
Graphics2D g2d = (Graphics2D) g;
FlatUIUtils.setRenderingHints(g2d);
Composite oldComposite = g2d.getComposite();
g2d.setComposite(composite);
g2d.setColor(background);
g2d.fill(new RoundRectangle2D.Float(0, 0, width, height, arc, arc));
g2d.setComposite(oldComposite);
}
/**
* 绘制部分圆角矩形边框
*
* @param g2 Graphics2D
* @param x x坐标
* @param y y坐标
* @param width 宽度
* @param height 高度
* @param borderWidth 边框宽度
* @param arc 圆角
*/
public static void paintPartRoundButtonBorder(Component c, Graphics2D g2, int x, int y, int width, int height,
float borderWidth, float arc) {
if (isLeftRoundButton(c)) {
paintPartRoundButtonBorder(g2, x, y, width, height, borderWidth, arc, LEFT, false);
} else {
paintPartRoundButtonBorder(g2, x, y, width, height, borderWidth, arc, RIGHT, false);
}
}
/**
* 绘制部分圆角矩形边框
*
* @param g2 Graphics2D
* @param x x坐标
* @param y y坐标
* @param width 宽度
* @param height 高度
* @param borderWidth 边框宽度
* @param arc 圆角
* @param roundPart 圆角的方位,当前只能设置一侧
* @param closedPath 是否封闭,非圆角那一侧是否有边框,是为有边框
*/
public static void paintPartRoundButtonBorder(Graphics2D g2, int x, int y, int width, int height,
float borderWidth, float arc, String roundPart, boolean closedPath) {
FlatUIUtils.setRenderingHints(g2);
arc = scale(arc);
float t = scale(borderWidth);
float t2x = t * 2;
Path2D path2D = new Path2D.Float(Path2D.WIND_EVEN_ODD);
switch (roundPart) {
case LEFT: {
path2D.append(createLeftRoundRectangle(x, y, width, height, arc), false);
path2D.append(createLeftRoundRectangle(x + t, y + t,
width - (closedPath ? t2x : t), height - t2x, arc - t2x), false);
break;
}
case RIGHT:
default: {
path2D.append(createRightRoundRectangle(x, y, width, height, arc), false);
path2D.append(createRightRoundRectangle(x + (closedPath ? t : 0), y + t,
width - (closedPath ? t2x : t), height - t2x, arc - t2x), false);
break;
}
}
g2.fill(path2D);
}
/**
* 绘制圆角tab边框
*
* @param g2 Graphics2D
* @param x x坐标
* @param y y坐标
* @param width 宽度
* @param height 高度
* @param borderWidth 边框宽度
* @param arc 圆角
*/
public static void paintRoundTabBorder(Graphics2D g2, double x, double y, double width, double height,
float borderWidth, float arc) {
FlatUIUtils.setRenderingHints(g2);
arc = scale(arc);
float t = scale(borderWidth);
float t2x = t * 2;
Path2D path2D = new Path2D.Float(Path2D.WIND_EVEN_ODD);
path2D.append(createTopRoundRectangle(x, y, width, height, arc), false);
path2D.append(createTopRoundRectangle(x + t, y + t, width - t2x, height - t, arc - t2x), false);
g2.fill(path2D);
}
/**
* 创建一个部分圆角的矩形路径
* <p>
* 注意:
* 在swing中,UI的样式的 arc 数值是直径,而 css 中 border-radius 为半径,
* 因此我们配置的 arc 全部为 border-radius 的2倍。但是使用 Path2D 绘制时,
* 绘制方式其实是使用半径来进行计算的,为了保持调用一致,对外 API 还是以 arc
* 的形式,因此方法内部需要对 arc 进行取半处理,
*
* @param x x坐标
* @param y y坐标
* @param width 矩形宽度
* @param height 矩形高度
* @param arcTopLeft 左上圆角弧度
* @param arcTopRight 右上圆角弧度
* @param arcBottomRight 右下圆角弧度
* @param arcBottomLeft 左下圆角弧度
* @return 路径
*/
public static Path2D createPartRoundRectangle(double x, double y, double width, double height,
double arcTopLeft, double arcTopRight, double arcBottomRight, double arcBottomLeft) {
double radiusTopLeft = arcTopLeft / 2;
double radiusTopRight = arcTopRight / 2;
double radiusBottomLeft = arcBottomLeft / 2;
double radiusBottomRight = arcBottomRight / 2;
Path2D path = new Path2D.Double(Path2D.WIND_EVEN_ODD, 10);
path.moveTo(x + radiusTopLeft, y);
path.lineTo(x + width - radiusTopRight, y);
path.quadTo(x + width, y, x + width, y + radiusTopRight);
path.lineTo(x + width, y + height - radiusBottomRight);
path.quadTo(x + width, y + height, x + width - radiusBottomRight, y + height);
path.lineTo(x + radiusBottomLeft, y + height);
path.quadTo(x, y + height, x, y + height - radiusBottomLeft);
path.lineTo(x, y + radiusTopLeft);
path.quadTo(x, y, x + radiusTopLeft, y);
path.closePath();
return path;
}
/**
* 创建一个左圆角矩形路径
*
* @param x x坐标
* @param y y坐标
* @param width 矩形宽度
* @param height 矩形高度
* @param arc 圆角弧度
* @return 路径
*/
public static Path2D createLeftRoundRectangle(float x, float y, float width, float height, float arc) {
return createPartRoundRectangle(x, y, width, height, arc, 0, 0, arc);
}
/**
* 创建一个右圆角矩形路径
*
* @param x x坐标
* @param y y坐标
* @param width 矩形宽度
* @param height 矩形高度
* @param arc 圆角弧度
* @return 路径
*/
public static Path2D createRightRoundRectangle(float x, float y, float width, float height, float arc) {
return createPartRoundRectangle(x, y, width, height, 0, arc, arc, 0);
}
/**
* 创建一个顶圆角矩形路径
*
* @param x x坐标
* @param y y坐标
* @param width 矩形宽度
* @param height 矩形高度
* @param arc 圆角弧度
* @return 路径
*/
public static Path2D createTopRoundRectangle(double x, double y, double width, double height, double arc) {
return createPartRoundRectangle(x, y, width, height, arc, arc, 0, 0);
}
/**
* 创建一个左上圆角的矩形路径
*
* @param x x坐标
* @param y y坐标
* @param width 矩形宽度
* @param height 矩形高度
* @param arc 圆角弧度
* @return 路径
*/
public static Path2D createTopLeftRoundRectangle(float x, float y, float width, float height, float arc) {
return createPartRoundRectangle(x, y, width, height, arc, 0, 0, 0);
}
/**
* 创建一个左下圆角的矩形路径
*
* @param x x坐标
* @param y y坐标
* @param width 矩形宽度
* @param height 矩形高度
* @param arc 圆角弧度
* @return 路径
*/
public static Path2D createBottomLeftRoundRectangle(float x, float y, float width, float height, float arc) {
return createPartRoundRectangle(x, y, width, height, 0, 0, 0, arc);
}
/**
* 创建一个右上圆角的矩形路径
*
* @param x x坐标
* @param y y坐标
* @param width 矩形宽度
* @param height 矩形高度
* @param arc 圆角弧度
* @return 路径
*/
public static Path2D createTopRightRoundRectangle(float x, float y, float width, float height, float arc) {
return createPartRoundRectangle(x, y, width, height, 0, arc, 0, 0);
}
/**
* 创建一个右下圆角的矩形路径
*
* @param x x坐标
* @param y y坐标
* @param width 矩形宽度
* @param height 矩形高度
* @param arc 圆角弧度
* @return 路径
*/
public static Path2D createBottomRightRoundRectangle(float x, float y, float width, float height, float arc) {
return createPartRoundRectangle(x, y, width, height, 0, 0, arc, 0);
}
/**
* 标签加粗显示,并设置下划线;适用于小标题
*
* @param label 标签
*/
public static void wrapBoldLabelWithUnderline(JLabel label) {
label.setBorder(FineBorderFactory.createDefaultUnderlineBorder());
FineUIStyle.setStyle(label, FineUIStyle.LABEL_BOLD);
}
/**
* 面板元素头部添加小标题
*
* @param component 面板元素
* @param title 标题文本
* @return 包装面板
*/
public static Component wrapComponentWithTitle(Component component, String title) {
UILabel label = new UILabel(title);
wrapBoldLabelWithUnderline(label);
return column(LayoutConstants.VERTICAL_GAP,
cell(label), cell(component).weight(1.0)
).getComponent();
}
/**
* 基于组件创建一个UIScrollPane的装饰层,内部的ScrollPane仅当悬浮时显示滚动条
*
* @param c 组件
* @return UIScrollPane的装饰层
*/
public static JLayer<UIScrollPane> createCollapsibleScrollBarLayer(Component c) {
return new JLayer<>(new UIScrollPane(c, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER),
new CollapsibleScrollBarLayerUI());
}
/**
* 基于组件创建一个UIScrollPane的装饰层,内部的ScrollPane仅当悬浮时显示滚动条
*
* @param c 组件
* @param verticalPolicy 滚动条垂直显示策略
* @param horizontalPolicy 滚动条水平显示策略
* @return UIScrollPane的装饰层
*/
public static JLayer<UIScrollPane> createCollapsibleScrollBarLayer(Component c, int verticalPolicy, int horizontalPolicy) {
return new JLayer<>(new UIScrollPane(c, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER),
new CollapsibleScrollBarLayerUI(verticalPolicy, horizontalPolicy));
}
/**
* 设置组件字体大小,已适配字体缩放
*
* @param c 组件
* @param size 字体大小
*/
public static void setFontSize(Component c, int size) {
Font font = c.getFont();
Font newSizeFont = font.deriveFont(font.getStyle(), scale(size));
c.setFont(newSizeFont);
}
/**
* 获取缩放后的国际化尺寸
*
* @param i18nDimensionKey 国际化key值
* @return 缩放后的国际化尺寸
*/
public static Dimension getScaledI18nDimension(String i18nDimensionKey) {
return FineUIScale.scale(DesignSizeI18nManager.getInstance().i18nDimension(i18nDimensionKey));
}
/**
* 创建一个支持自动换行的提示文本
*
* @param text 显示的文本内容
* @return 自动换行提示文本
*/
public static JTextArea createAutoWrapTipLabel(String text) {
return LabelUtils.createAutoWrapLabel(text, FineUIUtils.getUIColor("Label.tipColor", "inactiveCaption"));
}
/**
* 基于设计器父面板,计算当前面板尺寸
* @param width 宽度比例
* @param height 高度比例
*
* @return 面板尺寸
*/
public static Dimension calPaneDimensionByContext(double width, double height) {
DesignerFrame parent = DesignerContext.getDesignerFrame();
return new Dimension((int) (parent.getWidth() * width),(int) (parent.getHeight() * height));
}
}