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
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)); |
|
} |
|
}
|
|
|