帆软报表设计器源代码。
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.
 
 
 
 

425 lines
14 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.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 java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
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 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 radius 圆角
*/
public static void paintWithComposite(Graphics g, Composite composite, Color background,
int width, int height, int radius) {
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, radius, radius));
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) {
FlatUIUtils.setRenderingHints(g2);
arc = scale(arc);
float t = scale(borderWidth);
float t2x = t * 2;
Path2D path2D = new Path2D.Float(Path2D.WIND_EVEN_ODD);
if (isLeftRoundButton(c)) {
path2D.append(createLeftRoundRectangle(x, y, width, height, arc), false);
path2D.append(createLeftRoundRectangle(x + t, y + t, width - t, height - t2x, arc - t), false);
} else {
path2D.append(createRightRoundRectangle(x, y, width, height, arc), false);
path2D.append(createRightRoundRectangle(x, y + t, width - t, height - t2x, arc - t), false);
}
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 - t), false);
g2.fill(path2D);
}
/**
* 创建一个部分圆角的矩形路径
*
* @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) {
Path2D path = new Path2D.Double(Path2D.WIND_EVEN_ODD, 7);
path.moveTo(x + arcTopLeft, y);
path.lineTo(x + width - arcTopRight, y);
path.quadTo(x + width, y, x + width, y + arcTopRight);
path.lineTo(x + width, y + height - arcBottomRight);
path.quadTo(x + width, y + height, x + width - arcBottomRight, y + height);
path.lineTo(x + arcBottomLeft, y + height);
path.quadTo(x, y + height, x, y + height - arcBottomLeft);
path.lineTo(x, y + arcTopLeft);
path.quadTo(x, y, x + arcTopLeft, 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)
).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));
}
}