Levy.Xie-解安森
3 months ago
1886 changed files with 35898 additions and 21109 deletions
@ -0,0 +1,68 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import com.fr.essential.errorprone.annotations.Immutable; |
||||
import org.jetbrains.annotations.NotNull; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import javax.swing.Icon; |
||||
import java.awt.Dimension; |
||||
import java.util.Collection; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
/** |
||||
* 抽象图标集 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/15 |
||||
*/ |
||||
@Immutable |
||||
public abstract class AbstractIconSet implements IconSet { |
||||
|
||||
protected String name; |
||||
protected boolean dark; |
||||
|
||||
private final Map<String, IconSource<? extends Icon>> iconSourceMap = new ConcurrentHashMap<>(64); |
||||
private final Map<String, Icon> iconCache = new ConcurrentHashMap<>(64); |
||||
|
||||
public AbstractIconSet() { |
||||
} |
||||
|
||||
@Override |
||||
public @NotNull String getId() { |
||||
return name; |
||||
} |
||||
|
||||
@Override |
||||
public @NotNull Collection<String> getIds() { |
||||
return iconSourceMap.keySet(); |
||||
} |
||||
|
||||
@Override |
||||
public void addIcon(@NotNull IconSource<? extends Icon> icon) { |
||||
iconSourceMap.put(icon.getId(), icon); |
||||
} |
||||
|
||||
@SafeVarargs |
||||
@Override |
||||
public final void addIcon(@NotNull IconSource<? extends Icon>... icons) { |
||||
for (IconSource<? extends Icon> icon : icons) { |
||||
iconSourceMap.put(icon.getId(), icon); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public @Nullable Icon findIcon(@NotNull String id, @NotNull Dimension dimension, IconType type) { |
||||
String cacheKey = IconManager.genCacheKey(id, dimension, type); |
||||
Icon icon = iconCache.get(cacheKey); |
||||
if (icon == null) { |
||||
IconSource<? extends Icon> iconSource = iconSourceMap.get(id); |
||||
if (iconSource != null) { |
||||
icon = iconSource.loadIcon(dimension, type); |
||||
iconCache.put(cacheKey, icon); |
||||
} |
||||
} |
||||
return icon; |
||||
} |
||||
} |
@ -0,0 +1,109 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import com.fr.third.errorprone.annotations.Immutable; |
||||
import org.jetbrains.annotations.NotNull; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import javax.swing.Icon; |
||||
import java.awt.Dimension; |
||||
|
||||
|
||||
/** |
||||
* 抽象图标源 |
||||
* <p> |
||||
* 1. 记录图标来源的所有信息 |
||||
* 2. 如果使用来源提供灰化和反白图标,source负责提供图标的不同来源 |
||||
* 3. 默认查找命名,如图标名称为 path/pic.svg,根据以下规则自动查询灰化和反白图标 |
||||
* 1). 灰化图 path/pic_disable.svg |
||||
* 2). 反白图 path/pic_white.svg |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/14 |
||||
*/ |
||||
@Immutable |
||||
public abstract class AbstractIconSource<I extends Icon> implements IconSource<I> { |
||||
|
||||
protected String id; |
||||
|
||||
protected final IconResource resource; |
||||
|
||||
@Nullable |
||||
protected IconResource grayResource; |
||||
|
||||
@Nullable |
||||
protected IconResource whiteResource; |
||||
|
||||
|
||||
public AbstractIconSource(@NotNull final String id, @NotNull final IconResource resource) { |
||||
this.id = id; |
||||
this.resource = resource; |
||||
} |
||||
|
||||
|
||||
public AbstractIconSource(@NotNull final String id, |
||||
@NotNull final IconResource resource, |
||||
@Nullable final IconResource grayResource, |
||||
@Nullable final IconResource whiteResource) { |
||||
this.id = id; |
||||
this.resource = resource; |
||||
this.grayResource = grayResource; |
||||
this.whiteResource = whiteResource; |
||||
} |
||||
|
||||
@NotNull |
||||
@Override |
||||
public String getId() { |
||||
return id; |
||||
} |
||||
|
||||
@NotNull |
||||
@Override |
||||
public IconResource getResource() { |
||||
return resource; |
||||
} |
||||
|
||||
@NotNull |
||||
@Override |
||||
public I loadIcon(Dimension dimension, IconType type) { |
||||
try { |
||||
switch (type) { |
||||
case disable: |
||||
return loadDisabledIcon(dimension); |
||||
case white: |
||||
return white(dimension); |
||||
default: |
||||
return loadIcon(resource, dimension, type); |
||||
} |
||||
|
||||
} catch (final Exception e) { |
||||
throw new IconException("Unable to load Icon: " + getId(), e); |
||||
} |
||||
} |
||||
|
||||
@NotNull |
||||
protected abstract I loadIcon(@NotNull IconResource resource, Dimension dimension, IconType type); |
||||
|
||||
|
||||
/** |
||||
* 先找提供明确URL的灰化图, |
||||
* 再找指定自动寻找路径的灰化图, |
||||
* 最后再由具体图标提供默认灰化图 |
||||
* |
||||
* @return 灰化图 |
||||
*/ |
||||
private @NotNull I loadDisabledIcon(Dimension dimension) { |
||||
if (grayResource != null) { |
||||
return loadIcon(grayResource, dimension, IconType.normal); |
||||
} |
||||
return loadIcon(resource, dimension, IconType.disable); |
||||
} |
||||
|
||||
private @NotNull I white(Dimension dimension) { |
||||
if (whiteResource != null) { |
||||
return loadIcon(whiteResource, dimension, IconType.normal); |
||||
} |
||||
return loadIcon(resource, dimension, IconType.white); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,23 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import javax.swing.Icon; |
||||
|
||||
/** |
||||
* 创建一个灰化 Icon |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/16 |
||||
*/ |
||||
public interface DisabledIcon { |
||||
|
||||
/** |
||||
* 创建一份灰化图标 |
||||
* |
||||
* @return 灰化图标 |
||||
*/ |
||||
@NotNull |
||||
Icon disabled(); |
||||
} |
@ -0,0 +1,59 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import com.formdev.flatlaf.util.Graphics2DProxy; |
||||
|
||||
import java.awt.Color; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Paint; |
||||
import java.awt.image.RGBImageFilter; |
||||
|
||||
/** |
||||
* 颜色过滤器画板 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/21 |
||||
*/ |
||||
public class GraphicsFilter |
||||
extends Graphics2DProxy { |
||||
private final RGBImageFilter grayFilter; |
||||
|
||||
public GraphicsFilter(Graphics2D delegate, RGBImageFilter grayFilter) { |
||||
super(delegate); |
||||
this.grayFilter = grayFilter; |
||||
} |
||||
|
||||
@Override |
||||
public Graphics create() { |
||||
return new GraphicsFilter((Graphics2D) super.create(), grayFilter); |
||||
} |
||||
|
||||
@Override |
||||
public Graphics create(int x, int y, int width, int height) { |
||||
return new GraphicsFilter((Graphics2D) super.create(x, y, width, height), grayFilter); |
||||
} |
||||
|
||||
@Override |
||||
public void setColor(Color c) { |
||||
super.setColor(filterColor(c)); |
||||
} |
||||
|
||||
@Override |
||||
public void setPaint(Paint paint) { |
||||
if (paint instanceof Color) { |
||||
paint = filterColor((Color) paint); |
||||
} |
||||
super.setPaint(paint); |
||||
} |
||||
|
||||
private Color filterColor(Color color) { |
||||
|
||||
if (grayFilter != null) { |
||||
int oldRGB = color.getRGB(); |
||||
int newRGB = grayFilter.filterRGB(0, 0, oldRGB); |
||||
color = (newRGB != oldRGB) ? new Color(newRGB, true) : color; |
||||
} |
||||
return color; |
||||
} |
||||
} |
@ -0,0 +1,29 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
/** |
||||
* 图标异常 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/6 |
||||
*/ |
||||
public class IconException extends RuntimeException { |
||||
public IconException() { |
||||
} |
||||
|
||||
public IconException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public IconException(String message, Throwable cause) { |
||||
super(message, cause); |
||||
} |
||||
|
||||
public IconException(Throwable cause) { |
||||
super(cause); |
||||
} |
||||
|
||||
public IconException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { |
||||
super(message, cause, enableSuppression, writableStackTrace); |
||||
} |
||||
} |
@ -0,0 +1,179 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import com.fr.base.extension.FileExtension; |
||||
import com.fr.general.IOUtils; |
||||
import com.fr.log.FineLoggerFactory; |
||||
import com.fr.third.errorprone.annotations.Immutable; |
||||
import org.jetbrains.annotations.NotNull; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import javax.swing.Icon; |
||||
import java.awt.Dimension; |
||||
import java.lang.ref.WeakReference; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
|
||||
/** |
||||
* 图标管理器 |
||||
* 1. 提供注册管理图标集,方便整体替换 |
||||
* 2. 提供图标缓存 |
||||
* 3. 查找图标 |
||||
* 4. 配合 {@link LazyIcon} 实现图标懒加载 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/9/12 |
||||
*/ |
||||
@Immutable |
||||
public class IconManager { |
||||
|
||||
public static final String ICON_DISABLE_SUFFIX = "_disable"; |
||||
public static final Dimension DEFAULT_DIMENSION = new Dimension(16, 16); |
||||
private static final ArrayList<IconSet> ICON_SETS = new ArrayList<>(2); |
||||
private static final HashMap<String, WeakReference<Icon>> CACHE = new HashMap<>(64); |
||||
|
||||
|
||||
/** |
||||
* 获取图标集 |
||||
* |
||||
* @param id 图标集ID |
||||
* @return 图标集 |
||||
*/ |
||||
public static IconSet getSet(String id) { |
||||
for (IconSet set : ICON_SETS) { |
||||
if (set.getId().equals(id)) { |
||||
return set; |
||||
} |
||||
} |
||||
throw new IconException("[IconManager] Can not find icon set by id: " + id); |
||||
} |
||||
|
||||
/** |
||||
* 添加图标集 |
||||
* |
||||
* @param set 图标集 |
||||
*/ |
||||
public static void addSet(@NotNull IconSet set) { |
||||
ICON_SETS.remove(set); |
||||
ICON_SETS.add(set); |
||||
clearCache(); |
||||
} |
||||
|
||||
/** |
||||
* 根据图标ID获取图标 |
||||
* <p> |
||||
* 查找路径 |
||||
* 1)查找图集图标 |
||||
* 2)路径为图片图标,从路径再查找 |
||||
* 3)提供默认svg图标 |
||||
* |
||||
* @param id 图标ID |
||||
* @param <I> 图标类型 |
||||
* @return 图标 |
||||
*/ |
||||
@NotNull |
||||
public static <I extends Icon> I getIcon(@NotNull final String id, @NotNull Dimension dimension, @NotNull IconType type) { |
||||
Icon icon = findIcon(id, dimension, type); |
||||
if (icon == null) { |
||||
// 只有找不到再进行其他fallback,提升效率
|
||||
if (IconManager.isImageIcon(id)) { |
||||
return (I) fallbackLegacyIcon(id); |
||||
} else { |
||||
FineLoggerFactory.getLogger().warn("[IconManager] Can not find icon by id: " + id); |
||||
return (I) new LazyIcon("default"); |
||||
} |
||||
} |
||||
return (I) icon; |
||||
} |
||||
|
||||
private static Icon fallbackLegacyIcon(String id) { |
||||
return IOUtils.readIcon(id); |
||||
} |
||||
|
||||
@Nullable |
||||
private static <I extends Icon> I findIcon(String id, Dimension dimension, IconType type) { |
||||
String cacheKey = genCacheKey(id, dimension, type); |
||||
final WeakReference<Icon> reference = CACHE.get(cacheKey); |
||||
I icon = reference != null ? (I) reference.get() : null; |
||||
if (icon == null) { |
||||
for (IconSet set : ICON_SETS) { |
||||
Icon f = set.findIcon(id, dimension, type); |
||||
if (f != null) { |
||||
icon = (I) f; |
||||
CACHE.put(cacheKey, new WeakReference<>(icon)); |
||||
} |
||||
} |
||||
} |
||||
return icon; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 生成缓存key |
||||
* |
||||
* @param id id |
||||
* @param dimension 尺寸 |
||||
* @param type 图标类型 |
||||
* @return 缓存key |
||||
*/ |
||||
public static @NotNull String genCacheKey(String id, Dimension dimension, IconType type) { |
||||
if (DEFAULT_DIMENSION.equals(dimension)) { |
||||
return id + "_" + type; |
||||
} |
||||
return id + "_" + dimension.width + "_" + dimension.height + "_" + type; |
||||
} |
||||
|
||||
/** |
||||
* 是否SVG图标格式 |
||||
* |
||||
* @param path 路径 |
||||
* @return 是否SVG图标格式 |
||||
*/ |
||||
public static boolean isSvgIcon(String path) { |
||||
return FileExtension.SVG.matchExtension(path); |
||||
} |
||||
|
||||
/** |
||||
* 是否支持的图片图标格式,目前只支持png和jpg |
||||
* |
||||
* @param path 路径 |
||||
* @return 是否支持的图片图标格式 |
||||
*/ |
||||
public static boolean isImageIcon(String path) { |
||||
return FileExtension.PNG.matchExtension(path) |
||||
|| FileExtension.JPG.matchExtension(path); |
||||
} |
||||
|
||||
/** |
||||
* 判断是否存在指定id的icon,非io读取行为,而是从已注册的sourceMap中遍历判断 |
||||
* |
||||
* @param id id |
||||
* @return 是否存在 |
||||
*/ |
||||
public static boolean existIcon(String id) { |
||||
for (IconSet set : ICON_SETS) { |
||||
if (set.getIds().contains(id)) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 清理所有缓存 |
||||
*/ |
||||
public static void clearCache() { |
||||
CACHE.clear(); |
||||
} |
||||
|
||||
/** |
||||
* 查找灰化图标 |
||||
* |
||||
* @param path 原始路径 |
||||
* @return 灰化路径 |
||||
*/ |
||||
public static String findDisablePath(String path) { |
||||
int i = path.lastIndexOf('.'); |
||||
return path.substring(0, i) + ICON_DISABLE_SUFFIX + path.substring(i); |
||||
} |
||||
} |
@ -0,0 +1,23 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import java.io.InputStream; |
||||
|
||||
/** |
||||
* 资源接口 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/6 |
||||
*/ |
||||
public interface IconResource { |
||||
|
||||
/** |
||||
* 获取输入资源流 |
||||
* |
||||
* @return 资源流 |
||||
*/ |
||||
@NotNull |
||||
InputStream getInputStream(); |
||||
} |
@ -0,0 +1,50 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import com.fr.third.errorprone.annotations.Immutable; |
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import javax.swing.Icon; |
||||
import java.awt.Dimension; |
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* 图标集 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/6 |
||||
*/ |
||||
@Immutable |
||||
public interface IconSet extends Identifiable { |
||||
|
||||
/** |
||||
* 返回集合中所有 Icons 的 id。 |
||||
* |
||||
* @return 集合中所有 Icons 的 id。 |
||||
*/ |
||||
@NotNull |
||||
Collection<String> getIds(); |
||||
|
||||
/** |
||||
* 将指定 IconSource 引用的新 Icon 添加到集合中。 |
||||
* |
||||
* @param icon icon 源 |
||||
*/ |
||||
void addIcon(@NotNull IconSource<? extends Icon> icon); |
||||
|
||||
/** |
||||
* 将指定 IconSource 引用的新 Icon 添加到集合中。 |
||||
* |
||||
* @param icon icon 源 |
||||
*/ |
||||
void addIcon(@NotNull IconSource<? extends Icon>... icon); |
||||
|
||||
/** |
||||
* 返回指定 id 的 Icon。 |
||||
* |
||||
* @param id id |
||||
* @return Icon |
||||
*/ |
||||
Icon findIcon(@NotNull String id, @NotNull Dimension dimension, IconType type); |
||||
|
||||
} |
@ -0,0 +1,37 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import com.fr.third.errorprone.annotations.Immutable; |
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import javax.swing.Icon; |
||||
import java.awt.Dimension; |
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* 图标源,在进行图标管理的时候代替真实图标对象 |
||||
* 使用时加载,节省内存 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/6 |
||||
*/ |
||||
@Immutable |
||||
public interface IconSource<I extends Icon> extends Identifiable, Cloneable, Serializable { |
||||
|
||||
/** |
||||
* 获取图标资源 |
||||
* |
||||
* @return 图标资源 |
||||
*/ |
||||
@NotNull |
||||
IconResource getResource(); |
||||
|
||||
|
||||
/** |
||||
* 加载图标 |
||||
* |
||||
* @return 图标 |
||||
*/ |
||||
@NotNull |
||||
I loadIcon(Dimension dimension, IconType type); |
||||
} |
@ -0,0 +1,24 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
/** |
||||
* 图标类型 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2024/01/09 |
||||
*/ |
||||
public enum IconType { |
||||
/** |
||||
* 灰化图 |
||||
*/ |
||||
disable, |
||||
/** |
||||
* 白化图,用于反白场景 |
||||
*/ |
||||
white, |
||||
/** |
||||
* 原始图 |
||||
*/ |
||||
normal |
||||
|
||||
} |
@ -0,0 +1,12 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
/** |
||||
* id 接口 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/6 |
||||
*/ |
||||
public interface Identifiable { |
||||
String getId(); |
||||
} |
@ -0,0 +1,125 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import com.fine.theme.icon.img.ImageIconSource; |
||||
import com.fine.theme.icon.svg.SvgIconSource; |
||||
import com.formdev.flatlaf.json.Json; |
||||
import com.formdev.flatlaf.json.ParseException; |
||||
import com.fr.stable.StringUtils; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.InputStreamReader; |
||||
import java.io.Reader; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.Map; |
||||
import java.util.Objects; |
||||
|
||||
|
||||
/** |
||||
* json 格式图标集 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/17 |
||||
*/ |
||||
public class JsonIconSet extends AbstractIconSet { |
||||
|
||||
private String base; |
||||
|
||||
public JsonIconSet(UrlIconResource resource) { |
||||
addIcon(new SvgIconSource("default", "com/fine/theme/icon/default.svg")); |
||||
Map<String, Object> json; |
||||
try (InputStream in = resource.getInputStream()) { |
||||
try (Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { |
||||
json = (Map<String, Object>) Json.parse(reader); |
||||
} |
||||
} catch (ParseException | IOException ex) { |
||||
throw new RuntimeException(ex.getMessage(), ex); |
||||
} |
||||
|
||||
|
||||
name = (String) json.get("name"); |
||||
dark = Boolean.parseBoolean((String) json.get("dark")); |
||||
base = (String) json.get("base"); |
||||
if (base == null) { |
||||
base = StringUtils.EMPTY; |
||||
} |
||||
|
||||
Map<String, Object> icons = (Map<String, Object>) json.get("icons"); |
||||
|
||||
for (Map.Entry<String, Object> icon : icons.entrySet()) { |
||||
applyIcon(icon.getKey(), icon.getValue()); |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 从配置文件中添加icon,只处理认识的格式和结构 |
||||
*/ |
||||
private void applyIcon(String key, Object value) { |
||||
if (value instanceof String) { |
||||
dealWithIconString(key, (String) value); |
||||
} else if (value instanceof Map) { |
||||
dealWithIconMap(key, (Map<String, Object>) value); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 处理字符串格式的icon配置 |
||||
*/ |
||||
private void dealWithIconString(String key, String value) { |
||||
if (IconManager.isSvgIcon(value)) { |
||||
// 默认字符串提供正常图和灰化图
|
||||
addIcon(new SvgIconSource(key, |
||||
base + value, |
||||
IconManager.findDisablePath(base + value), |
||||
null |
||||
)); |
||||
} else if (IconManager.isImageIcon(value)) { |
||||
addIcon(new ImageIconSource(key, base + value)); |
||||
} |
||||
// 其他无法识别格式不处理
|
||||
} |
||||
|
||||
/** |
||||
* 处理object形式的icon配置 |
||||
*/ |
||||
private void dealWithIconMap(String key, Map<String, Object> value) { |
||||
String normalPath = (String) value.get(IconType.normal.name()); |
||||
String disablePath = (String) value.get(IconType.disable.name()); |
||||
String whitePath = (String) value.get(IconType.white.name()); |
||||
// 暂不支持混合格式,每个id的格式需要保持一致
|
||||
if (IconManager.isSvgIcon(normalPath)) { |
||||
addIcon(new SvgIconSource(key, |
||||
base + normalPath, |
||||
StringUtils.isNotBlank(disablePath) ? base + disablePath : null, |
||||
StringUtils.isNotBlank(whitePath) ? base + whitePath : null |
||||
)); |
||||
} else if (IconManager.isImageIcon(normalPath)) { |
||||
addIcon(new ImageIconSource(key, |
||||
base + normalPath, |
||||
StringUtils.isNotBlank(disablePath) ? base + disablePath : null, |
||||
StringUtils.isNotBlank(whitePath) ? base + whitePath : null |
||||
)); |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) { |
||||
return true; |
||||
} |
||||
if (o == null || getClass() != o.getClass()) { |
||||
return false; |
||||
} |
||||
JsonIconSet that = (JsonIconSet) o; |
||||
return Objects.equals(name, that.name); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hashCode(name); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,120 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import com.fr.third.errorprone.annotations.Immutable; |
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import javax.swing.Icon; |
||||
import java.awt.Component; |
||||
import java.awt.Dimension; |
||||
import java.awt.Graphics; |
||||
import java.util.StringJoiner; |
||||
|
||||
import static com.fine.theme.utils.FineUIScale.scale; |
||||
|
||||
/** |
||||
* 懒加载图标 |
||||
* 非常懒,宽高都不想算 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/6 |
||||
*/ |
||||
@Immutable |
||||
public class LazyIcon implements Identifiable, DisabledIcon, WhiteIcon, Icon { |
||||
@NotNull |
||||
private final String id; |
||||
|
||||
private final Dimension dimension; |
||||
|
||||
private final IconType type; |
||||
|
||||
public LazyIcon(@NotNull final String id) { |
||||
this.id = id; |
||||
this.dimension = IconManager.DEFAULT_DIMENSION; |
||||
this.type = IconType.normal; |
||||
} |
||||
|
||||
public LazyIcon(@NotNull final String id, int side) { |
||||
this.id = id; |
||||
this.dimension = new Dimension(side, side); |
||||
this.type = IconType.normal; |
||||
} |
||||
|
||||
public LazyIcon(@NotNull final String id, @NotNull Dimension dimension) { |
||||
this.id = id; |
||||
this.dimension = dimension; |
||||
this.type = IconType.normal; |
||||
} |
||||
|
||||
private LazyIcon(@NotNull final String id, @NotNull IconType type) { |
||||
this.id = id; |
||||
this.dimension = IconManager.DEFAULT_DIMENSION; |
||||
this.type = type; |
||||
} |
||||
|
||||
public LazyIcon(@NotNull final String id, @NotNull Dimension dimension, @NotNull IconType type) { |
||||
this.id = id; |
||||
this.dimension = dimension; |
||||
this.type = type; |
||||
} |
||||
|
||||
|
||||
@NotNull |
||||
@Override |
||||
public String getId() { |
||||
return id; |
||||
} |
||||
|
||||
@Override |
||||
public void paintIcon(@NotNull final Component c, @NotNull final Graphics g, final int x, final int y) { |
||||
getIcon().paintIcon(c, g, x, y); |
||||
} |
||||
|
||||
@Override |
||||
public int getIconWidth() { |
||||
return scale(dimension.width); |
||||
} |
||||
|
||||
@Override |
||||
public int getIconHeight() { |
||||
return scale(dimension.height); |
||||
} |
||||
|
||||
|
||||
@NotNull |
||||
public <I extends Icon> I getIcon() { |
||||
return IconManager.getIcon(getId(), dimension, type); |
||||
} |
||||
|
||||
/** |
||||
* 创建一份灰化图标 |
||||
* |
||||
* @return 灰化图标 |
||||
*/ |
||||
@NotNull |
||||
@Override |
||||
public Icon disabled() { |
||||
return new LazyIcon(getId(), dimension, IconType.disable); |
||||
} |
||||
|
||||
/** |
||||
* 创建一份白化图标 |
||||
* |
||||
* @return 白化图标 |
||||
*/ |
||||
@NotNull |
||||
@Override |
||||
public Icon white() { |
||||
return new LazyIcon(getId(), dimension, IconType.white); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
return new StringJoiner(", ", LazyIcon.class.getSimpleName() + "[", "]") |
||||
.add("id='" + id + "'") |
||||
.add("size=" + "[w=" + scale(dimension.width) + ",h=" + scale(dimension.height) + "]") |
||||
.add("type=" + type) |
||||
.toString(); |
||||
} |
||||
} |
@ -0,0 +1,53 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import com.fr.general.IOUtils; |
||||
import com.fr.io.utils.ResourceIOUtils; |
||||
import com.fr.third.errorprone.annotations.Immutable; |
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import java.io.InputStream; |
||||
import java.util.StringJoiner; |
||||
|
||||
/** |
||||
* url图标资源 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/15 |
||||
*/ |
||||
@Immutable |
||||
public class UrlIconResource implements IconResource { |
||||
|
||||
private final String path; |
||||
|
||||
public UrlIconResource(String path) { |
||||
this.path = path; |
||||
} |
||||
|
||||
public String getPath() { |
||||
return path; |
||||
} |
||||
|
||||
@Override |
||||
@NotNull |
||||
public InputStream getInputStream() { |
||||
InputStream inputStream = getInputStream(path); |
||||
if (inputStream == null) { |
||||
throw new IconException("Icon load failed: " + path); |
||||
} |
||||
return inputStream; |
||||
} |
||||
|
||||
|
||||
private InputStream getInputStream(String path) { |
||||
InputStream inputStream = IOUtils.getInputStream(path); |
||||
return inputStream != null ? inputStream : ResourceIOUtils.read(path); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return new StringJoiner(", ", UrlIconResource.class.getSimpleName() + "[", "]") |
||||
.add("path='" + path + "'") |
||||
.toString(); |
||||
} |
||||
} |
@ -0,0 +1,22 @@
|
||||
package com.fine.theme.icon; |
||||
|
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import javax.swing.Icon; |
||||
|
||||
/** |
||||
* 白化图像 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2024/1/8 |
||||
*/ |
||||
public interface WhiteIcon { |
||||
/** |
||||
* 创建一份白化图标 |
||||
* |
||||
* @return 灰化图标 |
||||
*/ |
||||
@NotNull |
||||
Icon white(); |
||||
} |
@ -0,0 +1,75 @@
|
||||
package com.fine.theme.icon.icons; |
||||
|
||||
import com.formdev.flatlaf.icons.FlatAnimatedIcon; |
||||
import com.formdev.flatlaf.util.ColorFunctions; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import javax.swing.AbstractButton; |
||||
import javax.swing.ButtonModel; |
||||
import javax.swing.JRadioButton; |
||||
import javax.swing.UIManager; |
||||
import java.awt.Color; |
||||
import java.awt.Component; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.geom.Ellipse2D; |
||||
|
||||
/** |
||||
* RadioButton 图标,带动画 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/22 |
||||
*/ |
||||
public class AnimatedRadioButtonIcon |
||||
extends FlatAnimatedIcon { |
||||
private static final int SIZE = 16; |
||||
private static final int BORDER_SIZE = 2; |
||||
private static final int ON_SIZE = 8; |
||||
|
||||
private final Color offColor = UIManager.getColor("CheckBox.icon.borderColor"); |
||||
private final Color onColor = UIManager.getColor("CheckBox.icon.checkmarkColor"); |
||||
|
||||
public AnimatedRadioButtonIcon() { |
||||
super(SIZE, SIZE, null); |
||||
} |
||||
|
||||
@Override |
||||
public void paintIconAnimated(Component c, Graphics g, int x, int y, float animatedValue) { |
||||
Color color = ColorFunctions.mix(onColor, offColor, animatedValue); |
||||
|
||||
// border
|
||||
g.setColor(getBorderColor(c, color, onColor)); |
||||
g.fillOval(0, 0, SIZE, SIZE); |
||||
|
||||
// background
|
||||
g.setColor(c.getBackground()); |
||||
float onDiameter = SIZE - (BORDER_SIZE + (ON_SIZE - BORDER_SIZE) * (animatedValue)); |
||||
float xy = (SIZE - onDiameter) / 2f; |
||||
((Graphics2D) g).fill(new Ellipse2D.Float(xy, xy, onDiameter, onDiameter)); |
||||
|
||||
} |
||||
|
||||
@Nullable |
||||
private Color getBorderColor(Component c, Color enableColor, Color hoverColor) { |
||||
if (c instanceof AbstractButton) { |
||||
ButtonModel model = ((AbstractButton) c).getModel(); |
||||
|
||||
if (model.isRollover()) { |
||||
return hoverColor; |
||||
} |
||||
} |
||||
return enableColor; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public float getValue(Component c) { |
||||
return ((JRadioButton) c).isSelected() ? 1 : 0; |
||||
} |
||||
|
||||
@Override |
||||
public int getAnimationDuration() { |
||||
return 200; |
||||
} |
||||
} |
@ -0,0 +1,58 @@
|
||||
package com.fine.theme.icon.img; |
||||
|
||||
import com.fine.theme.icon.AbstractIconSource; |
||||
import com.fine.theme.icon.IconResource; |
||||
import com.fine.theme.icon.IconType; |
||||
import com.fine.theme.icon.UrlIconResource; |
||||
import com.fr.clone.cloning.Immutable; |
||||
import com.fr.general.FRLogger; |
||||
import com.fr.general.IOUtils; |
||||
import com.fr.stable.StringUtils; |
||||
import org.jetbrains.annotations.NotNull; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import javax.swing.ImageIcon; |
||||
import java.awt.Dimension; |
||||
import java.awt.image.BufferedImage; |
||||
|
||||
/** |
||||
* 图片图标源 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2024/05/09 |
||||
*/ |
||||
@Immutable |
||||
public class ImageIconSource extends AbstractIconSource<ImageIcon> { |
||||
|
||||
|
||||
public ImageIconSource(@NotNull String id, @NotNull String resource) { |
||||
super(id, new UrlIconResource(resource)); |
||||
} |
||||
|
||||
public ImageIconSource(@NotNull String id, |
||||
@NotNull String resource, |
||||
@Nullable String grayResource, |
||||
@Nullable String whiteResource) { |
||||
super(id, new UrlIconResource(resource), |
||||
StringUtils.isEmpty(grayResource) ? null : new UrlIconResource(grayResource), |
||||
StringUtils.isEmpty(whiteResource) ? null : new UrlIconResource(whiteResource)); |
||||
} |
||||
|
||||
@NotNull |
||||
@Override |
||||
protected ImageIcon loadIcon(@NotNull IconResource resource, Dimension dimension, IconType type) { |
||||
byte[] bytes = IOUtils.inputStream2Bytes(resource.getInputStream()); |
||||
if (bytes == null && resource instanceof UrlIconResource) { |
||||
// 换readImageWithCache尝试读取
|
||||
UrlIconResource iconResource = (UrlIconResource) resource; |
||||
BufferedImage icon = IOUtils.readImageWithCache(iconResource.getPath()); |
||||
if (icon == null) { |
||||
FRLogger.getLogger().error(iconResource.getPath()); |
||||
return new ImageIcon(); |
||||
} |
||||
return new ImageIcon(icon); |
||||
} |
||||
return new ImageIcon(bytes); |
||||
} |
||||
} |
@ -0,0 +1,207 @@
|
||||
package com.fine.theme.icon.svg; |
||||
|
||||
import com.fine.theme.icon.DisabledIcon; |
||||
import com.fine.theme.icon.GraphicsFilter; |
||||
import com.fine.theme.icon.IconManager; |
||||
import com.fine.theme.icon.IconResource; |
||||
import com.fine.theme.icon.IconType; |
||||
import com.fine.theme.icon.UrlIconResource; |
||||
import com.fine.theme.icon.WhiteIcon; |
||||
import com.formdev.flatlaf.FlatLaf; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
import com.formdev.flatlaf.util.GrayFilter; |
||||
import com.fr.clone.cloning.Immutable; |
||||
import com.fr.log.FineLoggerFactory; |
||||
import com.fr.value.NullableLazyValue; |
||||
import com.github.weisj.jsvg.SVGDocument; |
||||
import com.github.weisj.jsvg.attributes.ViewBox; |
||||
import com.github.weisj.jsvg.parser.SVGLoader; |
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import javax.swing.Icon; |
||||
import javax.swing.JComponent; |
||||
import javax.swing.UIManager; |
||||
import java.awt.Component; |
||||
import java.awt.Dimension; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.image.RGBImageFilter; |
||||
import java.util.StringJoiner; |
||||
|
||||
import static com.fine.theme.utils.FineUIScale.scale; |
||||
|
||||
/** |
||||
* svg图标 |
||||
* 1.绘制长度会跟随DPI比率变化 |
||||
* 1跟2的缩放原因不同,因此不能混淆,宽高测量等依旧 |
||||
* 使用DPI缩放进行,只有绘制内容时使用Retina绘制(如果有) |
||||
* Retina绘制不影响最终尺寸,注意区分 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/15 |
||||
*/ |
||||
@Immutable |
||||
public class SvgIcon implements DisabledIcon, WhiteIcon, Icon { |
||||
|
||||
private final Dimension size; |
||||
private final Dimension scaleSize; |
||||
private final IconResource resource; |
||||
private final IconType type; |
||||
|
||||
private final static NullableLazyValue<SVGDocument> DEFAULT_ICON_DOC = |
||||
NullableLazyValue.createValue(SvgIcon::loadDefault); |
||||
private final NullableLazyValue<SVGDocument> svgDocument = |
||||
NullableLazyValue.createValue(() -> load(IconType.normal)); |
||||
private final NullableLazyValue<SVGDocument> whiteSvgDocument = |
||||
NullableLazyValue.createValue(() -> load(IconType.white)); |
||||
|
||||
public SvgIcon(IconResource resource, Dimension size, IconType type) { |
||||
this.resource = resource; |
||||
this.size = size; |
||||
// 根据dpi进行缩放
|
||||
this.scaleSize = scale(size); |
||||
this.type = type; |
||||
} |
||||
|
||||
/** |
||||
* 如果支持绘制Retina绘制,则进行Retina绘制, |
||||
* 绘制结束不影响任何外部尺寸 |
||||
*/ |
||||
@Override |
||||
public void paintIcon(Component c, Graphics g, int x, int y) { |
||||
if (type == IconType.disable) { |
||||
g = grayGraphics(g); |
||||
} |
||||
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints(g); |
||||
try { |
||||
render(c, g, x, y, this::fallbackRender); |
||||
} finally { |
||||
FlatUIUtils.resetRenderingHints(g, oldRenderingHints); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 用于fallback渲染disable图像,这段代码来自异常分支,使用率要保持较低水平 |
||||
*/ |
||||
private boolean fallbackRender(Component c, Graphics g, int x, int y) { |
||||
if (resource instanceof UrlIconResource) { |
||||
String path = ((UrlIconResource) resource).getPath(); |
||||
int index = path.lastIndexOf(IconManager.ICON_DISABLE_SUFFIX); |
||||
if (path.contains(IconManager.ICON_DISABLE_SUFFIX) && index > 0) { |
||||
SVGLoader loader = new SVGLoader(); |
||||
SVGDocument document = loader.load( |
||||
new UrlIconResource(path.substring(0, index) + |
||||
path.substring(index + IconManager.ICON_DISABLE_SUFFIX.length())) |
||||
.getInputStream()); |
||||
if (document != null) { |
||||
document.render((JComponent) c, grayGraphics(g), |
||||
new ViewBox(x, y, scaleSize.width, scaleSize.height)); |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private Graphics2D grayGraphics(Graphics g) { |
||||
Object grayFilterObj = UIManager.get("Component.grayFilter"); |
||||
RGBImageFilter grayFilter = (grayFilterObj instanceof RGBImageFilter) |
||||
? (RGBImageFilter) grayFilterObj |
||||
: GrayFilter.createDisabledIconFilter(FlatLaf.isLafDark()); |
||||
|
||||
return new GraphicsFilter((Graphics2D) g.create(), grayFilter); |
||||
} |
||||
|
||||
@Override |
||||
public int getIconWidth() { |
||||
return scaleSize.width; |
||||
} |
||||
|
||||
@Override |
||||
public int getIconHeight() { |
||||
return scaleSize.height; |
||||
} |
||||
|
||||
private void render(Component c, Graphics g, int x, int y, FallbackRender fallbackRender) { |
||||
SVGDocument document; |
||||
try { |
||||
if (type == IconType.white) { |
||||
document = whiteSvgDocument.getValue(); |
||||
} else { |
||||
document = svgDocument.getValue(); |
||||
} |
||||
// 由于 weisj 库中加载svg描述出现问题,则返回一个Null,这里补充一个默认图标
|
||||
if (document == null) { |
||||
document = DEFAULT_ICON_DOC.getValue(); |
||||
} |
||||
document.render((JComponent) c, (Graphics2D) g, new ViewBox(x, y, scaleSize.width, scaleSize.height)); |
||||
} catch (Exception e) { |
||||
boolean rendered = fallbackRender.render(c, g, x, y); |
||||
if (rendered) { |
||||
FineLoggerFactory.getLogger().warn("SvgIcon from url: " + resource + " paint with fallbackRender"); |
||||
} else { |
||||
FineLoggerFactory.getLogger().error("SvgIcon from url: " + resource + "can not paint.", e); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private static SVGDocument loadDefault() { |
||||
SVGLoader loader = new SVGLoader(); |
||||
UrlIconResource iconResource = new UrlIconResource("com/fine/theme/icon/default.svg"); |
||||
return loader.load(iconResource.getInputStream()); |
||||
} |
||||
|
||||
|
||||
private SVGDocument load(IconType type) { |
||||
SVGLoader loader = new SVGLoader(); |
||||
return type == IconType.white |
||||
? loader.load(resource.getInputStream(), new WhiteParser()) |
||||
: loader.load(resource.getInputStream()); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return new StringJoiner(", ", SvgIcon.class.getSimpleName() + "[", "]") |
||||
.add("resource=" + resource) |
||||
.add("type=" + type) |
||||
.add("size=" + size) |
||||
.add("scaleSize=" + scaleSize) |
||||
.toString(); |
||||
} |
||||
|
||||
/** |
||||
* 默认提供一个简单的灰化处理 |
||||
*/ |
||||
@Override |
||||
public @NotNull SvgIcon white() { |
||||
return new SvgIcon(resource, size, IconType.white); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 默认提供一个简单的灰化处理 |
||||
*/ |
||||
@Override |
||||
public @NotNull SvgIcon disabled() { |
||||
return new SvgIcon(resource, size, IconType.disable); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 用于回退渲染的回调类 |
||||
*/ |
||||
@FunctionalInterface |
||||
interface FallbackRender { |
||||
/** |
||||
* 渲染图标 |
||||
* |
||||
* @param c 待绘制组件 |
||||
* @param g 抽象画板 |
||||
* @param x 起点横坐标 |
||||
* @param y 起点纵坐标 |
||||
* @return 是否使用了回退渲染 |
||||
*/ |
||||
boolean render(Component c, Graphics g, int x, int y); |
||||
} |
||||
} |
@ -0,0 +1,43 @@
|
||||
package com.fine.theme.icon.svg; |
||||
|
||||
import com.fine.theme.icon.AbstractIconSource; |
||||
import com.fine.theme.icon.IconResource; |
||||
import com.fine.theme.icon.IconType; |
||||
import com.fine.theme.icon.UrlIconResource; |
||||
import com.fr.clone.cloning.Immutable; |
||||
import com.fr.stable.StringUtils; |
||||
import org.jetbrains.annotations.NotNull; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import java.awt.Dimension; |
||||
|
||||
/** |
||||
* svg图标源 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/14 |
||||
*/ |
||||
@Immutable |
||||
public class SvgIconSource extends AbstractIconSource<SvgIcon> { |
||||
|
||||
|
||||
public SvgIconSource(@NotNull String id, @NotNull String resource) { |
||||
super(id, new UrlIconResource(resource)); |
||||
} |
||||
|
||||
public SvgIconSource(@NotNull String id, |
||||
@NotNull String resource, |
||||
@Nullable String grayResource, |
||||
@Nullable String whiteResource) { |
||||
super(id, new UrlIconResource(resource), |
||||
StringUtils.isEmpty(grayResource) ? null : new UrlIconResource(grayResource), |
||||
StringUtils.isEmpty(whiteResource) ? null : new UrlIconResource(whiteResource)); |
||||
} |
||||
|
||||
@NotNull |
||||
@Override |
||||
protected SvgIcon loadIcon(@NotNull IconResource resource, Dimension dimension, IconType type) { |
||||
return new SvgIcon(resource, dimension, type); |
||||
} |
||||
} |
@ -0,0 +1,50 @@
|
||||
package com.fine.theme.icon.svg; |
||||
|
||||
import com.github.weisj.jsvg.attributes.paint.AwtSVGPaint; |
||||
import com.github.weisj.jsvg.attributes.paint.PaintParser; |
||||
import com.github.weisj.jsvg.attributes.paint.SVGPaint; |
||||
import com.github.weisj.jsvg.parser.AttributeNode; |
||||
import com.github.weisj.jsvg.parser.DefaultParserProvider; |
||||
import org.jetbrains.annotations.NotNull; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import java.awt.Color; |
||||
|
||||
/** |
||||
* svg绘制白化转化器 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2024/1/8 |
||||
*/ |
||||
public class WhiteParser extends DefaultParserProvider { |
||||
@Override |
||||
public @NotNull PaintParser createPaintParser() { |
||||
return new WhitePaintParser(super.createPaintParser()); |
||||
} |
||||
|
||||
|
||||
static class WhitePaintParser implements PaintParser { |
||||
|
||||
private final PaintParser delegate; |
||||
|
||||
WhitePaintParser(PaintParser delegate) { |
||||
this.delegate = delegate; |
||||
} |
||||
|
||||
@Override |
||||
public @Nullable Color parseColor(@NotNull String value, @NotNull AttributeNode attributeNode) { |
||||
return delegate.parseColor(value, attributeNode); |
||||
} |
||||
|
||||
@Override |
||||
public @Nullable SVGPaint parsePaint(@Nullable String value, @NotNull AttributeNode attributeNode) { |
||||
SVGPaint paint = delegate.parsePaint(value, attributeNode); |
||||
if (!(paint instanceof AwtSVGPaint)) { |
||||
return paint; |
||||
} |
||||
return new AwtSVGPaint(Color.WHITE); |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,160 @@
|
||||
package com.fine.theme.icon.svg.batik; |
||||
|
||||
import com.fine.theme.icon.DisabledIcon; |
||||
import com.fine.theme.icon.GraphicsFilter; |
||||
import com.fine.theme.icon.IconResource; |
||||
import com.fine.theme.icon.IconType; |
||||
import com.fine.theme.icon.WhiteIcon; |
||||
import com.formdev.flatlaf.FlatLaf; |
||||
import com.formdev.flatlaf.util.GrayFilter; |
||||
import com.fr.clone.cloning.Immutable; |
||||
import com.fr.log.FineLoggerFactory; |
||||
import org.apache.batik.transcoder.TranscoderException; |
||||
import org.apache.batik.transcoder.TranscoderInput; |
||||
import org.jetbrains.annotations.NotNull; |
||||
|
||||
import javax.swing.Icon; |
||||
import javax.swing.UIManager; |
||||
import java.awt.Component; |
||||
import java.awt.Dimension; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.image.BufferedImage; |
||||
import java.awt.image.RGBImageFilter; |
||||
import java.util.StringJoiner; |
||||
|
||||
import static com.fine.theme.utils.FineUIScale.scale; |
||||
import static com.fine.theme.utils.FineUIUtils.RETINA_SCALE_FACTOR; |
||||
import static com.fine.theme.utils.FineUIUtils.getRetina; |
||||
|
||||
/** |
||||
* svg图标 |
||||
* 1.绘制长度会跟随DPI比率变化 |
||||
* 1跟2的缩放原因不同,因此不能混淆,宽高测量等依旧 |
||||
* 使用DPI缩放进行,只有绘制内容时使用Retina绘制(如果有) |
||||
* Retina绘制不影响最终尺寸,注意区分 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/15 |
||||
*/ |
||||
@Immutable |
||||
public class BatikSvgIcon implements DisabledIcon, WhiteIcon, Icon { |
||||
|
||||
private final Dimension size; |
||||
private final Dimension scaleSize; |
||||
private final IconResource resource; |
||||
private final IconType type; |
||||
|
||||
|
||||
public BatikSvgIcon(IconResource resource, Dimension size) { |
||||
this(resource, size, IconType.normal); |
||||
} |
||||
|
||||
public BatikSvgIcon(IconResource resource, Dimension size, IconType type) { |
||||
this.resource = resource; |
||||
this.size = size; |
||||
// 根据dpi进行缩放
|
||||
this.scaleSize = scale(size); |
||||
this.type = type; |
||||
} |
||||
|
||||
public BatikSvgIcon(IconResource resource, int side) { |
||||
this(resource, new Dimension(side, side), IconType.normal); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 如果支持绘制Retina绘制,则进行Retina绘制, |
||||
* 绘制结束不影响任何外部尺寸 |
||||
*/ |
||||
@Override |
||||
public void paintIcon(Component c, Graphics g, int x, int y) { |
||||
if (getRetina()) { |
||||
BufferedImage image = toImage(scaleRetina(size)); |
||||
// 高清绘制的原理:scale(1/2,1/2)的原理是坐标减半,底层是矩阵进行坐标变换,意思是坐标减半进行绘制,
|
||||
// 这样就可以将两倍图绘制到一倍的大小,如果这时候设备支持Retina绘制(四个像素模拟一个像素),
|
||||
// 正好就可以将4个像素利用起来,每个像素点都有不同的颜色,而不像之前只能是四个共用一个颜色。因此图像
|
||||
// 可以更加细腻当然,绘图之后,需要将这个坐标变换给恢复。
|
||||
((Graphics2D) g).scale(1.0 / RETINA_SCALE_FACTOR, 1.0 / RETINA_SCALE_FACTOR); |
||||
g.drawImage(image, x * RETINA_SCALE_FACTOR, y * RETINA_SCALE_FACTOR, null); |
||||
((Graphics2D) g).scale(RETINA_SCALE_FACTOR, RETINA_SCALE_FACTOR); |
||||
} else { |
||||
BufferedImage image = toImage(size); |
||||
g.drawImage(image, x, y, null); |
||||
} |
||||
} |
||||
|
||||
private static Dimension scaleRetina(Dimension dimension) { |
||||
return getRetina() |
||||
? new Dimension(dimension.width * RETINA_SCALE_FACTOR, dimension.height * RETINA_SCALE_FACTOR) |
||||
: dimension; |
||||
} |
||||
|
||||
/** |
||||
* 根据指定尺寸绘制图片,这里尺寸为结算后的尺寸, |
||||
* 因此不必进行缩放 |
||||
* |
||||
* @param size 图像尺寸 |
||||
* @return 图像 |
||||
*/ |
||||
private BufferedImage toImage(Dimension size) { |
||||
SvgTranscoder transcoder = new SvgTranscoder(size); |
||||
TranscoderInput transcoderInput = new TranscoderInput(resource.getInputStream()); |
||||
try { |
||||
transcoder.transcode(transcoderInput, null); |
||||
return transcoder.getImage(); |
||||
} catch (TranscoderException e) { |
||||
FineLoggerFactory.getLogger().error("SvgIcon from url: " + resource + "can not paint.", e); |
||||
} |
||||
return transcoder.getImage(); |
||||
} |
||||
|
||||
|
||||
private Graphics2D grayGraphics(Graphics g) { |
||||
Object grayFilterObj = UIManager.get("Component.grayFilter"); |
||||
RGBImageFilter grayFilter = (grayFilterObj instanceof RGBImageFilter) |
||||
? (RGBImageFilter) grayFilterObj |
||||
: GrayFilter.createDisabledIconFilter(FlatLaf.isLafDark()); |
||||
|
||||
return new GraphicsFilter((Graphics2D) g.create(), grayFilter); |
||||
} |
||||
|
||||
@Override |
||||
public int getIconWidth() { |
||||
return scaleSize.width; |
||||
} |
||||
|
||||
@Override |
||||
public int getIconHeight() { |
||||
return scaleSize.height; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
return new StringJoiner(", ", BatikSvgIcon.class.getSimpleName() + "[", "]") |
||||
.add("resource=" + resource) |
||||
.add("type=" + type) |
||||
.add("size=" + size) |
||||
.add("scaleSize=" + scaleSize) |
||||
.toString(); |
||||
} |
||||
|
||||
/** |
||||
* 默认提供一个简单的灰化处理 |
||||
*/ |
||||
@Override |
||||
public @NotNull BatikSvgIcon white() { |
||||
return new BatikSvgIcon(resource, size, IconType.white); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 默认提供一个简单的灰化处理 |
||||
*/ |
||||
@Override |
||||
public @NotNull BatikSvgIcon disabled() { |
||||
return new BatikSvgIcon(resource, size, IconType.disable); |
||||
} |
||||
} |
@ -0,0 +1,90 @@
|
||||
package com.fine.theme.icon.svg.batik; |
||||
|
||||
import org.apache.batik.transcoder.SVGAbstractTranscoder; |
||||
import org.apache.batik.transcoder.TranscoderException; |
||||
import org.apache.batik.transcoder.TranscoderOutput; |
||||
import org.apache.batik.transcoder.image.ImageTranscoder; |
||||
|
||||
import java.awt.Color; |
||||
import java.awt.Dimension; |
||||
import java.awt.image.BufferedImage; |
||||
import java.awt.image.DataBuffer; |
||||
import java.awt.image.IndexColorModel; |
||||
import java.awt.image.Raster; |
||||
|
||||
/** |
||||
* Svg图标转码器 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/15 |
||||
*/ |
||||
public class SvgTranscoder extends ImageTranscoder { |
||||
|
||||
public enum Type { |
||||
gray, white, origin |
||||
} |
||||
|
||||
private BufferedImage bufferedImage; |
||||
private Type type = Type.origin; |
||||
|
||||
public SvgTranscoder(Dimension size) { |
||||
addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, (float) size.getWidth()); |
||||
addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, (float) size.getHeight()); |
||||
} |
||||
|
||||
public SvgTranscoder(Dimension size, Color background, Type type) { |
||||
this.type = type; |
||||
addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, (float) size.getWidth()); |
||||
addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, (float) size.getHeight()); |
||||
addTranscodingHint(ImageTranscoder.KEY_BACKGROUND_COLOR, background); |
||||
} |
||||
|
||||
|
||||
public SvgTranscoder(float width, float height) { |
||||
addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, width); |
||||
addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, height); |
||||
} |
||||
|
||||
@Override |
||||
public BufferedImage createImage(int width, int height) { |
||||
switch (type) { |
||||
case gray: |
||||
return createGrayImage(width, height); |
||||
case white: |
||||
return createWhiteImage(width, height); |
||||
default: |
||||
return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 灰化底图 |
||||
*/ |
||||
private BufferedImage createGrayImage(int width, int height) { |
||||
return new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 灰化底图 |
||||
*/ |
||||
private BufferedImage createWhiteImage(int width, int height) { |
||||
byte[] arr = {(byte) 0xff, (byte) 0,}; |
||||
return new BufferedImage( |
||||
new IndexColorModel(1, 2, arr, arr, arr), |
||||
Raster.createPackedRaster(DataBuffer.TYPE_BYTE, width, height, 1, 1, null), |
||||
false, |
||||
null); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void writeImage(BufferedImage bufferedImage, TranscoderOutput transcoderOutput) throws TranscoderException { |
||||
this.bufferedImage = bufferedImage; |
||||
} |
||||
|
||||
public BufferedImage getImage() { |
||||
return bufferedImage; |
||||
} |
||||
} |
@ -0,0 +1,73 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fr.design.gui.icontainer.UIScrollPane; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.JLayer; |
||||
import javax.swing.JScrollPane; |
||||
import javax.swing.ScrollPaneConstants; |
||||
import javax.swing.plaf.LayerUI; |
||||
import java.awt.AWTEvent; |
||||
import java.awt.Component; |
||||
import java.awt.event.MouseEvent; |
||||
|
||||
/** |
||||
* 滚动面板的装饰层UI,支持滚动条仅当悬浮时显示 |
||||
* 使用见工具类: {@link com.fine.theme.utils.FineUIUtils#createCollapsibleScrollBarLayer(Component)} |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2024/01/25 |
||||
*/ |
||||
public class CollapsibleScrollBarLayerUI extends LayerUI<UIScrollPane> { |
||||
private final int verticalPolicy; |
||||
private final int horizontalPolicy; |
||||
|
||||
public CollapsibleScrollBarLayerUI() { |
||||
this(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); |
||||
} |
||||
|
||||
public CollapsibleScrollBarLayerUI(int verticalPolicy, int horizontalPolicy) { |
||||
this.verticalPolicy = verticalPolicy; |
||||
this.horizontalPolicy = horizontalPolicy; |
||||
} |
||||
|
||||
@Override |
||||
public void installUI(JComponent c) { |
||||
super.installUI(c); |
||||
if (c instanceof JLayer) { |
||||
((JLayer<?>) c).setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void uninstallUI(JComponent c) { |
||||
if (c instanceof JLayer) { |
||||
((JLayer<?>) c).setLayerEventMask(0); |
||||
} |
||||
super.uninstallUI(c); |
||||
} |
||||
|
||||
@Override |
||||
protected void processMouseEvent(MouseEvent e, JLayer<? extends UIScrollPane> l) { |
||||
JScrollPane view = l.getView(); |
||||
switch (e.getID()) { |
||||
case MouseEvent.MOUSE_ENTERED: |
||||
// 在鼠标进入时恢复滚动条显示策略
|
||||
if (view != null) { |
||||
view.setVerticalScrollBarPolicy(verticalPolicy); |
||||
view.setHorizontalScrollBarPolicy(horizontalPolicy); |
||||
} |
||||
break; |
||||
case MouseEvent.MOUSE_EXITED: |
||||
// 在鼠标退出时隐藏滚动条
|
||||
if (view != null) { |
||||
view.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); |
||||
view.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); |
||||
} |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,42 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatButtonBorder; |
||||
|
||||
import java.awt.Component; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Paint; |
||||
|
||||
import static com.fine.theme.light.ui.FineButtonUI.isPartRoundButton; |
||||
|
||||
/** |
||||
* 按钮边框 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/12/20 |
||||
*/ |
||||
public class FineButtonBorder extends FlatButtonBorder { |
||||
|
||||
public FineButtonBorder() { |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { |
||||
if (isPartRoundButton(c)) { |
||||
Graphics2D g2 = (Graphics2D) g.create(); |
||||
Paint borderPaint = getBorderColor(c); |
||||
if (borderPaint == null) { |
||||
return; |
||||
} |
||||
g2.setPaint(borderPaint); |
||||
FineUIUtils.paintPartRoundButtonBorder(c, g2, x, y, width, height, borderWidth, (float) getArc(c)); |
||||
} else { |
||||
super.paintBorder(c, g, x, y, width, height); |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,30 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import javax.swing.plaf.PanelUI; |
||||
|
||||
/** |
||||
* 按钮组UI,应用于 {@link com.fr.design.gui.ibutton.UIButtonGroup} |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2023/12/15 |
||||
*/ |
||||
public class FineButtonGroupUI extends PanelUI { |
||||
|
||||
/** |
||||
* 创建UI |
||||
* |
||||
* @param c 组件 |
||||
* @return ComponentUI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineButtonGroupUI(); |
||||
} |
||||
|
||||
@Override |
||||
public void uninstallUI(JComponent c) { |
||||
super.uninstallUI(c); |
||||
} |
||||
} |
@ -0,0 +1,114 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineClientProperties; |
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatButtonUI; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
|
||||
import javax.swing.AbstractButton; |
||||
import javax.swing.JButton; |
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Color; |
||||
import java.awt.Component; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.geom.Path2D; |
||||
|
||||
import static com.fine.theme.utils.FineClientProperties.BUTTON_BORDER; |
||||
|
||||
/** |
||||
* 按钮UI |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/12/20 |
||||
*/ |
||||
public class FineButtonUI extends FlatButtonUI { |
||||
|
||||
/** |
||||
* @param shared |
||||
* @since 2 |
||||
*/ |
||||
protected FineButtonUI(boolean shared) { |
||||
super(shared); |
||||
} |
||||
|
||||
/** |
||||
* 是否左圆角矩形 |
||||
* |
||||
* @param c 组件 |
||||
* @return 是否左圆角矩形 |
||||
*/ |
||||
public static boolean isLeftRoundButton(Component c) { |
||||
return c instanceof JButton |
||||
&& FineClientProperties.BUTTON_BORDER_LEFT_ROUND_RECT.equals(getButtonBorderTypeStr((JButton) c)); |
||||
} |
||||
|
||||
/** |
||||
* 是否右圆角矩形 |
||||
* |
||||
* @param c 组件 |
||||
* @return 是否右圆角矩形 |
||||
*/ |
||||
public static boolean isRightRoundButton(Component c) { |
||||
return c instanceof JButton |
||||
&& FineClientProperties.BUTTON_BORDER_RIGHT_ROUND_RECT.equals(getButtonBorderTypeStr((JButton) c)); |
||||
} |
||||
|
||||
/** |
||||
* 是否部分圆角矩形 |
||||
* |
||||
* @param c 组件 |
||||
* @return 是否部分圆角矩形 |
||||
*/ |
||||
public static boolean isPartRoundButton(Component c) { |
||||
return isLeftRoundButton(c) || isRightRoundButton(c); |
||||
} |
||||
|
||||
protected void paintBackground(Graphics g, JComponent c) { |
||||
if (isPartRoundButton(c)) { |
||||
Color background = getBackground(c); |
||||
if (background == null) { |
||||
return; |
||||
} |
||||
|
||||
Graphics2D g2 = (Graphics2D) g.create(); |
||||
try { |
||||
FlatUIUtils.setRenderingHints(g2); |
||||
float arc = FlatUIUtils.getBorderArc(c); |
||||
int width = c.getWidth(); |
||||
int height = c.getHeight(); |
||||
|
||||
g2.setColor(FlatUIUtils.deriveColor(background, getBackgroundBase(c, false))); |
||||
Path2D path2DLeft; |
||||
if (isLeftRoundButton(c)) { |
||||
path2DLeft = FineUIUtils.createLeftRoundRectangle(0, 0, width, height, arc); |
||||
} else { |
||||
path2DLeft = FineUIUtils.createRightRoundRectangle(0, 0, width, height, arc); |
||||
} |
||||
g2.fill(path2DLeft); |
||||
} finally { |
||||
g2.dispose(); |
||||
} |
||||
} else { |
||||
super.paintBackground(g, c); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 创建UI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineButtonUI(false); |
||||
} |
||||
|
||||
static String getButtonBorderTypeStr(AbstractButton c) { |
||||
Object value = c.getClientProperty(BUTTON_BORDER); |
||||
if (value instanceof String) { |
||||
return (String) value; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,37 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.icon.LazyIcon; |
||||
import com.formdev.flatlaf.ui.FlatCheckBoxUI; |
||||
|
||||
import javax.swing.AbstractButton; |
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
|
||||
/** |
||||
* 提供 {@link javax.swing.JCheckBox} 的UI类 |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2023/12/14 |
||||
*/ |
||||
public class FineCheckBoxUI extends FlatCheckBoxUI { |
||||
|
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineCheckBoxUI(false); |
||||
} |
||||
|
||||
protected FineCheckBoxUI(boolean shared) { |
||||
super(shared); |
||||
} |
||||
|
||||
@Override |
||||
public void installDefaults(AbstractButton b) { |
||||
super.installDefaults(b); |
||||
b.setIcon(new LazyIcon("checkbox_unchecked")); |
||||
b.setSelectedIcon(new LazyIcon("checkbox_checked")); |
||||
b.setRolloverIcon(new LazyIcon("checkbox_hovered")); |
||||
b.setDisabledIcon(new LazyIcon("checkbox_unchecked").disabled()); |
||||
b.setDisabledSelectedIcon(new LazyIcon("checkbox_checked").disabled()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,66 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
import com.fr.base.Utils; |
||||
import com.fr.design.gui.ibutton.UIColorButton; |
||||
|
||||
import javax.swing.ButtonModel; |
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Color; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Rectangle; |
||||
import java.awt.geom.RoundRectangle2D; |
||||
|
||||
import static com.fine.theme.utils.FineUIScale.scale; |
||||
|
||||
/** |
||||
* 颜色按钮 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2024/1/3 |
||||
*/ |
||||
public class FineColorButtonUI extends FineButtonUI { |
||||
|
||||
public static final float HEIGHT = 2.5f; |
||||
public static final float WIDTH = 14; |
||||
public static final float Y = 13.5f; |
||||
|
||||
/** |
||||
* @param shared |
||||
* @since 2 |
||||
*/ |
||||
protected FineColorButtonUI(boolean shared) { |
||||
super(shared); |
||||
} |
||||
|
||||
/** |
||||
* 创建UI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineColorButtonUI(false); |
||||
} |
||||
|
||||
@Override |
||||
protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect) { |
||||
super.paintIcon(g, c, iconRect); |
||||
UIColorButton b = (UIColorButton) c; |
||||
ButtonModel model = b.getModel(); |
||||
if (model.isEnabled()) { |
||||
g.setColor(b.getColor()); |
||||
} else { |
||||
g.setColor(new Color(Utils.filterRGB(b.getColor().getRGB(), 50))); |
||||
} |
||||
FlatUIUtils.setRenderingHints(g); |
||||
Graphics2D g2d = (Graphics2D) g; |
||||
float height = scale(HEIGHT); |
||||
float width = scale(WIDTH); |
||||
// 计算实际大小与icon区域大小的偏移,用于居中调整
|
||||
float offsetX = (iconRect.width - width) / 2.0f; |
||||
RoundRectangle2D.Float colorRect = new RoundRectangle2D.Float( |
||||
iconRect.x + offsetX, iconRect.y + scale(Y), width, height, height, height); |
||||
g2d.fill(colorRect); |
||||
} |
||||
} |
@ -0,0 +1,90 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineClientProperties; |
||||
import com.fine.theme.utils.FineUIStyle; |
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatPanelUI; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
import com.fr.design.gui.ibutton.UICombinationButton; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Color; |
||||
import java.awt.Graphics; |
||||
import java.beans.PropertyChangeEvent; |
||||
|
||||
import static com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; |
||||
|
||||
/** |
||||
* 双组件按钮UI |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/12/21 |
||||
*/ |
||||
public class FineCombinationButtonUI extends FlatPanelUI { |
||||
@Styleable(dot = true) |
||||
protected Color background; |
||||
|
||||
@Styleable(dot = true) |
||||
protected int arc; |
||||
|
||||
|
||||
/** |
||||
* @param shared |
||||
* @since 2 |
||||
*/ |
||||
protected FineCombinationButtonUI(boolean shared) { |
||||
super(shared); |
||||
} |
||||
|
||||
/** |
||||
* 创建UI |
||||
* |
||||
* @param c 组件 |
||||
* @return ComponentUI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineCombinationButtonUI(false); |
||||
} |
||||
|
||||
@Override |
||||
public void installUI(JComponent c) { |
||||
super.installUI(c); |
||||
background = FineUIUtils.getUIColor("CombinationButton.background", "desktop"); |
||||
arc = FineUIUtils.getUIInt("CombinationButton.arc", "Component.arc"); |
||||
} |
||||
|
||||
@Override |
||||
public void uninstallUI(JComponent c) { |
||||
super.uninstallUI(c); |
||||
} |
||||
|
||||
@Override |
||||
public void paint(Graphics g, JComponent c) { |
||||
paintBackground(g, c); |
||||
super.paint(g, c); |
||||
} |
||||
|
||||
protected void paintBackground(Graphics g, JComponent c) { |
||||
FlatUIUtils.setRenderingHints(g); |
||||
g.setColor(background); |
||||
g.fillRoundRect(0, 0, c.getWidth(), c.getHeight(), arc, arc); |
||||
} |
||||
|
||||
@Override |
||||
public void propertyChange(PropertyChangeEvent e) { |
||||
super.propertyChange(e); |
||||
switch (e.getPropertyName()) { |
||||
case FineClientProperties.STYLE_CLASS: |
||||
UICombinationButton b = (UICombinationButton) e.getSource(); |
||||
if (FineUIStyle.STYLE_PRIMARY.equals(e.getNewValue())) { |
||||
b.setPrimary(); |
||||
} |
||||
b.repaint(); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,67 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineClientProperties; |
||||
import com.fine.theme.utils.FineUIScale; |
||||
import com.formdev.flatlaf.ui.FlatComboBoxUI; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import javax.swing.JButton; |
||||
import javax.swing.JComponent; |
||||
import javax.swing.SwingConstants; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Dimension; |
||||
import java.awt.Graphics2D; |
||||
|
||||
/** |
||||
* 提供 {@link javax.swing.JComboBox} 的UI类 |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2023/12/07 |
||||
*/ |
||||
public class FineComboBoxUI extends FlatComboBoxUI { |
||||
|
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineComboBoxUI(); |
||||
} |
||||
|
||||
@Override |
||||
protected JButton createArrowButton() { |
||||
return new FineComboBoxButton(); |
||||
} |
||||
|
||||
protected class FineComboBoxButton extends FlatComboBoxButton { |
||||
|
||||
@Override |
||||
protected void paintArrow(Graphics2D g) { |
||||
if (isPopupVisible(comboBox)) { |
||||
setDirection(SwingConstants.NORTH); |
||||
} else { |
||||
setDirection(SwingConstants.SOUTH); |
||||
} |
||||
super.paintArrow(g); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Dimension getMinimumSize(JComponent c) { |
||||
// ComboBox基于子组件计算适配尺寸性能一般,仅考虑部分ComboBox进行适配计算,其他采用默认值
|
||||
if (FineClientProperties.ADAPTIVE_COMBO_BOX.equals(getComboBoxTypeStr(c))) { |
||||
return super.getMinimumSize(c); |
||||
} |
||||
return FineUIScale.scale(new Dimension( |
||||
FlatUIUtils.getUIInt("ComboBox.minimumWidth", 72), |
||||
FlatUIUtils.getUIInt("ComboBox.comboHeight", 24) |
||||
)); |
||||
} |
||||
|
||||
@Nullable |
||||
static String getComboBoxTypeStr(JComponent c) { |
||||
Object value = c.getClientProperty(FineClientProperties.COMBO_BOX_TYPE); |
||||
if (value instanceof String) { |
||||
return (String) value; |
||||
} |
||||
return null; |
||||
} |
||||
} |
@ -0,0 +1,309 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatPanelUI; |
||||
import com.fr.design.style.background.gradient.GradientBar; |
||||
import com.fr.design.style.background.gradient.SelectColorPointBtn; |
||||
import com.fr.stable.AssistUtils; |
||||
import com.fr.stable.os.OperatingSystem; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.UIManager; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Color; |
||||
import java.awt.Dimension; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.LinearGradientPaint; |
||||
import java.awt.RenderingHints; |
||||
import java.awt.event.MouseAdapter; |
||||
import java.awt.event.MouseEvent; |
||||
import java.awt.event.MouseListener; |
||||
import java.awt.event.MouseMotionAdapter; |
||||
import java.awt.geom.Path2D; |
||||
import java.awt.geom.Point2D; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 渐变色滑块 UI类 |
||||
* |
||||
* @author Leo.Qin |
||||
* @since 11.0 |
||||
* Created on 2023/12/19 |
||||
*/ |
||||
public class FineGradientBarUI extends FlatPanelUI { |
||||
|
||||
private int directionalShapeSize; |
||||
private int recHeight; |
||||
private int width; |
||||
private int height; |
||||
private int borderWidth; |
||||
private Color borderColor; |
||||
private Color thumbBorderColor; |
||||
private Color hoverThumbColor; |
||||
private Color pressedThumbColor; |
||||
|
||||
private MouseMotionAdapter mouseMotionListener; |
||||
|
||||
GradientBar gradientBar; |
||||
private List<SelectColorPointBtn> list; |
||||
private SelectColorPointBtn p1; |
||||
private SelectColorPointBtn p2; |
||||
private MouseListener mouseListener; |
||||
private double offset = 0.0001; |
||||
|
||||
boolean[] hoverStatus; |
||||
|
||||
protected FineGradientBarUI(boolean shared) { |
||||
super(shared); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 创建UI |
||||
* |
||||
* @param c 组件 |
||||
* @return UI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineGradientBarUI(false); |
||||
} |
||||
|
||||
@Override |
||||
public void installUI(JComponent c) { |
||||
super.installUI(c); |
||||
installDefaults(c); |
||||
|
||||
gradientBar = (GradientBar) c; |
||||
mouseMotionListener = new TrackMotionListener(); |
||||
mouseListener = new TrackMouseListener(); |
||||
gradientBar.addMouseMotionListener(mouseMotionListener); |
||||
gradientBar.addMouseListener(mouseListener); |
||||
} |
||||
|
||||
private void installDefaults(JComponent c) { |
||||
directionalShapeSize = FineUIUtils.getAndScaleInt("GradientBar.thumbWidth", 12); |
||||
recHeight = FineUIUtils.getAndScaleInt("GradientBar.recHeight", 30); |
||||
width = FineUIUtils.getAndScaleInt("GradientBar.recWidth", 160); |
||||
height = recHeight + directionalShapeSize; |
||||
borderWidth = FineUIUtils.getAndScaleInt("GradientBar.borderWidth", 1); |
||||
borderColor = UIManager.getColor("GradientBar.borderColor"); |
||||
thumbBorderColor = UIManager.getColor("GradientBar.thumbBorderColor"); |
||||
hoverThumbColor = UIManager.getColor("GradientBar.hoverThumbColor"); |
||||
pressedThumbColor = UIManager.getColor("GradientBar.pressedThumbColor"); |
||||
} |
||||
|
||||
private class TrackMouseListener extends MouseAdapter { |
||||
@Override |
||||
public void mouseExited(MouseEvent e) { |
||||
for (int i = 0; i < list.size(); i++) { |
||||
SelectColorPointBtn selectColorPointBtn = list.get(i); |
||||
selectColorPointBtn.setHover(false); |
||||
hoverStatus[i] = false; |
||||
} |
||||
|
||||
gradientBar.repaint(); |
||||
} |
||||
|
||||
@Override |
||||
public void mouseEntered(MouseEvent e) { |
||||
checkHoverStatus(e); |
||||
} |
||||
|
||||
@Override |
||||
public void mousePressed(MouseEvent e) { |
||||
for (SelectColorPointBtn btn : list) { |
||||
boolean hover = isOverBtn(e, btn); |
||||
btn.setPressed(hover); |
||||
} |
||||
|
||||
gradientBar.repaint(); |
||||
} |
||||
|
||||
@Override |
||||
public void mouseReleased(MouseEvent e) { |
||||
for (SelectColorPointBtn btn : list) { |
||||
btn.setPressed(false); |
||||
} |
||||
|
||||
gradientBar.repaint(); |
||||
} |
||||
} |
||||
|
||||
|
||||
private class TrackMotionListener extends MouseMotionAdapter { |
||||
int index; |
||||
|
||||
@Override |
||||
public void mouseDragged(MouseEvent e) { |
||||
if (!gradientBar.isDraggable()) { |
||||
return; |
||||
} |
||||
index = getSelectedIndex(e, index); |
||||
int halfSize = directionalShapeSize / 2; |
||||
boolean x = e.getX() <= gradientBar.getWidth() - halfSize && e.getX() >= halfSize; |
||||
if (x) { |
||||
list.get(index).setStartPosition((double) (e.getX() - halfSize) / (gradientBar.getWidth() - directionalShapeSize)); |
||||
gradientBar.repaint(); |
||||
} |
||||
} |
||||
|
||||
private int getSelectedIndex(MouseEvent e, int index) { |
||||
int oldIndex = index; |
||||
|
||||
for (int i = 0; i < list.size(); i++) { |
||||
if (list.get(i).contains(e.getX(), e.getY())) { |
||||
index = i; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (OperatingSystem.isLinux() && AssistUtils.equals(oldIndex, index)) { |
||||
if (Math.abs(p1.getX() - e.getX()) > Math.abs(p2.getX() - e.getX())) { |
||||
index = 1; |
||||
} else { |
||||
index = 0; |
||||
} |
||||
} |
||||
return index; |
||||
} |
||||
|
||||
@Override |
||||
public void mouseMoved(MouseEvent e) { |
||||
checkHoverStatus(e); |
||||
} |
||||
|
||||
} |
||||
|
||||
private void checkHoverStatus(MouseEvent e) { |
||||
boolean repaint = false; |
||||
for (int i = 0; i < list.size(); i++) { |
||||
SelectColorPointBtn btn = list.get(i); |
||||
boolean hover = isOverBtn(e, btn); |
||||
if (hoverStatus[i] != hover) { |
||||
repaint = true; |
||||
hoverStatus[i] = hover; |
||||
btn.setHover(hover); |
||||
} |
||||
} |
||||
|
||||
if (repaint) { |
||||
gradientBar.repaint(); |
||||
} |
||||
} |
||||
|
||||
private boolean isOverBtn(MouseEvent e, SelectColorPointBtn btn) { |
||||
return btn.contains(e.getX(), e.getY()); |
||||
} |
||||
|
||||
@Override |
||||
public void paint(Graphics g, JComponent c) { |
||||
Graphics2D g2 = (Graphics2D) g; |
||||
|
||||
|
||||
gradientBar = (GradientBar) c; |
||||
list = gradientBar.getList(); |
||||
p1 = gradientBar.getSelectColorPointBtnP1(); |
||||
p2 = gradientBar.getSelectColorPointBtnP2(); |
||||
if (hoverStatus == null) { |
||||
hoverStatus = new boolean[list.size()]; |
||||
} |
||||
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
||||
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); |
||||
|
||||
GradientBar component = (GradientBar) c; |
||||
List<SelectColorPointBtn> btnList = component.getList(); |
||||
Collections.sort(btnList); |
||||
|
||||
|
||||
paintBorder(g2, component); |
||||
paintContent(g2, component); |
||||
paintButton(g2, btnList); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 实际绘制区域x范围(directionalShapeSize / 2, width - directionalShapeSize) |
||||
*/ |
||||
private void paintContent(Graphics2D g2d, GradientBar c) { |
||||
List<SelectColorPointBtn> btnList = c.getList(); |
||||
|
||||
int halfSize = directionalShapeSize / 2; |
||||
Point2D start = new Point2D.Float(halfSize, 0); |
||||
Point2D end = new Point2D.Float(c.getWidth() - halfSize, 0); |
||||
|
||||
|
||||
Collections.sort(btnList); |
||||
Color[] colors = new Color[btnList.size()]; |
||||
for (int i = 0; i < btnList.size(); i++) { |
||||
colors[i] = btnList.get(i).getColorInner(); |
||||
} |
||||
|
||||
float[] dist = getColorFloats(c, btnList); |
||||
LinearGradientPaint paint = new LinearGradientPaint(start, end, dist, colors); |
||||
g2d.setPaint(paint); |
||||
g2d.fillRect(halfSize + borderWidth, borderWidth, c.getWidth() - directionalShapeSize - borderWidth * 2, recHeight - borderWidth * 2); |
||||
} |
||||
|
||||
private float[] getColorFloats(GradientBar c, List<SelectColorPointBtn> btnList) { |
||||
float[] dist = new float[btnList.size()]; |
||||
for (int i = 0; i < btnList.size(); i++) { |
||||
if (btnList.get(i).getStartPosition() < 0) { |
||||
dist[i] = 0; |
||||
} else if (btnList.get(i).getStartPosition() > 1) { |
||||
dist[i] = 1; |
||||
} else { |
||||
dist[i] = (float) btnList.get(i).getStartPosition(); |
||||
} |
||||
|
||||
btnList.get(i).setX(dist[i] * (c.getWidth() - directionalShapeSize) + (double) directionalShapeSize / 2); |
||||
btnList.get(i).setY(recHeight); |
||||
} |
||||
|
||||
float dist1 = dist[btnList.size() - 1]; |
||||
float dist2 = dist[btnList.size() - 2]; |
||||
if (AssistUtils.equals(dist1, dist2)) { |
||||
dist[btnList.size() - 1] = (float) (dist2 + offset); |
||||
} |
||||
return dist; |
||||
} |
||||
|
||||
private void paintBorder(Graphics2D g2d, GradientBar c) { |
||||
int halfSize = directionalShapeSize / 2; |
||||
if (borderColor == null) { |
||||
return; |
||||
} |
||||
g2d.setColor(borderColor); |
||||
g2d.fillRect(halfSize, 0, c.getWidth() - directionalShapeSize, recHeight); |
||||
} |
||||
|
||||
private void paintButton(Graphics2D g2d, List<SelectColorPointBtn> list) { |
||||
|
||||
for (SelectColorPointBtn selectColorPointBtn : list) { |
||||
Path2D directionalThumbShape = FineSliderUI.createDirectionalThumbShape((float) selectColorPointBtn.getX() - (float) directionalShapeSize / 2, (float) selectColorPointBtn.getY(), directionalShapeSize, directionalShapeSize, 0); |
||||
if (selectColorPointBtn.isHover() && hoverThumbColor != null) { |
||||
g2d.setColor(hoverThumbColor); |
||||
g2d.fill(directionalThumbShape); |
||||
} else if (selectColorPointBtn.isPressed() && pressedThumbColor != null) { |
||||
g2d.setColor(pressedThumbColor); |
||||
g2d.fill(directionalThumbShape); |
||||
} else if (thumbBorderColor != null) { |
||||
g2d.setColor(thumbBorderColor); |
||||
g2d.fill(directionalThumbShape); |
||||
} |
||||
selectColorPointBtn.updatePath(directionalThumbShape); |
||||
|
||||
Path2D innerThumbShape = FineSliderUI.createDirectionalThumbShape((float) selectColorPointBtn.getX() - (float) directionalShapeSize / 2 + borderWidth, (float) selectColorPointBtn.getY() + borderWidth, directionalShapeSize - borderWidth * 2, directionalShapeSize - borderWidth * 2, 0); |
||||
g2d.setColor(selectColorPointBtn.getColorInner()); |
||||
g2d.fill(innerThumbShape); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public Dimension getPreferredSize(JComponent c) { |
||||
return new Dimension(width, height); |
||||
} |
||||
} |
@ -0,0 +1,74 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import javax.swing.plaf.PanelUI; |
||||
import java.awt.Color; |
||||
import java.awt.Dimension; |
||||
import java.awt.Graphics; |
||||
|
||||
import static com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; |
||||
|
||||
/** |
||||
* HeadGroup 的UI类 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/12/15 |
||||
*/ |
||||
public class FineHeadGroupUI extends PanelUI { |
||||
|
||||
@Styleable(dot = true) |
||||
protected Color background; |
||||
|
||||
@Styleable(dot = true) |
||||
protected int arc; |
||||
|
||||
/** |
||||
* 创建UI |
||||
* |
||||
* @param c 组件 |
||||
* @return ComponentUI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineHeadGroupUI(); |
||||
} |
||||
|
||||
@Override |
||||
public void installUI(JComponent c) { |
||||
super.installUI(c); |
||||
background = FineUIUtils.getUIColor("HeadGroup.background", "desktop"); |
||||
arc = FineUIUtils.getUIInt("HeadGroup.arc", "Component.arc"); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void uninstallUI(JComponent c) { |
||||
super.uninstallUI(c); |
||||
} |
||||
|
||||
@Override |
||||
public Dimension getMinimumSize(JComponent component) { |
||||
return new Dimension(0, 0); |
||||
} |
||||
|
||||
@Override |
||||
public Dimension getMaximumSize(JComponent component) { |
||||
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); |
||||
} |
||||
|
||||
@Override |
||||
public void update(Graphics g, JComponent c) { |
||||
paintBackground(g, c); |
||||
paint(g, c); |
||||
} |
||||
|
||||
protected void paintBackground(Graphics g, JComponent c) { |
||||
FlatUIUtils.setRenderingHints(g); |
||||
g.setColor(background); |
||||
g.fillRoundRect(0, 0, c.getWidth(), c.getHeight(), arc, arc); |
||||
} |
||||
} |
@ -0,0 +1,112 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.icon.LazyIcon; |
||||
import com.fine.theme.utils.AnimatedPainter; |
||||
import com.fine.theme.utils.FineUIScale; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
import com.fr.design.foldablepane.HeaderPane; |
||||
|
||||
import javax.swing.Icon; |
||||
import javax.swing.JComponent; |
||||
import javax.swing.UIManager; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import javax.swing.plaf.PanelUI; |
||||
import java.awt.AlphaComposite; |
||||
import java.awt.Component; |
||||
import java.awt.Font; |
||||
import java.awt.FontMetrics; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Insets; |
||||
import java.awt.geom.AffineTransform; |
||||
|
||||
/** |
||||
* 标题组件UI |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2024/1/29 |
||||
*/ |
||||
public class FineHeaderPaneUI extends PanelUI implements AnimatedPainter { |
||||
|
||||
private Icon triangleRight; |
||||
|
||||
private static final int ICON_FIX = -2; |
||||
|
||||
/** |
||||
* 创建UI |
||||
* |
||||
* @param c 组件 |
||||
* @return ComponentUI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineHeaderPaneUI(); |
||||
} |
||||
|
||||
@Override |
||||
public void installUI(JComponent c) { |
||||
super.installUI(c); |
||||
triangleRight = new LazyIcon("triangle_right"); |
||||
} |
||||
|
||||
@Override |
||||
public void uninstallUI(JComponent c) { |
||||
super.uninstallUI(c); |
||||
} |
||||
|
||||
@Override |
||||
public void paint(Graphics g, JComponent c) { |
||||
paintWithAnimation(c, g, c.getX(), c.getY(), c.getWidth(), c.getHeight()); |
||||
} |
||||
|
||||
@Override |
||||
public void paintAnimated(Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues) { |
||||
Graphics2D g2d = (Graphics2D) g.create(); |
||||
HeaderPane headerPane = (HeaderPane) c; |
||||
g2d.setColor(c.getBackground()); |
||||
if (headerPane.isPressed()) { |
||||
g2d.setColor(UIManager.getColor("Button.pressedBackground")); |
||||
} |
||||
Insets insets = headerPane.getInsets(); |
||||
g2d.fillRect(0, insets.top / 2, |
||||
headerPane.getWidth(), headerPane.getHeight() - (insets.top + insets.bottom) / 2); |
||||
|
||||
int iconY = (headerPane.getHeight() - triangleRight.getIconHeight()) / 2; |
||||
|
||||
|
||||
// 动起来,旋转角度(弧度),动画帧绘制开始
|
||||
double rotationAngle = Math.toRadians(90 * animatedValues[0]); |
||||
|
||||
// 设置旋转中心
|
||||
AffineTransform oldTransform = g2d.getTransform(); |
||||
// 折叠面板需要icon显示左边缘对齐,fix一下
|
||||
g2d.rotate(rotationAngle, FineUIScale.scale(ICON_FIX) + triangleRight.getIconWidth() / 2.0, |
||||
iconY + triangleRight.getIconHeight() / 2.0); |
||||
// 绘制旋转后的正方形
|
||||
triangleRight.paintIcon(c, g2d, FineUIScale.scale(ICON_FIX), iconY); |
||||
// 恢复原始的变换
|
||||
g2d.setTransform(oldTransform); |
||||
// ----- 动画帧绘制结束 -----
|
||||
|
||||
g2d.setFont(c.getFont().deriveFont(Font.BOLD)); |
||||
g2d.setPaint(c.getForeground()); |
||||
g2d.setComposite(AlphaComposite.SrcOver.derive(c.getForeground().getAlpha() / 255f)); |
||||
|
||||
FontMetrics metrics = g2d.getFontMetrics(); |
||||
int ascent = metrics.getAscent(); |
||||
int descent = metrics.getDescent(); |
||||
|
||||
float titleX = triangleRight.getIconWidth() + FineUIScale.scale(ICON_FIX) |
||||
+ FineUIScale.scale(UIManager.getInt("ExpandablePane.HeaderPane.hGap")); |
||||
float titleY = (headerPane.getHeight() - (ascent + descent)) / 2.0f + ascent; |
||||
FlatUIUtils.setRenderingHints(g2d); |
||||
g2d.drawString(headerPane.getTitle(), titleX, titleY); |
||||
g2d.dispose(); |
||||
} |
||||
|
||||
@Override |
||||
public float[] getValues(Component c) { |
||||
HeaderPane headerPane = (HeaderPane) c; |
||||
return new float[]{headerPane.isShow() ? 1 : 0}; |
||||
} |
||||
} |
@ -0,0 +1,68 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatPanelUI; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.JPanel; |
||||
import javax.swing.UIManager; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.beans.PropertyChangeEvent; |
||||
|
||||
/** |
||||
* Input输入框类组件 UI类 |
||||
* |
||||
* @author Leo.Qin |
||||
* @since 11.0 |
||||
* Created on 2023/12/11 |
||||
*/ |
||||
public class FineInputUI extends FlatPanelUI { |
||||
|
||||
private static final String ENABLED = "enabled"; |
||||
private static final String EDITABLE = "editable"; |
||||
private final int defaultArc = 5; |
||||
|
||||
public FineInputUI(boolean shared) { |
||||
super(shared); |
||||
} |
||||
|
||||
/** |
||||
* 创建UI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineInputUI(false); |
||||
} |
||||
|
||||
@Override |
||||
public void installUI(JComponent c) { |
||||
super.installUI(c); |
||||
c.setBackground(UIManager.getColor("Input.background")); |
||||
c.setBorder(UIManager.getBorder("Input.border")); |
||||
} |
||||
|
||||
@Override |
||||
protected void installDefaults(JPanel p) { |
||||
super.installDefaults(p); |
||||
arc = FineUIUtils.getAndScaleInt("Input.arc", defaultArc); |
||||
} |
||||
|
||||
@Override |
||||
public void propertyChange(PropertyChangeEvent e) { |
||||
String propertyName = e.getPropertyName(); |
||||
if (EDITABLE.equals(propertyName) || ENABLED.equals(propertyName)) { |
||||
updateBackground(e); |
||||
} else { |
||||
super.propertyChange(e); |
||||
} |
||||
} |
||||
|
||||
private void updateBackground(PropertyChangeEvent e) { |
||||
JPanel source = (JPanel) e.getSource(); |
||||
if (e.getNewValue() == Boolean.FALSE) { |
||||
source.setBackground(UIManager.getColor("Input.disabledBackground")); |
||||
} else { |
||||
source.setBackground(UIManager.getColor("Input.background")); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,17 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.icon.JsonIconSet; |
||||
import com.fine.theme.icon.UrlIconResource; |
||||
|
||||
/** |
||||
* Fine 亮主题图标集 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2024/5/7 |
||||
*/ |
||||
public class FineLightIconSet extends JsonIconSet { |
||||
public FineLightIconSet() { |
||||
super(new UrlIconResource("com/fine/theme/light/ui/fine_light.icon.json")); |
||||
} |
||||
} |
@ -0,0 +1,80 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.icon.LazyIcon; |
||||
import com.fine.theme.utils.FineClientProperties; |
||||
import com.formdev.flatlaf.ui.FlatMenuItemRenderer; |
||||
import com.formdev.flatlaf.ui.FlatMenuItemUI; |
||||
|
||||
import javax.swing.Icon; |
||||
import javax.swing.JComponent; |
||||
import javax.swing.JMenuItem; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Color; |
||||
|
||||
import java.awt.Font; |
||||
|
||||
import java.awt.Graphics; |
||||
import java.awt.Rectangle; |
||||
|
||||
/** |
||||
* menuItem UI类 |
||||
* |
||||
* @author Leo.Qin |
||||
* @since 11.0 |
||||
* Created on 2024/1/8 |
||||
*/ |
||||
public class FineMenuItemUI extends FlatMenuItemUI { |
||||
int iconSize = 16; |
||||
int rightMargin = 10; |
||||
|
||||
/** |
||||
* 创建UI |
||||
* |
||||
* @param c |
||||
* @return |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineMenuItemUI(); |
||||
} |
||||
|
||||
@Override |
||||
protected FlatMenuItemRenderer createRenderer() { |
||||
return new FineMenuItemRenderer(menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter); |
||||
} |
||||
|
||||
@Override |
||||
public void paint(Graphics g, JComponent c) { |
||||
super.paint(g, c); |
||||
|
||||
Object itemType = c.getClientProperty(FineClientProperties.MENU_ITEM_TYPE); |
||||
if (FineClientProperties.MENU_ITEM_TYPE_LOCK.equals(itemType)) { |
||||
LazyIcon icon = new LazyIcon("locked"); |
||||
|
||||
icon.paintIcon(c, g, c.getWidth() - rightMargin - iconSize, (c.getHeight() - iconSize) / 2); |
||||
} |
||||
} |
||||
|
||||
static class FineMenuItemRenderer extends FlatMenuItemRenderer { |
||||
protected FineMenuItemRenderer(JMenuItem menuItem, Icon checkIcon, Icon arrowIcon, |
||||
Font acceleratorFont, String acceleratorDelimiter ) |
||||
{ |
||||
super(menuItem, checkIcon, arrowIcon, acceleratorFont, acceleratorDelimiter); |
||||
} |
||||
|
||||
@Override |
||||
protected void paintText( Graphics g, Rectangle textRect, String text, Color selectionForeground, Color disabledForeground ) { |
||||
if (!isTopLevelMenu(menuItem) && menuItem.getIcon() == null) { |
||||
textRect.x -= minimumIconSize.width; |
||||
} |
||||
super.paintText(g, textRect, text, selectionForeground, disabledForeground); |
||||
} |
||||
|
||||
@Override |
||||
protected void paintIcon(Graphics g, Rectangle iconRect, Icon icon, Color checkBackground, Color selectionBackground) { |
||||
if (menuItem.getIcon() != null) { |
||||
super.paintIcon(g, iconRect, icon, checkBackground, selectionBackground); |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,38 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIStyle; |
||||
import com.formdev.flatlaf.ui.FlatOptionPaneUI; |
||||
|
||||
import javax.swing.JButton; |
||||
import javax.swing.JComponent; |
||||
import javax.swing.UIManager; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Component; |
||||
import java.awt.Container; |
||||
|
||||
/** |
||||
* @author Renekton |
||||
* @since 2024/08/15 |
||||
* Created on 2024/08/15 |
||||
*/ |
||||
public class FineOptionPaneUI extends FlatOptionPaneUI { |
||||
|
||||
/** |
||||
* 注册当前类的UI对象 |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c ) { |
||||
return new FineOptionPaneUI(); |
||||
} |
||||
|
||||
@Override |
||||
protected void addButtonComponents(Container container, Object[] buttons, |
||||
int initialIndex) { |
||||
super.addButtonComponents(container, buttons, initialIndex); |
||||
Component[] components = container.getComponents(); |
||||
for (Component component: components) { |
||||
if (UIManager.getString("OptionPane.okButtonText", optionPane.getLocale()).equals(((JButton) component).getText())) { |
||||
FineUIStyle.setStyle((JButton) component, FineUIStyle.STYLE_PRIMARY); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,55 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatPanelUI; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
|
||||
import java.awt.Graphics; |
||||
|
||||
import static com.fine.theme.utils.FineClientProperties.PANEL_TYPE; |
||||
import static com.fine.theme.utils.FineClientProperties.ROUNDED_PANEL; |
||||
|
||||
/** |
||||
* Panel 面板UI |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2024/08/02 |
||||
*/ |
||||
public class FinePanelUI extends FlatPanelUI { |
||||
|
||||
/** |
||||
* @param shared |
||||
* @since 2 |
||||
*/ |
||||
protected FinePanelUI(boolean shared) { |
||||
super(shared); |
||||
} |
||||
|
||||
/** |
||||
* 创建UI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FinePanelUI(false); |
||||
} |
||||
|
||||
@Override |
||||
public void update(Graphics g, JComponent c) { |
||||
if (ROUNDED_PANEL.equals(getPanelTypeStr(c))) { |
||||
this.arc = FineUIUtils.getUIInt("Panel.arc", "Component.arc"); |
||||
} |
||||
super.update(g, c); |
||||
} |
||||
|
||||
@Nullable |
||||
static String getPanelTypeStr(JComponent p) { |
||||
Object value = p.getClientProperty(PANEL_TYPE); |
||||
if (value instanceof String) { |
||||
return (String) value; |
||||
} |
||||
return null; |
||||
} |
||||
} |
@ -0,0 +1,19 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatPopupMenuBorder; |
||||
|
||||
/** |
||||
* PopupMenu Border类 |
||||
* |
||||
* @author Leo.Qin |
||||
* @since 11.0 |
||||
* Created on 2023/12/25 |
||||
*/ |
||||
public class FinePopupMenuBorder extends FlatPopupMenuBorder { |
||||
|
||||
@Override |
||||
public int getArc() { |
||||
return FineUIUtils.getAndScaleInt("PopupMenu.arc", 5); |
||||
} |
||||
} |
@ -0,0 +1,70 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatPopupMenuSeparatorUI; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.JSeparator; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Insets; |
||||
import java.awt.geom.Rectangle2D; |
||||
|
||||
import static com.formdev.flatlaf.util.UIScale.scale; |
||||
|
||||
/** |
||||
* popup弹窗分割线UI |
||||
* |
||||
* @author Leo.Qin |
||||
* @since 11.0 |
||||
* Created on 2024/1/8 |
||||
*/ |
||||
public class FinePopupMenuSeparatorUI extends FlatPopupMenuSeparatorUI { |
||||
protected Insets insets; |
||||
|
||||
/** |
||||
* @param shared |
||||
* @since 2 |
||||
*/ |
||||
protected FinePopupMenuSeparatorUI(boolean shared) { |
||||
super(shared); |
||||
} |
||||
|
||||
/** |
||||
* 创建UI类 |
||||
* |
||||
* @param c |
||||
* @return |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FinePopupMenuSeparatorUI(false); |
||||
} |
||||
|
||||
@Override |
||||
protected void installDefaults(JSeparator s) { |
||||
super.installDefaults(s); |
||||
insets = FineUIUtils.getAndScaleUIInsets("PopupMenuSeparator.Insets", new Insets(0, 10, 0, 10)); |
||||
} |
||||
|
||||
@Override |
||||
public void paint(Graphics g, JComponent c) { |
||||
Graphics2D g2 = (Graphics2D) g.create(); |
||||
try { |
||||
FlatUIUtils.setRenderingHints(g2); |
||||
g2.setColor(c.getForeground()); |
||||
|
||||
float width = scale((float) stripeWidth); |
||||
float indent = scale((float) stripeIndent); |
||||
|
||||
if (((JSeparator) c).getOrientation() == JSeparator.VERTICAL) { |
||||
g2.fill(new Rectangle2D.Float(indent, insets.left, width - (insets.left + insets.right), c.getHeight())); |
||||
} else { |
||||
g2.fill(new Rectangle2D.Float(insets.left, indent, c.getWidth() - (insets.left + insets.right), width)); |
||||
} |
||||
} finally { |
||||
g2.dispose(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,58 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatPopupMenuUI; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.RenderingHints; |
||||
import java.awt.geom.RoundRectangle2D; |
||||
|
||||
/** |
||||
* PopupMenu UI类 |
||||
* |
||||
* @author Leo.Qin |
||||
* @since 11.0 |
||||
* Created on 2023/12/25 |
||||
*/ |
||||
public class FinePopupMenuUI extends FlatPopupMenuUI { |
||||
private int arc; |
||||
private final int DEFAULT_ARC = 10; |
||||
|
||||
/** |
||||
* 创建UI |
||||
* |
||||
* @param c 组件 |
||||
* @return UI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FinePopupMenuUI(); |
||||
} |
||||
|
||||
@Override |
||||
public void installDefaults() { |
||||
super.installDefaults(); |
||||
arc = FineUIUtils.getAndScaleInt("PopupMenu.arc", DEFAULT_ARC); |
||||
} |
||||
|
||||
@Override |
||||
public void paint(Graphics g, JComponent c) { |
||||
|
||||
// 绘制圆角矩形作为弹窗背景
|
||||
Graphics2D g2d = (Graphics2D) g; |
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
||||
RoundRectangle2D roundRect = new RoundRectangle2D.Double(0, 0, c.getWidth(), c.getHeight(), arc, arc); |
||||
g2d.setColor(c.getBackground()); |
||||
g2d.fill(roundRect); |
||||
|
||||
// 绘制组件内容
|
||||
super.paint(g, c); |
||||
} |
||||
|
||||
@Override |
||||
public void update(Graphics g, JComponent c) { |
||||
paint(g, c); |
||||
} |
||||
} |
@ -0,0 +1,28 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatRoundBorder; |
||||
|
||||
import javax.swing.UIManager; |
||||
import java.awt.Color; |
||||
import java.awt.Component; |
||||
|
||||
/** |
||||
* 报表编辑区域边框 |
||||
* |
||||
* @author Leo.Qin |
||||
* @since 11.0 |
||||
* Created on 2024/1/2 |
||||
*/ |
||||
public class FineReportComponentBorder extends FlatRoundBorder { |
||||
|
||||
@Override |
||||
protected int getArc(Component c) { |
||||
return FineUIUtils.getAndScaleInt("Center.arc", 10); |
||||
} |
||||
|
||||
@Override |
||||
protected Color getBorderColor(Component c) { |
||||
return UIManager.getColor("Center.ZoneBorderColor"); |
||||
} |
||||
} |
@ -0,0 +1,40 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatPanelUI; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.JPanel; |
||||
import javax.swing.plaf.ComponentUI; |
||||
|
||||
/** |
||||
* 报表编辑区域UI |
||||
* |
||||
* @author Leo.Qin |
||||
* @since 11.0 |
||||
* Created on 2024/1/2 |
||||
*/ |
||||
public class FineReportComponentCompositeUI extends FlatPanelUI { |
||||
/** |
||||
* @param shared |
||||
* @since 2 |
||||
*/ |
||||
protected FineReportComponentCompositeUI(boolean shared) { |
||||
super(shared); |
||||
} |
||||
|
||||
/** |
||||
* 创建UI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineReportComponentCompositeUI(false); |
||||
} |
||||
|
||||
@Override |
||||
protected void installDefaults(JPanel p) { |
||||
super.installDefaults(p); |
||||
this.arc = FineUIUtils.getAndScaleInt("Center.arc", 10); |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,63 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatRoundBorder; |
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; |
||||
import com.fr.design.event.HoverAware; |
||||
|
||||
import java.awt.Color; |
||||
import java.awt.Component; |
||||
import java.awt.Paint; |
||||
import java.util.StringJoiner; |
||||
|
||||
|
||||
/** |
||||
* 通用的Border类,具备hover、click、禁用等多种状态 |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2023/12/06 |
||||
*/ |
||||
public class FineRoundBorder extends FlatRoundBorder { |
||||
|
||||
@Styleable(dot = true) |
||||
protected Color borderColor = FineUIUtils.getUIColor("defaultBorderColor", "Component.borderColor"); |
||||
@Styleable(dot = true) |
||||
protected Color disabledBorderColor = FineUIUtils.getUIColor("defaultBorderColor", "Component.disabledBorderColor"); |
||||
@Styleable(dot = true) |
||||
protected Color highlightBorderColor = FineUIUtils.getUIColor("defaultHighlightBorderColor", "Component.focusedBorderColor"); |
||||
@Styleable(dot = true) |
||||
protected Color focusColor = FineUIUtils.getUIColor("defaultBorderFocusShadow", "Component.focusedBorderColor"); |
||||
|
||||
@Override |
||||
protected Paint getBorderColor(Component c) { |
||||
if (isEnabled(c)) { |
||||
if (c instanceof HoverAware && ((HoverAware) c).isHovered()) { |
||||
return getHoverBorderColor(); |
||||
} else { |
||||
return isFocused(c) ? focusedBorderColor : borderColor; |
||||
} |
||||
} |
||||
return disabledBorderColor; |
||||
} |
||||
|
||||
@Override |
||||
protected Color getFocusColor(Component c) { |
||||
return focusColor; |
||||
} |
||||
|
||||
protected Color getHoverBorderColor() { |
||||
return highlightBorderColor; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
return new StringJoiner(", ", FineRoundBorder.class.getSimpleName() + "[", "]") |
||||
.add("borderColor=" + borderColor) |
||||
.add("arc=" + arc) |
||||
.add("roundRect=" + roundRect) |
||||
.add("borderWidth=" + borderWidth) |
||||
.toString(); |
||||
} |
||||
} |
@ -0,0 +1,49 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import javax.swing.plaf.PanelUI; |
||||
import java.awt.Dimension; |
||||
|
||||
/** |
||||
* 选择框面板UI,应用于 {@link com.fr.design.style.AbstractSelectBox} |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2023/12/15 |
||||
*/ |
||||
public class FineSelectBoxUI extends PanelUI { |
||||
|
||||
private static final int DEFAULT_BOX_HEIGHT = 24; |
||||
|
||||
protected int boxHeight; |
||||
|
||||
/** |
||||
* 创建UI |
||||
* |
||||
* @param c 组件 |
||||
* @return ComponentUI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineSelectBoxUI(); |
||||
} |
||||
|
||||
@Override |
||||
public void installUI(JComponent c) { |
||||
super.installUI(c); |
||||
boxHeight = FineUIUtils.getAndScaleInt("ComboBox.comboHeight", DEFAULT_BOX_HEIGHT); |
||||
c.setBorder(new FineRoundBorder()); |
||||
} |
||||
|
||||
@Override |
||||
public void uninstallUI(JComponent c) { |
||||
super.uninstallUI(c); |
||||
} |
||||
|
||||
@Override |
||||
public Dimension getPreferredSize(JComponent c) { |
||||
return new Dimension(c.getWidth(), boxHeight); |
||||
} |
||||
} |
@ -0,0 +1,344 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatSliderUI; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
import com.formdev.flatlaf.util.HiDPIUtils; |
||||
import com.formdev.flatlaf.util.UIScale; |
||||
import com.fr.stable.AssistUtils; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.JSlider; |
||||
import javax.swing.UIManager; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Color; |
||||
import java.awt.Component; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Rectangle; |
||||
import java.awt.Shape; |
||||
import java.awt.geom.Ellipse2D; |
||||
import java.awt.geom.Path2D; |
||||
import java.awt.geom.RoundRectangle2D; |
||||
import java.util.Dictionary; |
||||
import java.util.Enumeration; |
||||
|
||||
/** |
||||
* 滑块slider UI类 |
||||
* |
||||
* @author Leo.Qin |
||||
* @since 11.0 |
||||
* Created on 2023/12/15 |
||||
*/ |
||||
public class FineSliderUI extends FlatSliderUI { |
||||
|
||||
private final int DEFAULT_LABEL_HEIGHT = 13; |
||||
private Color defaultForeground; |
||||
private int defaultLabelHeight; |
||||
|
||||
/** |
||||
* 创建UI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineSliderUI(); |
||||
} |
||||
|
||||
@Override |
||||
protected void installDefaults(JSlider slider) { |
||||
super.installDefaults(slider); |
||||
defaultForeground = UIManager.getColor("Slider.foreground"); |
||||
defaultLabelHeight = FineUIUtils.getAndScaleInt("Slider.labelHeight", DEFAULT_LABEL_HEIGHT); |
||||
} |
||||
|
||||
@Override |
||||
protected void calculateLabelRect() { |
||||
|
||||
if (slider.getPaintLabels()) { |
||||
calLabelRectWhenPaint(); |
||||
} else { |
||||
calLabelRectWhenNotPaint(); |
||||
} |
||||
} |
||||
|
||||
private void calLabelRectWhenPaint() { |
||||
labelRect.y = 0; |
||||
|
||||
if (slider.getOrientation() == JSlider.HORIZONTAL) { |
||||
labelRect.x = tickRect.x - trackBuffer; |
||||
labelRect.width = tickRect.width + (trackBuffer * 2); |
||||
labelRect.height = getHeightOfTallestLabel(); |
||||
} else { |
||||
if (isLeftToRight(slider)) { |
||||
labelRect.x = tickRect.x + tickRect.width; |
||||
labelRect.width = getWidthOfWidestLabel(); |
||||
} else { |
||||
labelRect.width = getWidthOfWidestLabel(); |
||||
labelRect.x = tickRect.x - labelRect.width; |
||||
} |
||||
labelRect.height = tickRect.height + (trackBuffer * 2); |
||||
} |
||||
} |
||||
|
||||
private void calLabelRectWhenNotPaint() { |
||||
labelRect.y = 0; |
||||
|
||||
if (slider.getOrientation() == JSlider.HORIZONTAL) { |
||||
labelRect.x = tickRect.x; |
||||
labelRect.width = tickRect.width; |
||||
labelRect.height = 0; |
||||
} else { |
||||
if (isLeftToRight(slider)) { |
||||
labelRect.x = tickRect.x + tickRect.width; |
||||
} else { |
||||
labelRect.x = tickRect.x; |
||||
} |
||||
labelRect.width = 0; |
||||
labelRect.height = tickRect.height; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void calculateTrackRect() { |
||||
if (slider.getOrientation() == JSlider.HORIZONTAL) { |
||||
calHorizontalTrackRect(); |
||||
} else { |
||||
calVerticalTrackRect(); |
||||
} |
||||
} |
||||
|
||||
private void calVerticalTrackRect() { |
||||
int centerSpacing; |
||||
centerSpacing = thumbRect.width; |
||||
if (isLeftToRight(slider)) { |
||||
if (slider.getPaintTicks()) { |
||||
centerSpacing += getTickLength(); |
||||
} |
||||
if (slider.getPaintLabels()) { |
||||
centerSpacing += getWidthOfWidestLabel(); |
||||
} |
||||
} else { |
||||
if (slider.getPaintTicks()) { |
||||
centerSpacing -= getTickLength(); |
||||
} |
||||
if (slider.getPaintLabels()) { |
||||
centerSpacing -= getWidthOfWidestLabel(); |
||||
} |
||||
} |
||||
trackRect.x = contentRect.x + getWidthOfWidestLabel() + (contentRect.width - centerSpacing - 1) / 2; |
||||
trackRect.y = contentRect.y + trackBuffer; |
||||
trackRect.width = thumbRect.width; |
||||
trackRect.height = contentRect.height - (trackBuffer * 2); |
||||
} |
||||
|
||||
private void calHorizontalTrackRect() { |
||||
int centerSpacing; |
||||
centerSpacing = thumbRect.height; |
||||
if (slider.getPaintTicks()) { |
||||
centerSpacing += getTickLength(); |
||||
} |
||||
|
||||
if (slider.getPaintLabels()) { |
||||
centerSpacing += getHeightOfTallestLabel(); |
||||
} |
||||
trackRect.x = contentRect.x + trackBuffer; |
||||
trackRect.y = contentRect.y + getHeightOfTallestLabel() + (contentRect.height - centerSpacing - 1) / 2; |
||||
trackRect.width = contentRect.width - (trackBuffer * 2); |
||||
trackRect.height = thumbRect.height; |
||||
} |
||||
|
||||
@Override |
||||
protected int getHeightOfTallestLabel() { |
||||
Dictionary dictionary = slider.getLabelTable(); |
||||
int tallest = 0; |
||||
if (dictionary != null) { |
||||
Enumeration keys = dictionary.keys(); |
||||
while (keys.hasMoreElements()) { |
||||
JComponent label = (JComponent) dictionary.get(keys.nextElement()); |
||||
tallest = Math.max(label.getPreferredSize().height, tallest); |
||||
} |
||||
} |
||||
return Math.min(tallest, defaultLabelHeight); |
||||
} |
||||
|
||||
@Override |
||||
protected int getWidthOfWidestLabel() { |
||||
Dictionary dictionary = slider.getLabelTable(); |
||||
int widest = 0; |
||||
if (dictionary != null) { |
||||
Enumeration keys = dictionary.keys(); |
||||
while (keys.hasMoreElements()) { |
||||
JComponent label = (JComponent) dictionary.get(keys.nextElement()); |
||||
widest = Math.max(label.getPreferredSize().width, widest); |
||||
} |
||||
} |
||||
return Math.min(widest, defaultLabelHeight); |
||||
} |
||||
|
||||
/** |
||||
* Convenience function for determining ComponentOrientation. Helps us |
||||
* avoid having Munge directives throughout the code. |
||||
*/ |
||||
static boolean isLeftToRight(Component c) { |
||||
return c.getComponentOrientation().isLeftToRight(); |
||||
} |
||||
|
||||
@Override |
||||
public void paintThumb(Graphics g) { |
||||
Color thumbColor = getThumbColor(); |
||||
Color color = stateColor(slider, thumbHover, thumbPressed, thumbColor, disabledThumbColor, null, hoverThumbColor, pressedThumbColor); |
||||
color = FlatUIUtils.deriveColor(color, thumbColor); |
||||
|
||||
Color foreground = slider.getForeground(); |
||||
Color borderColor = (thumbBorderColor != null && foreground == defaultForeground) ? stateColor(slider, false, false, thumbBorderColor, disabledThumbBorderColor, focusedThumbBorderColor, null, null) : null; |
||||
|
||||
Color focusedColor = FlatUIUtils.deriveColor(this.focusedColor, (foreground != defaultForeground) ? foreground : focusBaseColor); |
||||
|
||||
paintThumb(g, slider, thumbRect, isRoundThumb(), color, borderColor, focusedColor, thumbBorderWidth, focusWidth); |
||||
} |
||||
|
||||
/** |
||||
* Paints the thumb. |
||||
* |
||||
* @param g the graphics context |
||||
* @param slider the slider |
||||
* @param thumbRect the thumb rectangle |
||||
* @param roundThumb whether the thumb should be round |
||||
* @param thumbColor the thumb color |
||||
* @param thumbBorderColor the thumb border color |
||||
* @param focusedColor the focused color |
||||
* @param thumbBorderWidth the thumb border width |
||||
* @param focusWidth the focus width |
||||
*/ |
||||
public static void paintThumb(Graphics g, JSlider slider, Rectangle thumbRect, boolean roundThumb, Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int focusWidth) { |
||||
double systemScaleFactor = UIScale.getSystemScaleFactor((Graphics2D) g); |
||||
int scaleFactor2 = 2; |
||||
if (systemScaleFactor != 1 && systemScaleFactor != scaleFactor2) { |
||||
// paint at scale 1x to avoid clipping on right and bottom edges at 125%, 150% or 175%
|
||||
HiDPIUtils.paintAtScale1x((Graphics2D) g, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, (g2d, x2, y2, width2, height2, scaleFactor) -> { |
||||
paintThumbImpl(g, slider, x2, y2, width2, height2, roundThumb, thumbColor, thumbBorderColor, focusedColor, (float) (thumbBorderWidth * scaleFactor), (float) (focusWidth * scaleFactor)); |
||||
}); |
||||
return; |
||||
} |
||||
|
||||
paintThumbImpl(g, slider, thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, roundThumb, thumbColor, thumbBorderColor, focusedColor, thumbBorderWidth, focusWidth); |
||||
|
||||
} |
||||
|
||||
private static void paintThumbImpl(Graphics g, JSlider slider, int x, int y, int width, int height, boolean roundThumb, Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, float focusWidth) { |
||||
int fw = Math.round(UIScale.scale(focusWidth)); |
||||
int tx = x + fw; |
||||
int ty = y + fw; |
||||
int tw = width - fw - fw; |
||||
int th = height - fw - fw; |
||||
boolean focused = FlatUIUtils.isPermanentFocusOwner(slider); |
||||
|
||||
if (roundThumb) { |
||||
paintRoundThumb(g, x, y, width, height, thumbColor, thumbBorderColor, focusedColor, thumbBorderWidth, focused, tx, ty, tw, th); |
||||
} else { |
||||
paintDirectionalThumb(g, slider, x, y, width, height, thumbColor, thumbBorderColor, focusedColor, thumbBorderWidth, tw, th, focused, fw); |
||||
} |
||||
} |
||||
|
||||
private static void paintDirectionalThumb(Graphics g, JSlider slider, int x, int y, int width, int height, Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int tw, int th, boolean focused, int fw) { |
||||
Graphics2D g2 = (Graphics2D) g.create(); |
||||
try { |
||||
g2.translate(x, y); |
||||
if (slider.getOrientation() == JSlider.VERTICAL) { |
||||
if (slider.getComponentOrientation().isLeftToRight()) { |
||||
g2.translate(0, height); |
||||
g2.rotate(Math.toRadians(270)); |
||||
} else { |
||||
g2.translate(width, 0); |
||||
g2.rotate(Math.toRadians(90)); |
||||
} |
||||
|
||||
// rotate thumb width/height
|
||||
int temp = tw; |
||||
tw = th; |
||||
th = temp; |
||||
} |
||||
|
||||
paintDirectionalThumbImpl(thumbColor, thumbBorderColor, focusedColor, thumbBorderWidth, tw, th, focused, fw, g2); |
||||
} finally { |
||||
g2.dispose(); |
||||
} |
||||
} |
||||
|
||||
private static void paintDirectionalThumbImpl(Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, int tw, int th, boolean focused, int fw, Graphics2D g2) { |
||||
// paint thumb focus border
|
||||
if (focused) { |
||||
g2.setColor(focusedColor); |
||||
g2.fill(createDirectionalThumbShape(0, 0, tw + fw + fw, th + fw + fw, fw)); |
||||
} |
||||
|
||||
if (thumbBorderColor != null) { |
||||
// paint thumb border
|
||||
g2.setColor(thumbBorderColor); |
||||
g2.fill(createDirectionalThumbShape(fw, fw, tw, th, 0)); |
||||
|
||||
// paint thumb background
|
||||
float lw = UIScale.scale(thumbBorderWidth); |
||||
g2.setColor(thumbColor); |
||||
g2.fill(createDirectionalThumbShape(fw + lw, fw + lw, tw - lw - lw, th - lw - lw, 0)); |
||||
} else { |
||||
// paint thumb background
|
||||
g2.setColor(thumbColor); |
||||
g2.fill(createDirectionalThumbShape(fw, fw, tw, th, 0)); |
||||
} |
||||
} |
||||
|
||||
private static void paintRoundThumb(Graphics g, int x, int y, int width, int height, Color thumbColor, Color thumbBorderColor, Color focusedColor, float thumbBorderWidth, boolean focused, int tx, int ty, int tw, int th) { |
||||
// paint thumb focus border
|
||||
if (focused) { |
||||
g.setColor(focusedColor); |
||||
((Graphics2D) g).fill(createRoundThumbShape(x, y, width, height)); |
||||
} |
||||
|
||||
if (thumbBorderColor != null) { |
||||
// paint thumb border
|
||||
g.setColor(thumbBorderColor); |
||||
((Graphics2D) g).fill(createRoundThumbShape(tx, ty, tw, th)); |
||||
|
||||
// paint thumb background
|
||||
float lw = UIScale.scale(thumbBorderWidth); |
||||
g.setColor(thumbColor); |
||||
((Graphics2D) g).fill(createRoundThumbShape(tx + lw, ty + lw, tw - lw - lw, th - lw - lw)); |
||||
} else { |
||||
// paint thumb background
|
||||
g.setColor(thumbColor); |
||||
((Graphics2D) g).fill(createRoundThumbShape(tx, ty, tw, th)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 无标签下创建圆形Thumb形状 |
||||
*/ |
||||
public static Shape createRoundThumbShape(float x, float y, float w, float h) { |
||||
if (AssistUtils.equals(w, h)) { |
||||
return new Ellipse2D.Float(x, y, w, h); |
||||
} else { |
||||
float arc = Math.min(w, h); |
||||
return new RoundRectangle2D.Float(x, y, w, h, arc, arc); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 有标签下创建Thumb形状 |
||||
*/ |
||||
public static Path2D createDirectionalThumbShape(float x, float y, float w, float h, float arc) { |
||||
|
||||
float wh = w / 2; |
||||
Path2D path = new Path2D.Float(Path2D.WIND_NON_ZERO, 9); |
||||
path.moveTo(x + wh, y); // 移到反转后的位置
|
||||
path.lineTo(x, y + wh); // 线到反转后的位置
|
||||
path.lineTo(x, y + h - arc); // 线到反转后的位置
|
||||
path.quadTo(x, y + h, x + arc, y + h); // 贝塞尔曲线到反转后的位置
|
||||
path.lineTo(x + (w - arc), y + h); // 线到反转后的位置
|
||||
path.quadTo(x + w, y + h, x + w, y + h - arc); // 贝塞尔曲线到反转后的位置
|
||||
path.lineTo(x + w, y + wh); // 线到反转后的位置
|
||||
path.closePath(); // 关闭路径
|
||||
|
||||
return path; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,108 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIStyle; |
||||
import com.formdev.flatlaf.ui.FlatTableHeaderUI; |
||||
import com.fr.stable.StringUtils; |
||||
import sun.swing.DefaultLookup; |
||||
|
||||
import javax.swing.BorderFactory; |
||||
import javax.swing.JComponent; |
||||
import javax.swing.JTable; |
||||
import javax.swing.SwingConstants; |
||||
import javax.swing.UIManager; |
||||
import javax.swing.border.Border; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import javax.swing.plaf.UIResource; |
||||
import javax.swing.table.DefaultTableCellRenderer; |
||||
import javax.swing.table.JTableHeader; |
||||
import java.awt.Color; |
||||
import java.awt.Component; |
||||
import java.awt.Graphics; |
||||
|
||||
/** |
||||
* 应用于 JTable 的UI |
||||
* |
||||
* @author lemon |
||||
* @since |
||||
* Created on |
||||
*/ |
||||
public class FineTableHeaderUI extends FlatTableHeaderUI { |
||||
protected static Color selectionBackground = UIManager.getColor("Table.background"); |
||||
|
||||
@Override |
||||
public void installUI(JComponent c) { |
||||
super.installUI(c); |
||||
JTableHeader header = (JTableHeader) c; |
||||
header.setDefaultRenderer(new TableHeaderRenderer()); |
||||
JTable table = header.getTable(); |
||||
|
||||
if (table != null) { |
||||
table.setDefaultRenderer(Object.class, new TableRenderer()); |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void paint(Graphics g, JComponent c) { |
||||
FineUIStyle.setStyle(((JTableHeader) c).getTable(), FineUIStyle.DEFAULT_TABLE); |
||||
super.paint(g, c); |
||||
} |
||||
|
||||
/** |
||||
* 表头 render |
||||
*/ |
||||
public static class TableHeaderRenderer extends DefaultTableCellRenderer implements UIResource { |
||||
public TableHeaderRenderer() { |
||||
} |
||||
|
||||
@Override |
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { |
||||
Border border = DefaultLookup.getBorder(this, this.ui, "TableHeader.cellBorder"); |
||||
this.setText(value == null ? "" : value.toString()); |
||||
this.setHorizontalAlignment(SwingConstants.LEFT); |
||||
this.setBorder(border); |
||||
return this; |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* UI |
||||
* @param c |
||||
* @return |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineTableHeaderUI(); |
||||
} |
||||
|
||||
/** |
||||
* 表身 render |
||||
*/ |
||||
public static class TableRenderer extends DefaultTableCellRenderer { |
||||
public TableRenderer() { |
||||
setHorizontalAlignment(SwingConstants.LEFT); |
||||
} |
||||
|
||||
@Override |
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { |
||||
Class<?> columnClass = table.getColumnClass(0); |
||||
Border border; |
||||
if (column == table.getColumnCount() - 1) { |
||||
border = BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, UIManager.getColor("defaultBorderColor")), |
||||
UIManager.getBorder("Table.cellNoFocusBorder")); |
||||
} else { |
||||
border = BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(0, 0, 1, 1, UIManager.getColor("defaultBorderColor")), |
||||
UIManager.getBorder("Table.cellNoFocusBorder")); |
||||
} |
||||
if (isSelected && columnClass != Boolean.class) { |
||||
selectionBackground = UIManager.getColor( "Table.selectionBackground"); |
||||
} else { |
||||
selectionBackground = UIManager.getColor("Table.background"); |
||||
} |
||||
setBackground(selectionBackground); |
||||
setText(value == null ? StringUtils.BLANK : String.valueOf(value)); |
||||
setBorder(border); |
||||
return this; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,53 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.formdev.flatlaf.ui.FlatScrollBarUI; |
||||
import com.fr.design.gui.iscrollbar.UIVerticalScrollBar; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.JTable; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Graphics; |
||||
import java.awt.Rectangle; |
||||
|
||||
/** |
||||
* 应用于 JTable 的垂直滚动条UI |
||||
* |
||||
* @author lemon |
||||
* @since |
||||
* Created on 2024/08/11 |
||||
*/ |
||||
public class FineTableScrollBarPaneUI extends FlatScrollBarUI { |
||||
|
||||
public FineTableScrollBarPaneUI() { |
||||
} |
||||
|
||||
/** |
||||
* 创建UI |
||||
* |
||||
* @param c 组件 |
||||
* @return ComponentUI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineTableScrollBarPaneUI(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) { |
||||
super.paintTrack(g, c, trackBounds); |
||||
} |
||||
|
||||
@Override |
||||
protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) { |
||||
if (c instanceof UIVerticalScrollBar) { |
||||
// 确保滚动条滑块不会超过表头区域
|
||||
JTable table = ((UIVerticalScrollBar) c).getTable(); |
||||
Rectangle headerRect = table.getTableHeader().getBounds(); |
||||
int headerHeight = headerRect.height; |
||||
thumbBounds.y = Math.max(thumbBounds.y, headerHeight); |
||||
} |
||||
|
||||
super.paintThumb(g, c, thumbBounds); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,52 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import javax.swing.plaf.PanelUI; |
||||
import java.awt.Color; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Insets; |
||||
import java.awt.geom.RoundRectangle2D; |
||||
|
||||
/** |
||||
* 模版列表菜单ui |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2024/1/11 |
||||
*/ |
||||
public class FineTemplateListMenuItemUI extends PanelUI { |
||||
/** |
||||
* 创建UI |
||||
* |
||||
* @param c 组件 |
||||
* @return ComponentUI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineTemplateListMenuItemUI(); |
||||
} |
||||
|
||||
@Override |
||||
public void update(Graphics g, JComponent c) { |
||||
Color color = g.getColor(); |
||||
g.setColor(c.getBackground()); |
||||
Insets insets = c.getInsets(); |
||||
Object[] old = FlatUIUtils.setRenderingHints(g); |
||||
((Graphics2D) g).fill(new RoundRectangle2D.Float( |
||||
insets.left, insets.top, |
||||
(float) c.getWidth() - insets.left - insets.right, |
||||
(float) c.getHeight() - insets.top - insets.bottom, |
||||
3, 3)); |
||||
g.setColor(color); |
||||
FlatUIUtils.resetRenderingHints(g, old); |
||||
super.paint(g, c); |
||||
} |
||||
|
||||
@Override |
||||
public void uninstallUI(JComponent c) { |
||||
super.uninstallUI(c); |
||||
} |
||||
} |
@ -0,0 +1,396 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.icon.LazyIcon; |
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
import com.fr.base.GraphHelper; |
||||
import com.fr.base.vcs.DesignerMode; |
||||
import com.fr.design.file.MultiTemplateTabPane; |
||||
import com.fr.stable.collections.combination.Pair; |
||||
|
||||
import javax.swing.Icon; |
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import javax.swing.plaf.PanelUI; |
||||
import java.awt.Color; |
||||
import java.awt.Dimension; |
||||
import java.awt.FontMetrics; |
||||
import java.awt.GradientPaint; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Insets; |
||||
import java.awt.geom.Path2D; |
||||
import java.awt.geom.Point2D; |
||||
import java.awt.geom.Rectangle2D; |
||||
|
||||
import static com.fine.theme.utils.FineUIScale.scale; |
||||
import static com.fine.theme.utils.FineUIUtils.paintRoundTabBorder; |
||||
import static com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; |
||||
import static com.fr.design.file.MultiTemplateTabPane.leadingWidth; |
||||
import static com.fr.design.file.MultiTemplateTabPane.TRAILING_WIDTH; |
||||
|
||||
/** |
||||
* 文件Tab栏UI |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/12/27 |
||||
*/ |
||||
public class FineTemplateTabPaneUI extends PanelUI { |
||||
private MultiTemplateTabPane tabPane; |
||||
|
||||
private static final String ELLIPSIS = "..."; |
||||
private static final int ICON_TEXT_GAP = 4; |
||||
|
||||
@Styleable(dot = true) |
||||
protected Color background; |
||||
|
||||
@Styleable(dot = true) |
||||
protected Color selectedBackground; |
||||
|
||||
@Styleable(dot = true) |
||||
protected Color hoverColor; |
||||
|
||||
@Styleable(dot = true) |
||||
protected Color closeIconHoverBackground; |
||||
|
||||
@Styleable(dot = true) |
||||
protected Color closeHoverBackground; |
||||
|
||||
@Styleable(dot = true) |
||||
protected Color borderColor; |
||||
|
||||
@Styleable(dot = true) |
||||
protected int tabHeight; |
||||
|
||||
@Styleable(dot = true) |
||||
protected int separatorHeight; |
||||
|
||||
@Styleable(dot = true) |
||||
protected int borderWidth; |
||||
|
||||
@Styleable(dot = true) |
||||
protected int tabArc; |
||||
|
||||
@Styleable(dot = true) |
||||
protected Insets tabInsets; |
||||
|
||||
protected Icon fileIcon; |
||||
protected Icon moreAction; |
||||
protected Icon addAction; |
||||
protected Icon moreHoverAction; |
||||
|
||||
|
||||
private Icon closeIcon; |
||||
|
||||
private Icon closeHoverIcon; |
||||
|
||||
private int trailingWidth; |
||||
|
||||
protected FineTemplateTabPaneUI() { |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 创建UI |
||||
* |
||||
* @param c 组件 |
||||
* @return ComponentUI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new FineTemplateTabPaneUI(); |
||||
} |
||||
|
||||
@Override |
||||
public void installUI(JComponent c) { |
||||
super.installUI(c); |
||||
this.tabPane = (MultiTemplateTabPane) c; |
||||
closeIcon = new LazyIcon("clear"); |
||||
closeHoverIcon = new LazyIcon("clear_hover"); |
||||
addAction = new LazyIcon("add_worksheet"); |
||||
moreAction = new LazyIcon("tool_more"); |
||||
moreHoverAction = new LazyIcon("tool_more_hover"); |
||||
fileIcon = new LazyIcon("cpt_icon"); |
||||
trailingWidth = scale(TRAILING_WIDTH); |
||||
|
||||
borderWidth = FineUIUtils.getUIInt("TemplateTabPane.borderWidth", "TemplateTabPane.borderWidth"); |
||||
tabArc = FineUIUtils.getUIInt("TemplateTabPane.tabArc", "TemplateTabPane.tabArc"); |
||||
background = FineUIUtils.getUIColor("TemplateTabPane.background", "TabbedPane.background"); |
||||
selectedBackground = FineUIUtils.getUIColor("TemplateTabPane.selectedBackground", "TemplateTabPane.selectedBackground"); |
||||
closeHoverBackground = FineUIUtils.getUIColor("TemplateTabPane.closeHoverBackground", "TemplateTabPane.closeHoverBackground"); |
||||
borderColor = FineUIUtils.getUIColor("TemplateTabPane.borderColor", "TabbedPane.tabSeparatorColor"); |
||||
hoverColor = FineUIUtils.getUIColor("TemplateTabPane.hoverColor", "TemplateTabPane.hoverColor"); |
||||
closeIconHoverBackground = FineUIUtils.getUIColor("TemplateTabPane.icon.hoverBackground ", "TemplateTabPane.icon.hoverBackground "); |
||||
// ---- scaled ----
|
||||
tabInsets = FineUIUtils.getAndScaleUIInsets("TemplateTabPane.tabInsets", new Insets(4, 6, 4, 6)); |
||||
tabHeight = FineUIUtils.getAndScaleInt("TemplateTabPane.tabHeight", "TabbedPane.tabHeight"); |
||||
separatorHeight = FineUIUtils.getAndScaleInt("TemplateTabPane.separatorHeight", "TemplateTabPane.separatorHeight"); |
||||
} |
||||
|
||||
@Override |
||||
public void uninstallUI(JComponent c) { |
||||
super.uninstallUI(c); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void update(Graphics g, JComponent c) { |
||||
super.update(g, c); |
||||
double maxWidth = c.getWidth() - scale(leadingWidth) - trailingWidth; |
||||
Graphics2D g2d = (Graphics2D) g; |
||||
paintDefaultBackground(g2d); |
||||
paintPaneUnderLine(c.getWidth(), g2d); |
||||
paintTabs(g2d, maxWidth); |
||||
} |
||||
|
||||
private void paintDefaultBackground(Graphics2D g2d) { |
||||
//画默认背景
|
||||
g2d.setPaint(new GradientPaint(0, 0, tabPane.getBackground(), 1, (float) (tabHeight), tabPane.getBackground())); |
||||
g2d.fillRect(0, 0, tabPane.getWidth(), tabHeight); |
||||
} |
||||
|
||||
private void paintPaneUnderLine(float w, Graphics2D g2d) { |
||||
g2d.setPaint(borderColor); |
||||
float h = (float) tabHeight; |
||||
int t = scale(borderWidth); |
||||
Path2D border = new Path2D.Float(Path2D.WIND_EVEN_ODD); |
||||
border.append(FlatUIUtils.createComponentRectangle(0, 0, w, h, 0), false); |
||||
border.append(FlatUIUtils.createComponentRectangle(0, 0, w, h - t, 0), false); |
||||
g2d.fill(border); |
||||
} |
||||
|
||||
private void paintTabs(Graphics2D g2d, double maxWidth) { |
||||
|
||||
int maxStringlength = calculateStringMaxLength(); |
||||
if (tabPane.getSelectedIndex() >= tabPane.getTabCount()) { |
||||
tabPane.setSelectedIndex(tabPane.getTabCount() - 1); |
||||
} |
||||
if (tabPane.getSelectedIndex() < 0) { |
||||
tabPane.setSelectedIndex(0); |
||||
} |
||||
double templateStartX = scale(leadingWidth); |
||||
|
||||
|
||||
//从可以开始展示在tab面板上的tab开始画
|
||||
Pair<Integer, Integer> viewRange = tabPane.getViewRange(); |
||||
for (int i = viewRange.getFirst(); i <= viewRange.getSecond(); i++) { |
||||
Icon icon = tabPane.getTemplateIconByIndex(i); |
||||
String name = tabPane.getTemplateShowNameByIndex(i); |
||||
//如果tab名字的长度大于最大能显示的英文字符长度,则进行省略号处理
|
||||
if (getStringWidth(name) > maxStringlength) { |
||||
name = getEllipsisName(name, maxStringlength); |
||||
} |
||||
|
||||
|
||||
Icon tabcloseIcon = tabPane.isCloseCurrent(i) ? closeHoverIcon : closeIcon; |
||||
if (i == tabPane.getSelectedIndex()) { |
||||
paintSelectedTab(g2d, icon, templateStartX, name, tabcloseIcon); |
||||
} else { |
||||
paintUnSelectedTab(g2d, icon, templateStartX, name, tabcloseIcon, |
||||
tabPane.getHoverIndex(), i); |
||||
} |
||||
templateStartX += tabPane.getTabWidth(); |
||||
} |
||||
|
||||
paintSeparators(g2d); |
||||
|
||||
if (!DesignerMode.isVcsMode()) { |
||||
paintTrailingAction(g2d, maxWidth); |
||||
} |
||||
} |
||||
|
||||
private void paintSeparators(Graphics2D g2d) { |
||||
g2d.setPaint(borderColor); |
||||
float x = scale(leadingWidth); |
||||
Pair<Integer, Integer> viewRange = tabPane.getViewRange(); |
||||
for (int i = viewRange.getFirst(); i <= viewRange.getSecond(); i++) { |
||||
if (i != tabPane.getSelectedIndex() |
||||
&& i + 1 != tabPane.getSelectedIndex()) { |
||||
paintSeparator(g2d, x); |
||||
} |
||||
x += tabPane.getTabWidth(); |
||||
} |
||||
} |
||||
|
||||
|
||||
private void paintTrailingAction(Graphics2D g2d, double tabPaneWidth) { |
||||
int x = scale(leadingWidth) + (int) tabPaneWidth + (trailingWidth - moreAction.getIconWidth()) / 2; |
||||
int y = (tabHeight - moreAction.getIconHeight()) / 2; |
||||
if (tabPane.isHoverMoreAction()) { |
||||
moreHoverAction.paintIcon(tabPane, g2d, x, y); |
||||
} else { |
||||
moreAction.paintIcon(tabPane, g2d, x, y); |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 判断tab文字的长度大于能装下的最大文字长度,要用省略号 |
||||
* |
||||
* @param name |
||||
* @param maxStringlength |
||||
* @return |
||||
*/ |
||||
private String getEllipsisName(String name, int maxStringlength) { |
||||
|
||||
int ellipsisWidth = getStringWidth(ELLIPSIS); |
||||
int leftkeyPoint = 0; |
||||
int rightKeyPoint = name.length() - 1; |
||||
int leftStrWidth = 0; |
||||
int rightStrWidth = 0; |
||||
while (leftStrWidth + rightStrWidth + ellipsisWidth < maxStringlength) { |
||||
if (leftStrWidth <= rightStrWidth) { |
||||
leftkeyPoint++; |
||||
} else { |
||||
rightKeyPoint--; |
||||
} |
||||
leftStrWidth = getStringWidth(name.substring(0, leftkeyPoint)); |
||||
rightStrWidth = getStringWidth(name.substring(rightKeyPoint)); |
||||
|
||||
if (leftStrWidth + rightStrWidth + ellipsisWidth > maxStringlength) { |
||||
if (leftStrWidth <= rightStrWidth) { |
||||
rightKeyPoint++; |
||||
} else { |
||||
leftkeyPoint--; |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
return name.substring(0, leftkeyPoint) + ELLIPSIS + name.substring(rightKeyPoint); |
||||
} |
||||
|
||||
/** |
||||
* 计算过长度之后的每个tab的能接受的文字的英文字符数 |
||||
* |
||||
* @return |
||||
*/ |
||||
private int calculateStringMaxLength() { |
||||
return tabPane.getTabWidth() |
||||
- tabInsets.left - tabInsets.right |
||||
- ICON_TEXT_GAP * 2 |
||||
- fileIcon.getIconWidth() - closeIcon.getIconWidth(); |
||||
|
||||
} |
||||
|
||||
private int getStringWidth(String str) { |
||||
FontMetrics fm = GraphHelper.getFontMetrics(tabPane.getFont()); |
||||
int size = 0; |
||||
for (int i = 0; i < str.length(); i++) { |
||||
size += fm.charWidth(str.codePointAt(i)); |
||||
} |
||||
return size; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 画选中的tab |
||||
* |
||||
* @param g2d |
||||
* @param sheeticon |
||||
* @param templateStartX |
||||
* @param sheetName |
||||
* @param closeIcon |
||||
* @return |
||||
*/ |
||||
private void paintSelectedTab(Graphics2D g2d, Icon sheeticon, double templateStartX, String sheetName, Icon closeIcon) { |
||||
Object[] oriRenderingHints = FlatUIUtils.setRenderingHints(g2d); |
||||
// 绘制选中背景
|
||||
g2d.setPaint(selectedBackground); |
||||
Path2D tabShape = FineUIUtils.createTopRoundRectangle(templateStartX, 0, |
||||
tabPane.getTabWidth(), tabHeight, tabArc); |
||||
g2d.fill(tabShape); |
||||
// 绘制选中边框
|
||||
g2d.setPaint(borderColor); |
||||
paintRoundTabBorder(g2d, templateStartX, 0, |
||||
tabPane.getTabWidth(), tabHeight, borderWidth, (float) tabArc); |
||||
|
||||
// 绘制字符
|
||||
g2d.setPaint(tabPane.getForeground()); |
||||
Point2D.Double textPoint = calTextPoint(templateStartX, sheeticon.getIconWidth()); |
||||
g2d.drawString(sheetName, (int) textPoint.x, (int) textPoint.y); |
||||
|
||||
FlatUIUtils.resetRenderingHints(g2d, oriRenderingHints); |
||||
|
||||
// 绘制图标
|
||||
int sheetIconY = (tabHeight - sheeticon.getIconHeight()) / 2; |
||||
sheeticon.paintIcon(tabPane, g2d, (int) templateStartX + tabInsets.left, sheetIconY); |
||||
|
||||
int closePosition = (int) templateStartX + tabPane.getTabWidth() |
||||
- this.closeIcon.getIconWidth() - tabInsets.right; |
||||
int closeY = (tabHeight - closeIcon.getIconHeight()) / 2; |
||||
if (!DesignerMode.isVcsMode()) { |
||||
closeIcon.paintIcon(tabPane, g2d, closePosition, closeY); |
||||
} |
||||
} |
||||
|
||||
private Point2D.Double calTextPoint(double x, int iconWidth) { |
||||
FontMetrics fm = tabPane.getFontMetrics(tabPane.getFont()); |
||||
int ascent = fm.getAscent(); |
||||
int gap = (tabHeight - tabInsets.top - tabInsets.bottom - ascent) / 2; |
||||
double y = tabInsets.top + ascent + gap; |
||||
return new Point2D.Double(x + iconWidth + tabInsets.left + ICON_TEXT_GAP, y); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 画没有选中的tab |
||||
* |
||||
* @param g2d |
||||
* @param sheeticon |
||||
* @param templateStartX |
||||
* @param sheetName |
||||
* @param closeIcon |
||||
*/ |
||||
private void paintUnSelectedTab(Graphics2D g2d, Icon sheeticon, double templateStartX, String sheetName, Icon closeIcon, int mouseOveredIndex, int selfIndex) { |
||||
if (selfIndex == mouseOveredIndex) { |
||||
g2d.setPaint(hoverColor); |
||||
} else { |
||||
g2d.setPaint(background); |
||||
} |
||||
|
||||
Object[] oriRenderingHints = FlatUIUtils.setRenderingHints(g2d); |
||||
|
||||
Path2D tabShape = FineUIUtils.createTopRoundRectangle(templateStartX, 0, |
||||
tabPane.getTabWidth(), tabHeight - scale(borderWidth), tabArc); |
||||
g2d.fill(tabShape); |
||||
|
||||
// 画字符
|
||||
g2d.setPaint(tabPane.getForeground()); |
||||
Point2D.Double textPoint = calTextPoint(templateStartX, sheeticon.getIconWidth()); |
||||
g2d.drawString(sheetName, (int) textPoint.x, (int) textPoint.y); |
||||
|
||||
FlatUIUtils.resetRenderingHints(g2d, oriRenderingHints); |
||||
|
||||
// 画图标
|
||||
int sheetIconY = (tabHeight - sheeticon.getIconHeight()) / 2; |
||||
sheeticon.paintIcon(tabPane, g2d, (int) templateStartX + tabInsets.left, sheetIconY); |
||||
int closeY = (tabHeight - closeIcon.getIconHeight()) / 2; |
||||
int closePosition = (int) templateStartX + tabPane.getTabWidth() |
||||
- this.closeIcon.getIconWidth() - tabInsets.right; |
||||
if (!DesignerMode.isVcsMode()) { |
||||
closeIcon.paintIcon(tabPane, g2d, closePosition, closeY); |
||||
} |
||||
} |
||||
|
||||
private void paintSeparator(Graphics2D g2d, float templateStartX) { |
||||
float x = templateStartX + tabPane.getTabWidth(); |
||||
float gap = (tabHeight - separatorHeight) / 2.0f; |
||||
g2d.fill(new Rectangle2D.Float(x, gap, scale(borderWidth), tabHeight - gap * 2)); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Dimension getPreferredSize(JComponent c) { |
||||
return new Dimension(c.getWidth(), tabHeight); |
||||
} |
||||
|
||||
@Override |
||||
public Dimension getMinimumSize(JComponent c) { |
||||
return new Dimension(0, tabHeight); |
||||
} |
||||
|
||||
@Override |
||||
public Dimension getMaximumSize(JComponent c) { |
||||
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); |
||||
} |
||||
} |
@ -0,0 +1,218 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; |
||||
import com.formdev.flatlaf.ui.FlatToggleButtonUI; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
import com.fr.design.gui.ibutton.UIButton; |
||||
import org.jetbrains.annotations.NotNull; |
||||
import org.jetbrains.annotations.Nullable; |
||||
|
||||
import javax.swing.AbstractButton; |
||||
import javax.swing.JComponent; |
||||
import javax.swing.JToggleButton; |
||||
import javax.swing.UIManager; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Color; |
||||
import java.awt.Component; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Shape; |
||||
import java.awt.Rectangle; |
||||
|
||||
import static com.fine.theme.utils.FineClientProperties.BUTTON_TYPE_GROUP; |
||||
import static com.fine.theme.utils.FineClientProperties.BUTTON_TYPE; |
||||
import static com.fine.theme.utils.FineClientProperties.BUTTON_TYPE_TAB; |
||||
import static com.fine.theme.utils.FineClientProperties.BUTTON_GROUP_POSITION; |
||||
import static com.fine.theme.utils.FineClientProperties.GROUP_BUTTON_POSITION_INNER; |
||||
import static com.fine.theme.utils.FineClientProperties.GROUP_BUTTON_POSITION_LEFT; |
||||
import static com.fine.theme.utils.FineClientProperties.GROUP_BUTTON_POSITION_LEFT_BOTTOM; |
||||
import static com.fine.theme.utils.FineClientProperties.GROUP_BUTTON_POSITION_LEFT_TOP; |
||||
import static com.fine.theme.utils.FineClientProperties.GROUP_BUTTON_POSITION_RIGHT; |
||||
import static com.fine.theme.utils.FineClientProperties.GROUP_BUTTON_POSITION_RIGHT_BOTTOM; |
||||
import static com.fine.theme.utils.FineClientProperties.GROUP_BUTTON_POSITION_RIGHT_TOP; |
||||
import static com.fine.theme.utils.FineClientProperties.TAB_BUTTON_SELECTED_BACKGROUND; |
||||
import static com.formdev.flatlaf.FlatClientProperties.clientPropertyColor; |
||||
import static com.formdev.flatlaf.FlatClientProperties.clientPropertyInt; |
||||
|
||||
/** |
||||
* 提供 {@link javax.swing.JToggleButton} 的UI类 |
||||
* <p> |
||||
* |
||||
* @author vito |
||||
* @uiDefault ToggleButton.tab.arc int |
||||
* @since 11.0 |
||||
* Created on 2023/11/3 |
||||
*/ |
||||
public class FineToggleButtonUI extends FlatToggleButtonUI { |
||||
|
||||
@Styleable(dot = true) |
||||
protected int tabArc; |
||||
|
||||
@Styleable(dot = true) |
||||
protected Color groupBackground; |
||||
|
||||
@Styleable(dot = true) |
||||
protected Color groupSelectedBackground; |
||||
|
||||
@Styleable(dot = true) |
||||
protected Color groupSelectedForeground; |
||||
|
||||
public static ComponentUI createUI(JComponent c) { |
||||
return FlatUIUtils.canUseSharedUI(c) |
||||
? FlatUIUtils.createSharedUI(FlatToggleButtonUI.class, () -> new FineToggleButtonUI(true)) |
||||
: new FineToggleButtonUI(false); |
||||
} |
||||
|
||||
protected FineToggleButtonUI(boolean shared) { |
||||
super(shared); |
||||
} |
||||
|
||||
@Override |
||||
protected void installDefaults(AbstractButton b) { |
||||
super.installDefaults(b); |
||||
tabArc = UIManager.getInt("ToggleButton.tab.arc"); |
||||
groupBackground = FineUIUtils.getUIColor("ToggleButton.group.background", "ToggleButton.background"); |
||||
groupSelectedBackground = FineUIUtils.getUIColor("ToggleButton.group.selectedBackground", "ToggleButton.selectedBackground"); |
||||
groupSelectedForeground = FineUIUtils.getUIColor("ToggleButton.group.selectedForeground", "ToggleButton.selectedForeground"); |
||||
} |
||||
|
||||
|
||||
@Nullable |
||||
static String getButtonTypeStr(AbstractButton c) { |
||||
Object value = c.getClientProperty(BUTTON_TYPE); |
||||
if (value instanceof String) |
||||
return (String) value; |
||||
return null; |
||||
} |
||||
|
||||
static int getGroupButtonPosition(AbstractButton c) { |
||||
return clientPropertyInt(c, BUTTON_GROUP_POSITION, GROUP_BUTTON_POSITION_INNER); |
||||
} |
||||
|
||||
static boolean isTabButton(Component c) { |
||||
return c instanceof JToggleButton && BUTTON_TYPE_TAB.equals(getButtonTypeStr((JToggleButton) c)); |
||||
} |
||||
|
||||
static boolean isGroupButton(Component c) { |
||||
return c instanceof UIButton && BUTTON_TYPE_GROUP.equals(getButtonTypeStr((UIButton) c)); |
||||
} |
||||
|
||||
@Override |
||||
public void paint(Graphics g, JComponent c) { |
||||
if (isGroupButton(c) || isTabButton(c)) { |
||||
((AbstractButton)c).setMargin(FineUIUtils.getUIInsets("ToggleButton.compact.margin", "ToggleButton.margin")); |
||||
} |
||||
super.paint(g, c); |
||||
} |
||||
|
||||
@Override |
||||
protected void paintBackground(Graphics g, JComponent c) { |
||||
if (isTabButton(c)) { |
||||
paintTabButton(g, c); |
||||
} else if (isGroupButton(c)) { |
||||
paintGroupButton(g, c); |
||||
} else { |
||||
super.paintBackground(g, c); |
||||
} |
||||
} |
||||
|
||||
protected void paintTabButton(Graphics g, JComponent c) { |
||||
int height = c.getHeight(); |
||||
int width = c.getWidth(); |
||||
boolean selected = ((AbstractButton) c).isSelected(); |
||||
|
||||
// paint background
|
||||
Color background; |
||||
if(c.isEnabled() && selected) { |
||||
background = tabSelectedBackground; |
||||
} else { |
||||
Color enabledColor = selected ? clientPropertyColor(c, TAB_BUTTON_SELECTED_BACKGROUND, tabSelectedBackground) : null; |
||||
// use component background if explicitly set
|
||||
if (enabledColor == null) { |
||||
Color bg = c.getBackground(); |
||||
if (isCustomBackground(bg)) { |
||||
enabledColor = bg; |
||||
} |
||||
} |
||||
background = buttonStateColor(c, enabledColor, |
||||
null, tabFocusBackground, tabHoverBackground, null); |
||||
} |
||||
|
||||
if (background != null) { |
||||
g.setColor(background); |
||||
g.fillRoundRect(0, 0, width, height, tabArc, tabArc); |
||||
} |
||||
} |
||||
|
||||
protected void paintGroupButton(Graphics g, JComponent c) { |
||||
Color background = getBackground(c); |
||||
if (background == null) { |
||||
return; |
||||
} |
||||
Graphics2D g2 = (Graphics2D) g.create(); |
||||
try { |
||||
FlatUIUtils.setRenderingHints(g2); |
||||
g2.setColor(FlatUIUtils.deriveColor(background, getBackgroundBase(c, true))); |
||||
|
||||
int position = getGroupButtonPosition((AbstractButton) c); |
||||
if (position == GROUP_BUTTON_POSITION_INNER) { |
||||
float focusWidth = FlatUIUtils.getBorderFocusWidth(c); |
||||
FlatUIUtils.paintComponentBackground(g2, 0, 0, c.getWidth(), c.getHeight(), focusWidth, 0); |
||||
} else { |
||||
float arc = FlatUIUtils.getBorderArc( c ) / 2; |
||||
Shape path2D = getGroupButtonPath2D(c, position, arc); |
||||
g2.fill(path2D); |
||||
} |
||||
} finally { |
||||
g2.dispose(); |
||||
} |
||||
} |
||||
|
||||
@NotNull |
||||
private static Shape getGroupButtonPath2D(JComponent c, int position, float arc) { |
||||
Shape path2D; |
||||
switch (position) { |
||||
case GROUP_BUTTON_POSITION_LEFT: |
||||
path2D = FineUIUtils.createLeftRoundRectangle(0, 0, c.getWidth(), c.getHeight(), arc); |
||||
break; |
||||
case GROUP_BUTTON_POSITION_RIGHT: |
||||
path2D = FineUIUtils.createRightRoundRectangle(0, 0, c.getWidth(), c.getHeight(), arc); |
||||
break; |
||||
case GROUP_BUTTON_POSITION_LEFT_TOP: |
||||
path2D = FineUIUtils.createTopLeftRoundRectangle(0, 0, c.getWidth(), c.getHeight(), arc); |
||||
break; |
||||
case GROUP_BUTTON_POSITION_LEFT_BOTTOM: |
||||
path2D = FineUIUtils.createBottomLeftRoundRectangle(0, 0, c.getWidth(), c.getHeight(), arc); |
||||
break; |
||||
case GROUP_BUTTON_POSITION_RIGHT_TOP: |
||||
path2D = FineUIUtils.createTopRightRoundRectangle(0, 0, c.getWidth(), c.getHeight(), arc); |
||||
break; |
||||
case GROUP_BUTTON_POSITION_RIGHT_BOTTOM: |
||||
path2D = FineUIUtils.createBottomRightRoundRectangle(0, 0, c.getWidth(), c.getHeight(), arc); |
||||
break; |
||||
default: |
||||
path2D = new Rectangle(); |
||||
} |
||||
return path2D; |
||||
} |
||||
|
||||
@Override |
||||
protected Color getForeground(JComponent c) { |
||||
if (isGroupButton(c) && ((AbstractButton)c).isSelected()) { |
||||
return groupSelectedForeground; |
||||
} |
||||
return super.getForeground(c); |
||||
} |
||||
|
||||
@Override |
||||
protected Color getBackground(JComponent c) { |
||||
if (isGroupButton(c)) { |
||||
return ((AbstractButton)c).isSelected() ? groupSelectedBackground : groupBackground; |
||||
} |
||||
return super.getBackground(c); |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,102 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIScale; |
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatToolTipUI; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
import com.fr.base.BaseUtils; |
||||
import com.fr.stable.Constants; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.JToolTip; |
||||
import javax.swing.SwingUtilities; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Dimension; |
||||
import java.awt.FontMetrics; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Insets; |
||||
import java.awt.RenderingHints; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* ToolTip UI类 |
||||
* |
||||
* @author Leo.Qin |
||||
* @since 11.0 |
||||
* Created on 2023/12/8 |
||||
*/ |
||||
public class FineTooltipUI extends FlatToolTipUI { |
||||
private final int defaultMaxWidth = 392; |
||||
private final int defaultArc = 5; |
||||
private int maxWidth; |
||||
private int arc; |
||||
private List<String> lines; |
||||
|
||||
/** |
||||
* 创建UI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return FlatUIUtils.createSharedUI(FineTooltipUI.class, FineTooltipUI::new); |
||||
} |
||||
|
||||
@Override |
||||
public void paint(Graphics g, JComponent c) { |
||||
Graphics2D g2d = (Graphics2D) g; |
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
||||
|
||||
g2d.setColor(c.getBackground()); |
||||
g2d.fillRoundRect(0, 0, c.getWidth(), c.getHeight(), arc, arc); |
||||
|
||||
String text = ((JToolTip) c).getTipText(); |
||||
if (text == null || text.isEmpty()) { |
||||
return; |
||||
} |
||||
|
||||
Insets insets = c.getInsets(); |
||||
FontMetrics fm = g2d.getFontMetrics(); |
||||
|
||||
int x = insets.left; |
||||
int y = insets.top + fm.getAscent(); |
||||
|
||||
g2d.setColor(c.getForeground()); |
||||
|
||||
for (String line : lines) { |
||||
g2d.drawString(line, x, y); |
||||
y += fm.getHeight(); |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected void installDefaults(JComponent c) { |
||||
super.installDefaults(c); |
||||
c.setOpaque(false); |
||||
arc = FineUIScale.scale(FineUIUtils.getAndScaleInt("ToolTip.arc", defaultArc)); |
||||
maxWidth = FineUIUtils.getAndScaleInt("ToolTip.maxWidth", defaultMaxWidth); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Dimension getPreferredSize(JComponent c) { |
||||
String text = ((JToolTip) c).getTipText(); |
||||
if (text == null || text.isEmpty()) { |
||||
return new Dimension(); |
||||
} |
||||
|
||||
Insets insets = c.getInsets(); |
||||
int fontWidth = this.maxWidth - insets.left - insets.right; |
||||
lines = BaseUtils.getLineTextList(text, null, null, fontWidth, Constants.FR_PAINT_RESOLUTION); |
||||
|
||||
FontMetrics fm = c.getFontMetrics(c.getFont()); |
||||
|
||||
int width = 0; |
||||
int height = fm.getHeight() * Math.max(lines.size(), 1); |
||||
for (String line : lines) { |
||||
width = Math.max(width, SwingUtilities.computeStringWidth(fm, line)); |
||||
} |
||||
|
||||
return new Dimension(insets.left + width + insets.right, insets.top + height + insets.bottom); |
||||
} |
||||
} |
@ -0,0 +1,64 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.utils.FineUIUtils; |
||||
import com.formdev.flatlaf.ui.FlatButtonUI; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
|
||||
import javax.swing.AbstractButton; |
||||
import javax.swing.JComponent; |
||||
import java.awt.Color; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
|
||||
/** |
||||
* 矩形按钮UI,忽略圆角属性 |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2023/11/30 |
||||
*/ |
||||
public class RectangleButtonUI extends FlatButtonUI { |
||||
|
||||
|
||||
public RectangleButtonUI(boolean shared) { |
||||
super(shared); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected void installDefaults(AbstractButton b) { |
||||
super.installDefaults(b); |
||||
selectedBackground = FineUIUtils.getUIColor( "East.TabSelectedColor", "fill.deep"); |
||||
} |
||||
|
||||
@Override |
||||
protected void paintBackground(Graphics g, JComponent c) { |
||||
Color background = getBackground(c); |
||||
if (background == null) { |
||||
return; |
||||
} |
||||
Graphics2D g2 = (Graphics2D) g.create(); |
||||
try { |
||||
FlatUIUtils.setRenderingHints(g2); |
||||
g2.setColor(FlatUIUtils.deriveColor(background, getBackgroundBase(c, true))); |
||||
float focusWidth = FlatUIUtils.getBorderFocusWidth(c); |
||||
FlatUIUtils.paintComponentBackground(g2, 0, 0, c.getWidth(), c.getHeight(), focusWidth, 0); |
||||
} finally { |
||||
g2.dispose(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected Color getBackground(JComponent c) { |
||||
if( ((AbstractButton)c).isSelected() ) { |
||||
return selectedBackground; |
||||
} |
||||
return buttonStateColor( c, |
||||
getBackgroundBase( c, false ), |
||||
disabledBackground, |
||||
isCustomBackground( c.getBackground() ) ? null : focusedBackground, |
||||
hoverBackground, |
||||
pressedBackground ); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,92 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.formdev.flatlaf.ui.FlatScrollBarUI; |
||||
import com.formdev.flatlaf.ui.FlatUIUtils; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.JButton; |
||||
import javax.swing.UIManager; |
||||
import javax.swing.plaf.ComponentUI; |
||||
import java.awt.Color; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
|
||||
/** |
||||
* 应用于主面板报表工作区的滚动条UI,提供给 {@link com.fr.design.cell.bar.DynamicScrollBar} 的UI类 |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0` |
||||
* Created on 2023/12/08 |
||||
*/ |
||||
public class ReportScrollBarUI extends FlatScrollBarUI { |
||||
|
||||
/** |
||||
* 创建UI类 |
||||
* |
||||
* @param c 组件 |
||||
* @return ReportScrollBarUI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent c) { |
||||
return new ReportScrollBarUI(); |
||||
} |
||||
|
||||
@Override |
||||
public void installUI(JComponent c) { |
||||
super.installUI(c); |
||||
scrollBarWidth = UIManager.getInt("ScrollBar.largeBar.width"); |
||||
thumbInsets = UIManager.getInsets("ScrollBar.largeBar.thumbInsets"); |
||||
showButtons = UIManager.getBoolean("ScrollBar.largeBar.showButtons"); |
||||
trackColor = UIManager.getColor("ScrollBar.largeBar.track"); |
||||
} |
||||
|
||||
@Override |
||||
public void uninstallUI(JComponent c) { |
||||
super.uninstallUI(c); |
||||
} |
||||
|
||||
@Override |
||||
protected JButton createDecreaseButton(int orientation) { |
||||
return new ReportScrollBarButton(orientation); |
||||
} |
||||
|
||||
@Override |
||||
protected JButton createIncreaseButton(int orientation) { |
||||
return new ReportScrollBarButton(orientation); |
||||
} |
||||
|
||||
protected class ReportScrollBarButton extends FlatScrollBarButton { |
||||
|
||||
protected final Color defaultButtonBackground = UIManager.getColor("ScrollBar.largeBar.buttonBackground"); |
||||
|
||||
protected ReportScrollBarButton(int direction) { |
||||
super(direction); |
||||
} |
||||
|
||||
@Override |
||||
public void paint(Graphics g) { |
||||
Object[] oldRenderingHints = FlatUIUtils.setRenderingHints(g); |
||||
|
||||
// paint hover or pressed background
|
||||
if (isEnabled()) { |
||||
Color background = (pressedBackground != null && isPressed()) |
||||
? pressedBackground |
||||
: (hoverBackground != null && isHover() |
||||
? hoverBackground |
||||
: null); |
||||
|
||||
if (background == null) { |
||||
background = defaultButtonBackground; |
||||
} |
||||
g.setColor(deriveBackground(background)); |
||||
paintBackground((Graphics2D) g); |
||||
} |
||||
|
||||
// paint arrow
|
||||
g.setColor(deriveForeground(getArrowColor())); |
||||
paintArrow((Graphics2D) g); |
||||
|
||||
FlatUIUtils.resetRenderingHints(g, oldRenderingHints); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,32 @@
|
||||
package com.fine.theme.light.ui; |
||||
|
||||
import com.fine.theme.icon.LazyIcon; |
||||
import com.formdev.flatlaf.ui.FlatTreeUI; |
||||
|
||||
import javax.swing.JComponent; |
||||
import javax.swing.plaf.ComponentUI; |
||||
|
||||
/** |
||||
* 主题化的TreeUI,继承自FlatTreeUI |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2023/11/29 |
||||
*/ |
||||
public class UIFlatTreeUI extends FlatTreeUI { |
||||
|
||||
/** |
||||
* 创建组件UI |
||||
* @param x 组件 |
||||
* @return 返回组件UI |
||||
*/ |
||||
public static ComponentUI createUI(JComponent x) { |
||||
return new UIFlatTreeUI(); |
||||
} |
||||
|
||||
protected void installDefaults() { |
||||
super.installDefaults(); |
||||
setExpandedIcon(new LazyIcon("minus")); |
||||
setCollapsedIcon(new LazyIcon("plus")); |
||||
} |
||||
} |
@ -0,0 +1,52 @@
|
||||
package com.fine.theme.light.ui.laf; |
||||
|
||||
import com.fine.swing.ui.layout.Layouts; |
||||
import com.fine.theme.icon.IconManager; |
||||
import com.fine.theme.light.ui.FineLightIconSet; |
||||
import com.formdev.flatlaf.util.UIScale; |
||||
import com.fr.stable.StringUtils; |
||||
|
||||
/** |
||||
* Fine 暗色主题 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/9/12 |
||||
*/ |
||||
public class FineDarkLaf extends FineLaf { |
||||
|
||||
private static final String USER_SCALE_FACTOR = "userScaleFactor"; |
||||
|
||||
private static final String NAME = "FineLaf Dark"; |
||||
|
||||
/** |
||||
* 安装外观 |
||||
* |
||||
* @return 是否安装成功 |
||||
*/ |
||||
public static boolean setup() { |
||||
IconManager.addSet(new FineLightIconSet()); |
||||
Layouts.setScaleFactor(UIScale.getUserScaleFactor()); |
||||
UIScale.addPropertyChangeListener(evt -> { |
||||
if (StringUtils.equals(evt.getPropertyName(), USER_SCALE_FACTOR)) { |
||||
Layouts.setScaleFactor((float) evt.getNewValue()); |
||||
} |
||||
}); |
||||
return setup(new FineDarkLaf()); |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return NAME; |
||||
} |
||||
|
||||
@Override |
||||
public String getDescription() { |
||||
return "Fine New Dark Look and Feel"; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isDark() { |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,56 @@
|
||||
package com.fine.theme.light.ui.laf; |
||||
|
||||
import com.formdev.flatlaf.FlatLaf; |
||||
import com.formdev.flatlaf.util.SystemInfo; |
||||
|
||||
import javax.swing.PopupFactory; |
||||
|
||||
/** |
||||
* Fine designer new look and feel |
||||
* FineLaf.properties 定义公共属性,如UI等, |
||||
* 其他主题继承该主题进行自定义,防止出现UI不存在等问题 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/12/18 |
||||
*/ |
||||
public abstract class FineLaf extends FlatLaf { |
||||
|
||||
private static final String NAME = "FineLaf"; |
||||
|
||||
|
||||
@Override |
||||
public String getName() { |
||||
return NAME; |
||||
} |
||||
|
||||
@Override |
||||
public String getDescription() { |
||||
return "Fine New Look and Feel"; |
||||
} |
||||
|
||||
@Override |
||||
public void initialize() { |
||||
super.initialize(); |
||||
resetWindowDecorations(); |
||||
// flat默认使用系统弹窗,3.4版本mac及win11上支持圆角边框和阴影效果
|
||||
// popup弹窗不使用flat提供的工具,使用swing原生自带的
|
||||
if (shouldUseNativePopupFactory()) { |
||||
PopupFactory.setSharedInstance(new PopupFactory()); |
||||
} |
||||
} |
||||
|
||||
private static boolean shouldUseNativePopupFactory() { |
||||
return !SystemInfo.isMacOS && !SystemInfo.isWindows_11_orLater; |
||||
} |
||||
|
||||
/** |
||||
* 在win10和win11下重置窗口装饰,恢复系统原生 |
||||
*/ |
||||
private static void resetWindowDecorations() { |
||||
if (SystemInfo.isWindows_10_orLater) { |
||||
System.setProperty("flatlaf.useWindowDecorations", "false"); |
||||
System.setProperty("flatlaf.menuBarEmbedded", "false"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,52 @@
|
||||
package com.fine.theme.light.ui.laf; |
||||
|
||||
import com.fine.swing.ui.layout.Layouts; |
||||
import com.fine.theme.icon.IconManager; |
||||
import com.fine.theme.light.ui.FineLightIconSet; |
||||
import com.formdev.flatlaf.util.UIScale; |
||||
import com.fr.stable.StringUtils; |
||||
|
||||
/** |
||||
* Fine 亮色主题 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/9/12 |
||||
*/ |
||||
public class FineLightLaf extends FineLaf { |
||||
|
||||
public static final String USER_SCALE_FACTOR = "userScaleFactor"; |
||||
|
||||
private static final String NAME = "FineLaf Light"; |
||||
|
||||
/** |
||||
* 安装外观 |
||||
* |
||||
* @return 是否安装成功 |
||||
*/ |
||||
public static boolean setup() { |
||||
IconManager.addSet(new FineLightIconSet()); |
||||
Layouts.setScaleFactor(UIScale.getUserScaleFactor()); |
||||
UIScale.addPropertyChangeListener(evt -> { |
||||
if (StringUtils.equals(evt.getPropertyName(), USER_SCALE_FACTOR)) { |
||||
Layouts.setScaleFactor((float) evt.getNewValue()); |
||||
} |
||||
}); |
||||
return setup(new FineLightLaf()); |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return NAME; |
||||
} |
||||
|
||||
@Override |
||||
public String getDescription() { |
||||
return "Fine New Light Look and Feel"; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isDark() { |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,131 @@
|
||||
package com.fine.theme.utils; |
||||
|
||||
import com.formdev.flatlaf.util.Animator.Interpolator; |
||||
import com.formdev.flatlaf.util.CubicBezierEasing; |
||||
|
||||
import java.awt.Component; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
|
||||
/** |
||||
* Painter 可在组件值更改时自动对绘画进行动画处理。 |
||||
* getValues(Component) 返回组件的值。如果值已更改,则 paintAnimated(Component, Graphics2D, int, int, int, int, float[]) |
||||
* 使用动画值(从旧值到新值)多次调用。如果 getValues(Component) 返回多个值,则每个值都有自己独立的动画。 |
||||
* 仅当传递给 paintWithAnimation(Component, Graphics, int, int, int, int) 的组件是 的 JComponent实例时,动画才有效。 |
||||
* 在组件上设置客户端属性以存储动画状态。 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2024/1/29 |
||||
*/ |
||||
public interface AnimatedPainter { |
||||
/** |
||||
* 开始绘画。调用 paintAnimated(Component, Graphics2D, int, int, int, int, float[]) 一次来绘制当前值 |
||||
* 或者如果值与上次绘制相比发生了变化,则它会启动动画并使用动画值(从旧值到新值)多次调用 |
||||
* paintAnimated(Component, Graphics2D, int, int, int, int, float[]) 。 |
||||
* |
||||
* @param c 动画所属组件 |
||||
* @param g 绘制上下文 |
||||
* @param x x |
||||
* @param y y |
||||
* @param width 区域宽度 |
||||
* @param height 区域高度 |
||||
*/ |
||||
default void paintWithAnimation(Component c, Graphics g, int x, int y, int width, int height) { |
||||
AnimatedPainterSupport.paint(this, c, g, x, y, width, height); |
||||
} |
||||
|
||||
/** |
||||
* 绘制给定的(动画)值。 |
||||
* 从 paintWithAnimation(Component, Graphics, int, int, int, int)调用。 |
||||
* |
||||
* @param c 动画所属组件 |
||||
* @param g 绘制上下文 |
||||
* @param x x |
||||
* @param y y |
||||
* @param width 区域宽度 |
||||
* @param height 区域高度 |
||||
* @param animatedValues 动画值,介于先前的值 getValues(Component) 和返回的最新值 getValues(Component) 之间 |
||||
*/ |
||||
void paintAnimated(Component c, Graphics2D g, int x, int y, int width, int height, float[] animatedValues); |
||||
|
||||
/** |
||||
* 从 animator 调用以重新绘制区域。 |
||||
* 用于限制重绘区域。例如,如果希望只有底部边框有动画。如果对多个边框进行动画处理(例如底部和右侧),那么单独的重绘是没有意义的, |
||||
* 因为 Swing 重绘管理器会合并区域并重绘整个组件。默认实现会重新绘制整个给定区域。 |
||||
*/ |
||||
default void repaintDuringAnimation(Component c, int x, int y, int width, int height) { |
||||
c.repaint(x, y, width, height); |
||||
} |
||||
|
||||
/** |
||||
* 获取组件的值。 |
||||
* 如果值发生更改,则此类将从旧值动画到新值。如果返回多个值,则每个值都有自己独立的动画。 |
||||
* 对于切换按钮,0 表示关闭和 1 表示打开 |
||||
*/ |
||||
float[] getValues(Component c); |
||||
|
||||
/** |
||||
* 动画是否启用 |
||||
*/ |
||||
default boolean isAnimationEnabled() { |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 返回动画的持续时间(以毫秒为单位)(默认值为 150)。 |
||||
*/ |
||||
default int getAnimationDuration() { |
||||
return 150; |
||||
} |
||||
|
||||
/** |
||||
* 返回动画的分辨率(以毫秒为单位)(默认值为 10)。分辨率是计时事件之间的时间量。 |
||||
*/ |
||||
default int getAnimationResolution() { |
||||
return 10; |
||||
} |
||||
|
||||
/** |
||||
* 返回动画的插值器。默认值为 {@link CubicBezierEasing#STANDARD_EASING}. |
||||
*/ |
||||
default Interpolator getAnimationInterpolator() { |
||||
return CubicBezierEasing.STANDARD_EASING; |
||||
} |
||||
|
||||
/** |
||||
* 返回给定值索引和值的动画持续时间(以毫秒为单位)(默认值为 150)。 |
||||
*/ |
||||
default int getAnimationDuration(int valueIndex, float value) { |
||||
return getAnimationDuration(); |
||||
} |
||||
|
||||
/** |
||||
* 返回给定值索引和值的动画分辨率(以毫秒为单位)(默认值为 10)。分辨率是计时事件之间的时间量 |
||||
*/ |
||||
default int getAnimationResolution(int valueIndex, float value) { |
||||
return getAnimationResolution(); |
||||
} |
||||
|
||||
/** |
||||
* 返回给定值索引和值的动画插值器。默认值为 {@link CubicBezierEasing#STANDARD_EASING}. |
||||
*/ |
||||
default Interpolator getAnimationInterpolator(int valueIndex, float value) { |
||||
return getAnimationInterpolator(); |
||||
} |
||||
|
||||
/** |
||||
* 返回用于存储动画支持的客户端属性键。 |
||||
*/ |
||||
default Object getClientPropertyKey() { |
||||
return getClass(); |
||||
} |
||||
|
||||
/** |
||||
* 保存位置,提供给 repaintDuringAnimation(Component, int, int, int, int)使用重新绘制动画区域。 |
||||
* 仅当传递到的 paintWithAnimation(Component, Graphics, int, int, int, int) 图形上下文使用转换的位置时才需要。 |
||||
*/ |
||||
static void saveRepaintLocation(AnimatedPainter painter, Component c, int x, int y) { |
||||
AnimatedPainterSupport.saveRepaintLocation(painter, c, x, y); |
||||
} |
||||
} |
@ -0,0 +1,174 @@
|
||||
package com.fine.theme.utils; |
||||
|
||||
import com.formdev.flatlaf.util.Animator; |
||||
|
||||
import javax.swing.JComponent; |
||||
import java.awt.Component; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
|
||||
/** |
||||
* 动画支持类,用于存储动画状态并实现动画。 |
||||
* 逻辑保持与AnimatedIcon逻辑一致。 |
||||
* |
||||
* @author vito |
||||
* @since 12.0 |
||||
* Created on 2024/1/29 |
||||
*/ |
||||
class AnimatedPainterSupport { |
||||
private int valueIndex; |
||||
private float startValue; |
||||
private float targetValue; |
||||
private float animatedValue; |
||||
private float fraction; |
||||
|
||||
private Animator animator; |
||||
|
||||
// last bounds of the paint area needed to repaint while animating
|
||||
private int x; |
||||
private int y; |
||||
private int width; |
||||
private int height; |
||||
|
||||
/** |
||||
* 用于包内绘制,保持与AnimatedIcon逻辑一致。 |
||||
* 后期整合逻辑之后在整理代码质量 |
||||
*/ |
||||
static void paint(AnimatedPainter painter, Component c, Graphics g, |
||||
int x, int y, int width, int height) { |
||||
if (!isAnimationEnabled(painter, c)) { |
||||
// paint without animation if animation is disabled or
|
||||
// component is not a JComponent and therefore does not support
|
||||
// client properties, which are required to keep animation state
|
||||
painter.paintAnimated(c, (Graphics2D) g, x, y, width, height, painter.getValues(c)); |
||||
return; |
||||
} |
||||
|
||||
// get component values
|
||||
float[] values = painter.getValues(c); |
||||
|
||||
JComponent jc = (JComponent) c; |
||||
Object key = painter.getClientPropertyKey(); |
||||
AnimatedPainterSupport[] ass = (AnimatedPainterSupport[]) jc.getClientProperty(key); |
||||
|
||||
// check whether length of values array has changed
|
||||
if (ass != null && ass.length != values.length) { |
||||
// cancel all running animations
|
||||
for (AnimatedPainterSupport as : ass) { |
||||
if (as.animator != null) { |
||||
as.animator.cancel(); |
||||
} |
||||
} |
||||
ass = null; |
||||
} |
||||
|
||||
if (ass == null) { |
||||
ass = new AnimatedPainterSupport[values.length]; |
||||
jc.putClientProperty(key, ass); |
||||
} |
||||
|
||||
for (int i = 0; i < ass.length; i++) { |
||||
AnimatedPainterSupport as = ass[i]; |
||||
float value = values[i]; |
||||
|
||||
if (as == null) { |
||||
// painted first time --> do not animate, but remember current component value
|
||||
as = new AnimatedPainterSupport(); |
||||
as.valueIndex = i; |
||||
as.startValue = as.targetValue = as.animatedValue = value; |
||||
ass[i] = as; |
||||
} else if (value != as.targetValue) { |
||||
// value changed --> (re)start animation
|
||||
|
||||
int animationDuration = painter.getAnimationDuration(as.valueIndex, value); |
||||
|
||||
// do not animate if animation duration (for current value) is zero
|
||||
if (animationDuration <= 0) { |
||||
if (as.animator != null) { |
||||
as.animator.cancel(); |
||||
as.animator = null; |
||||
} |
||||
as.startValue = as.targetValue = as.animatedValue = value; |
||||
as.fraction = 0; |
||||
continue; |
||||
} |
||||
|
||||
if (as.animator == null) { |
||||
// create animator
|
||||
AnimatedPainterSupport as2 = as; |
||||
as.animator = new Animator(1, fraction -> { |
||||
// check whether component was removed while animation is running
|
||||
if (!c.isDisplayable()) { |
||||
as2.animator.stop(); |
||||
return; |
||||
} |
||||
|
||||
// compute animated value
|
||||
as2.animatedValue = as2.startValue + ((as2.targetValue - as2.startValue) * fraction); |
||||
as2.fraction = fraction; |
||||
|
||||
// repaint
|
||||
painter.repaintDuringAnimation(c, as2.x, as2.y, as2.width, as2.height); |
||||
}, () -> { |
||||
as2.startValue = as2.animatedValue = as2.targetValue; |
||||
as2.animator = null; |
||||
}); |
||||
} |
||||
|
||||
if (as.animator.isRunning()) { |
||||
// if animation is still running, restart it from the current
|
||||
// animated value to the new target value with reduced duration
|
||||
as.animator.cancel(); |
||||
int duration2 = (int) (animationDuration * as.fraction); |
||||
if (duration2 > 0) { |
||||
as.animator.setDuration(duration2); |
||||
} |
||||
as.startValue = as.animatedValue; |
||||
} else { |
||||
// new animation
|
||||
as.animator.setDuration(animationDuration); |
||||
|
||||
as.animatedValue = as.startValue; |
||||
} |
||||
|
||||
// update animator for new value
|
||||
as.animator.setResolution(painter.getAnimationResolution(as.valueIndex, value)); |
||||
as.animator.setInterpolator(painter.getAnimationInterpolator(as.valueIndex, value)); |
||||
|
||||
// start animation
|
||||
as.targetValue = value; |
||||
as.animator.start(); |
||||
} |
||||
|
||||
as.x = x; |
||||
as.y = y; |
||||
as.width = width; |
||||
as.height = height; |
||||
} |
||||
|
||||
float[] animatedValues = new float[ass.length]; |
||||
for (int i = 0; i < ass.length; i++) { |
||||
animatedValues[i] = ass[i].animatedValue; |
||||
} |
||||
|
||||
painter.paintAnimated(c, (Graphics2D) g, x, y, width, height, animatedValues); |
||||
} |
||||
|
||||
private static boolean isAnimationEnabled(AnimatedPainter painter, Component c) { |
||||
return Animator.useAnimation() && painter.isAnimationEnabled() && c instanceof JComponent; |
||||
} |
||||
|
||||
static void saveRepaintLocation(AnimatedPainter painter, Component c, int x, int y) { |
||||
if (!isAnimationEnabled(painter, c)) { |
||||
return; |
||||
} |
||||
|
||||
AnimatedPainterSupport[] ass = (AnimatedPainterSupport[]) ((JComponent) c).getClientProperty(painter.getClientPropertyKey()); |
||||
if (ass != null) { |
||||
for (AnimatedPainterSupport as : ass) { |
||||
as.x = x; |
||||
as.y = y; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,43 @@
|
||||
package com.fine.theme.utils; |
||||
|
||||
import com.formdev.flatlaf.FlatClientProperties; |
||||
|
||||
/** |
||||
* FR-UI中使用的各类属性 |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2023/12/15 |
||||
*/ |
||||
public interface FineClientProperties extends FlatClientProperties { |
||||
|
||||
//--------------------------- ButtonGroup -----------------------
|
||||
String BUTTON_TYPE_GROUP = "group"; |
||||
|
||||
String BUTTON_BORDER = "buttonBorder"; |
||||
String BUTTON_BORDER_LEFT_ROUND_RECT = "leftRoundRect"; |
||||
String BUTTON_BORDER_RIGHT_ROUND_RECT = "rightRoundRect"; |
||||
|
||||
//--------------------------- PopupMenu -----------------------
|
||||
String MENU_ITEM_TYPE = "MenuItemType"; |
||||
String MENU_ITEM_TYPE_LOCK = "lock"; |
||||
|
||||
String BUTTON_GROUP_POSITION = "group_position"; |
||||
|
||||
//--------------------------- Panel ----------------------------
|
||||
String PANEL_TYPE = "panelType"; |
||||
String ROUNDED_PANEL = "roundedPanel"; |
||||
|
||||
//--------------------------- ComboBox ----------------------------
|
||||
String COMBO_BOX_TYPE = "comboBoxType"; |
||||
String ADAPTIVE_COMBO_BOX = "adaptiveComboBox"; |
||||
|
||||
int GROUP_BUTTON_POSITION_INNER = 0; |
||||
int GROUP_BUTTON_POSITION_LEFT = 1; |
||||
int GROUP_BUTTON_POSITION_RIGHT = 2; |
||||
int GROUP_BUTTON_POSITION_LEFT_TOP = 3; |
||||
int GROUP_BUTTON_POSITION_LEFT_BOTTOM = 4; |
||||
int GROUP_BUTTON_POSITION_RIGHT_TOP = 5; |
||||
int GROUP_BUTTON_POSITION_RIGHT_BOTTOM = 6; |
||||
|
||||
} |
@ -0,0 +1,65 @@
|
||||
package com.fine.theme.utils; |
||||
|
||||
import com.fine.theme.icon.LazyIcon; |
||||
import com.fr.design.gui.ibutton.UIButtonGroup; |
||||
import com.fr.stable.Constants; |
||||
|
||||
import javax.swing.Icon; |
||||
import javax.swing.JButton; |
||||
import javax.swing.JPanel; |
||||
|
||||
import static com.fine.swing.ui.layout.Layouts.cell; |
||||
import static com.fine.swing.ui.layout.Layouts.flex; |
||||
import static com.fine.swing.ui.layout.Layouts.row; |
||||
import static com.fine.theme.utils.FineUIStyle.STYLE_PRIMARY; |
||||
import static com.fine.theme.utils.FineUIStyle.setStyle; |
||||
|
||||
/** |
||||
* 设计器典型组件组合工厂 |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2024/07/26 |
||||
*/ |
||||
public class FineComponentsFactory { |
||||
|
||||
/** |
||||
* 创建水平对齐按钮组 |
||||
* |
||||
* @return 组件 |
||||
*/ |
||||
public static UIButtonGroup<Integer> createHorizontalAlignmentButtonGroup() { |
||||
Icon[][] alignmentIconArray = {{new LazyIcon("h_left"), new LazyIcon("h_left").white()}, |
||||
{new LazyIcon("h_center"), new LazyIcon("h_center").white()}, |
||||
{new LazyIcon("h_right"), new LazyIcon("h_right").white()}}; |
||||
Integer[] alignment = new Integer[]{Constants.LEFT, Constants.CENTER, Constants.RIGHT}; |
||||
return new UIButtonGroup<>(alignmentIconArray, alignment); |
||||
} |
||||
|
||||
/** |
||||
* 创建垂直对齐按钮组 |
||||
* |
||||
* @return 组件 |
||||
*/ |
||||
public static UIButtonGroup<Integer> createVerticalAlignmentButtonGroup() { |
||||
Icon[][] alignmentIconArray = {{new LazyIcon("v_top"), new LazyIcon("v_top").white()}, |
||||
{new LazyIcon("v_center"), new LazyIcon("v_center").white()}, |
||||
{new LazyIcon("v_bottom"), new LazyIcon("v_bottom").white()}}; |
||||
Integer[] alignment = new Integer[]{Constants.TOP, Constants.CENTER, Constants.BOTTOM}; |
||||
return new UIButtonGroup<>(alignmentIconArray, alignment); |
||||
} |
||||
|
||||
/** |
||||
* 创建确认&取消按钮组,间距为8,右对齐 |
||||
* |
||||
* @param confirmButton 确认按钮 |
||||
* @param cancelButton 取消按钮 |
||||
* @return 确认&取消按钮组面板 |
||||
*/ |
||||
public static JPanel createConfirmCancelButtonPane(JButton confirmButton, JButton cancelButton) { |
||||
setStyle(confirmButton, STYLE_PRIMARY); |
||||
return row(8, |
||||
flex(), cell(confirmButton), cell(cancelButton)).getComponent(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,189 @@
|
||||
package com.fine.theme.utils; |
||||
|
||||
import com.fine.swing.ui.layout.Column; |
||||
import com.fine.swing.ui.layout.Layouts; |
||||
import com.fine.swing.ui.layout.Row; |
||||
import com.fine.swing.ui.layout.Spacer; |
||||
import com.fr.design.foldablepane.UIExpandablePane; |
||||
import com.fr.design.gui.ilable.UILabel; |
||||
import com.fr.log.FineLoggerFactory; |
||||
import com.fr.stable.collections.combination.Pair; |
||||
|
||||
import javax.swing.JPanel; |
||||
import java.awt.BorderLayout; |
||||
import java.awt.Component; |
||||
import java.util.Arrays; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.IntStream; |
||||
|
||||
import static com.fine.swing.ui.layout.Layouts.cell; |
||||
|
||||
/** |
||||
* 设计器典型布局构建器 |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2024/05/08 |
||||
*/ |
||||
public class FineLayoutBuilder { |
||||
|
||||
/** |
||||
* 创建标准行列表格布局,行内元素均匀分布 |
||||
* |
||||
* @param elePerRow 每行元素数 |
||||
* @param rowSpacing 行内间距 |
||||
* @param colSpacing 列内间距 |
||||
* @param componentList 组件 |
||||
* @return 表格面板 |
||||
*/ |
||||
public static Column createCommonTableLayout(int elePerRow, int rowSpacing, int colSpacing, List<? extends Component> componentList) { |
||||
int rowNum = componentList.size() / elePerRow + 1; |
||||
Iterator<? extends Component> iterator = componentList.iterator(); |
||||
|
||||
Column column = new Column(); |
||||
column.setSpacing(colSpacing); |
||||
|
||||
Row currentRow; |
||||
for (int i = 0; i < rowNum; i++) { |
||||
currentRow = new Row(); |
||||
currentRow.setSpacing(rowSpacing); |
||||
for (int j = 0; j < elePerRow; j++) { |
||||
Layouts.populate(currentRow, cell( |
||||
iterator.hasNext() ? iterator.next() : new Spacer(1) |
||||
).weight(1)); |
||||
} |
||||
column.add(currentRow); |
||||
} |
||||
return column; |
||||
} |
||||
|
||||
/** |
||||
* 兼容TableLayout配置项,生成网格布局面板 |
||||
* |
||||
* @param colSpacing 间距 |
||||
* @param components 组件二维数组,内部每个一维数组均为行内组件,一维数组元素需与weight严格对应 |
||||
* @param weight 行内权重列表,形如[0.4,0.6] 即首个元素占比0.4,第二个元素占比0.6 |
||||
* @return 面板 |
||||
*/ |
||||
public static JPanel compatibleTableLayout(int colSpacing, Component[][] components, double[] weight) { |
||||
Column column = new Column(); |
||||
column.setSpacing(colSpacing); |
||||
try { |
||||
for (Component[] componentArray : components) { |
||||
Row row = new Row(); |
||||
List<Component> visibleComponents = Arrays.stream(componentArray) |
||||
.filter(com -> com != null && com.isVisible()).collect(Collectors.toList()); |
||||
if (visibleComponents.size() >= 1) { |
||||
// 仅当存在可见组件时处理布局
|
||||
dealWithVisibleComponents(weight, column, componentArray, row, visibleComponents); |
||||
} |
||||
} |
||||
return asBorderLayoutWrapped(column); |
||||
} catch (Exception e) { |
||||
FineLoggerFactory.getLogger().error(e, "[Designer] create layout failed."); |
||||
} |
||||
return new JPanel(); |
||||
} |
||||
|
||||
private static void dealWithVisibleComponents(double[] weight, Column column, Component[] value, Row row, List<Component> components) { |
||||
if (components.size() == 1 && value[0] != null) { |
||||
// 仅存在首个元素,则该元素自适应占满整行
|
||||
Layouts.populate(row, cell(components.get(0)).weight(1)); |
||||
} else { |
||||
// 其他场景,按权重分配布局,以适配原TableLayout形式
|
||||
for (int j = 0; j < value.length; j++) { |
||||
Component component = value[j]; |
||||
if (component == null) { |
||||
component = new Spacer(1); |
||||
} |
||||
Layouts.populate(row, cell(component).weight(weight[j])); |
||||
} |
||||
} |
||||
column.add(row); |
||||
} |
||||
|
||||
/** |
||||
* 创建竖向排列的扩展面板列表 |
||||
* |
||||
* @param spacing 间距 |
||||
* @param elements 面板元素,含标题、面板 |
||||
* @return 竖向排列面板 |
||||
*/ |
||||
@SafeVarargs |
||||
public static Column createVerticalExpandPaneLayout(int spacing, Pair<String, JPanel>... elements) { |
||||
UIExpandablePane[] panes = IntStream.range(0, elements.length) |
||||
.mapToObj(i -> { |
||||
Pair<String, JPanel> pair = elements[i]; |
||||
if (i != elements.length - 1) { |
||||
return new UIExpandablePane(pair.getFirst(), pair.getSecond(), true); |
||||
} |
||||
return new UIExpandablePane(pair.getFirst(), pair.getSecond()); |
||||
}) |
||||
.toArray(UIExpandablePane[]::new); |
||||
return createVerticalLayout(spacing, panes); |
||||
} |
||||
|
||||
/** |
||||
* 创建垂直布局面板 |
||||
* |
||||
* @param spacing 间距 |
||||
* @param elements 面板元素 |
||||
* @return 面板 |
||||
*/ |
||||
public static Column createVerticalLayout(int spacing, Component... elements) { |
||||
Column column = new Column(); |
||||
column.setSpacing(spacing); |
||||
for (Component element : elements) { |
||||
column.add(element); |
||||
} |
||||
return column; |
||||
} |
||||
|
||||
/** |
||||
* 创建水平布局面板 |
||||
* |
||||
* @param spacing 间距 |
||||
* @param elements 面板元素 |
||||
* @return 面板 |
||||
*/ |
||||
public static Row createHorizontalLayout(int spacing, double[] weight, Component... elements) { |
||||
Row row = new Row(); |
||||
row.setSpacing(spacing); |
||||
for (int i = 0; i < elements.length; i++) { |
||||
Layouts.populate(row, cell(elements[i]).weight(weight[i])); |
||||
} |
||||
return row; |
||||
} |
||||
|
||||
/** |
||||
* 创建水平布局面板 |
||||
* |
||||
* @param spacing 间距 |
||||
* @param elements 面板元素 |
||||
* @return 面板 |
||||
*/ |
||||
public static Row createHorizontalLayout(int spacing, Component... elements) { |
||||
Row row = new Row(); |
||||
row.setSpacing(spacing); |
||||
for (Component element : elements) { |
||||
Layouts.populate(row, cell(element)); |
||||
} |
||||
return row; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 组件包装于BorderLayout中 |
||||
* |
||||
* @param component 组件 |
||||
* @return 包装后的面板 |
||||
*/ |
||||
public static JPanel asBorderLayoutWrapped(Component component) { |
||||
JPanel panel = new JPanel(new BorderLayout()); |
||||
panel.add(component); |
||||
return panel; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,25 @@
|
||||
package com.fine.theme.utils; |
||||
|
||||
import java.awt.Dimension; |
||||
|
||||
/** |
||||
* 主题UI常量 |
||||
* |
||||
* @author Levy.Xie |
||||
* @since 11.0 |
||||
* Created on 2024/08/05 |
||||
*/ |
||||
public class FineUIConstants { |
||||
|
||||
public static final int SCALE_FONT_SIZE_12 = FineUIScale.scale(12); |
||||
|
||||
public static final int SCALE_FONT_SIZE_13 = FineUIScale.scale(13); |
||||
|
||||
/** |
||||
* 对话框常量 |
||||
*/ |
||||
public static class Dialog { |
||||
public static final Dimension POP_DIALOG_MEDIUM = FineUIScale.scale(new Dimension(360, 400)); |
||||
|
||||
} |
||||
} |
@ -0,0 +1,86 @@
|
||||
package com.fine.theme.utils; |
||||
|
||||
import com.formdev.flatlaf.util.UIScale; |
||||
|
||||
import javax.swing.plaf.DimensionUIResource; |
||||
import javax.swing.plaf.InsetsUIResource; |
||||
import javax.swing.plaf.UIResource; |
||||
import java.awt.Dimension; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.Insets; |
||||
|
||||
/** |
||||
* UI缩放工具 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2023/11/15 |
||||
*/ |
||||
public class FineUIScale { |
||||
/** |
||||
* Multiplies the given value by the user scale factor. |
||||
*/ |
||||
public static float scale(float value) { |
||||
return UIScale.scale(value); |
||||
} |
||||
|
||||
/** |
||||
* Multiplies the given value by the user scale factor and rounds the result. |
||||
*/ |
||||
public static int scale(int value) { |
||||
return UIScale.scale(value); |
||||
} |
||||
|
||||
/** |
||||
* Similar as {@link #scale(int)} but always "rounds down". |
||||
* <p> |
||||
* For use in special cases. {@link #scale(int)} is the preferred method. |
||||
*/ |
||||
public static int scale2(int value) { |
||||
return UIScale.scale2(value); |
||||
} |
||||
|
||||
/** |
||||
* Divides the given value by the user scale factor. |
||||
*/ |
||||
public static float unscale(float value) { |
||||
return UIScale.unscale(value); |
||||
} |
||||
|
||||
/** |
||||
* Divides the given value by the user scale factor and rounds the result. |
||||
*/ |
||||
public static int unscale(int value) { |
||||
return UIScale.unscale(value); |
||||
} |
||||
|
||||
/** |
||||
* If user scale factor is not 1, scale the given graphics context by invoking |
||||
* {@link Graphics2D#scale(double, double)} with user scale factor. |
||||
*/ |
||||
public static void scaleGraphics(Graphics2D g) { |
||||
UIScale.scaleGraphics(g); |
||||
} |
||||
|
||||
/** |
||||
* Scales the given dimension with the user scale factor. |
||||
* <p> |
||||
* If user scale factor is 1, then the given dimension is simply returned. |
||||
* Otherwise, a new instance of {@link Dimension} or {@link DimensionUIResource} |
||||
* is returned, depending on whether the passed dimension implements {@link UIResource}. |
||||
*/ |
||||
public static Dimension scale(Dimension dimension) { |
||||
return UIScale.scale(dimension); |
||||
} |
||||
|
||||
/** |
||||
* Scales the given insets with the user scale factor. |
||||
* <p> |
||||
* If user scale factor is 1, then the given insets is simply returned. |
||||
* Otherwise, a new instance of {@link Insets} or {@link InsetsUIResource} |
||||
* is returned, depending on whether the passed dimension implements {@link UIResource}. |
||||
*/ |
||||
public static Insets scale(Insets insets) { |
||||
return UIScale.scale(insets); |
||||
} |
||||
} |
@ -0,0 +1,121 @@
|
||||
package com.fine.theme.utils; |
||||
|
||||
import com.finebi.cbb.utils.StringUtils; |
||||
|
||||
import javax.swing.JComponent; |
||||
|
||||
/** |
||||
* UI样式工具 |
||||
* |
||||
* @author vito |
||||
* @since 11.0 |
||||
* Created on 2024/1/4 |
||||
*/ |
||||
public interface FineUIStyle { |
||||
|
||||
String IN_TOOLBAR_GROUP = "inToolbarGroup"; |
||||
String STYLE_PRIMARY = "primary"; |
||||
String STYLE_SECONDARY = "secondary"; |
||||
String STYLE_TEXT = "text"; |
||||
String STYLE_SIZE_MEDIUM = "mediumSize"; |
||||
String STYLE_SIZE_SMALL = "smallSize"; |
||||
String MENU_BAR = "menuBar"; |
||||
String LIGHT_GREY = "lightGrey"; |
||||
String IN_TOOLBAR_LEFT = "inToolbarLeft"; |
||||
String IN_TOOLBAR_RIGHT = "inToolbarRight"; |
||||
String NORMAL_COLOR = "normalColor"; |
||||
String TOP_TOOLS = "topTools"; |
||||
String BRAND_COLOR_LABEL = "brandColorLabel"; |
||||
String BUTTON_TAB_ACTION = "tabAction"; |
||||
String LABEL_BOLD = "boldLabel"; |
||||
String LABEL_SECONDARY = "secondaryLabel"; |
||||
String LABEL_TIP = "tipLabel"; |
||||
String LABEL_WARNING_TIP = "warningTipLabel"; |
||||
String LABEL_UILIST = "uiListLabel"; |
||||
String PLAIN_BUTTON = "plainButton"; |
||||
String TOGGLE_GROUP = "inToggleGroup"; |
||||
String COMPACT_BUTTON = "compactButton"; |
||||
String MENU_TOOL_BAR = "menuToolBar"; |
||||
String MENU_ITEM_TOOL_BAR = "menuItemToolBar"; |
||||
String POPUP_MENU_TOOL_BAR = "popupMenuToolBar"; |
||||
String POPUP_MENU_DROPDOWN = "dropdownPopupMenu"; |
||||
String TRANSPARENT_TEXT_FIELD = "transparentTextField"; |
||||
String TRANSPARENT_BACKGROUND = "transparentBackground"; |
||||
String PURE_LIST = "pureList"; |
||||
String NO_BORDER_LIST = "noBorderList"; |
||||
String PURE_TREE = "pureTree"; |
||||
String PASTEL_BUTTON = "pastelButton"; |
||||
String BREADCRUMB_BUTTON = "breadcrumbButton"; |
||||
|
||||
String DEFAULT_TABLE = "defaultTable"; |
||||
String WHITE_BUTTON = "whiteButton"; |
||||
String ORIGINAL_BUTTON = "originalButton"; |
||||
String DETAIL_LABEL = "detailLabel"; |
||||
|
||||
|
||||
/** |
||||
* 添加组件的样式类,类似css,该方法会接在原样式后方 |
||||
* <code> |
||||
* FineClientProperties.appendStyle("primary small") |
||||
* </code> |
||||
* |
||||
* @param it 组件 |
||||
* @param styleClass 样式字符串,支持连续添加类,用空格 |
||||
*/ |
||||
static void appendStyle(JComponent it, String styleClass) { |
||||
Object oriProperty = it.getClientProperty(FineClientProperties.STYLE_CLASS); |
||||
if (oriProperty instanceof String && StringUtils.isNotBlank((String) oriProperty)) { |
||||
styleClass = oriProperty + " " + styleClass; |
||||
} |
||||
it.putClientProperty(FineClientProperties.STYLE_CLASS, styleClass); |
||||
} |
||||
|
||||
/** |
||||
* 设置组件的样式类,类似css,该方法会替换原样式 |
||||
* <code> |
||||
* FineClientProperties.setStyle("primary small") |
||||
* </code> |
||||
* |
||||
* @param jComponent 组件 |
||||
* @param styleClass 样式字符串,支持连续添加类,用空格 |
||||
*/ |
||||
static void setStyle(JComponent jComponent, String styleClass) { |
||||
jComponent.putClientProperty(FineClientProperties.STYLE_CLASS, styleClass); |
||||
} |
||||
|
||||
/** |
||||
* 样式组合 |
||||
* |
||||
* @param styleClasses 所有样式 |
||||
* @return 样式列表 |
||||
*/ |
||||
static String joinStyle(String... styleClasses) { |
||||
final StringBuilder sb = new StringBuilder(); |
||||
for (final String style : styleClasses) { |
||||
if (style == null) { |
||||
continue; |
||||
} |
||||
if (sb.length() > 0) { |
||||
sb.append(" "); |
||||
} |
||||
sb.append(style); |
||||
} |
||||
return sb.toString(); |
||||
} |
||||
|
||||
/** |
||||
* 包含样式 |
||||
* |
||||
* @param jComponent 组件 |
||||
* @param styleClass 样式 |
||||
* @return 是否包含指定的样式 |
||||
*/ |
||||
static boolean hasStyle(JComponent jComponent, String styleClass) { |
||||
Object style = jComponent.getClientProperty(FineClientProperties.STYLE_CLASS); |
||||
if (style instanceof String && StringUtils.isNotBlank((String) style)) { |
||||
return ((String) style).contains(styleClass); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,438 @@
|
||||
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.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 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).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); |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue