Browse Source

REPORT-127437 feat:适配插件图标

fbp/feature
lemon 3 months ago
parent
commit
783e2af1cd
  1. 213
      designer-base/src/main/java/com/fine/theme/icon/plugin/PluginIconManager.java
  2. 136
      designer-base/src/main/java/com/fine/theme/icon/plugin/PluginIconSet.java
  3. 129
      designer-base/src/main/java/com/fine/theme/icon/plugin/PluginLazyIcon.java
  4. 68
      designer-base/src/main/java/com/fr/design/fun/LazyIconProvider.java
  5. 71
      designer-base/src/main/java/com/fr/design/fun/impl/AbstractLazyIconProvider.java

213
designer-base/src/main/java/com/fine/theme/icon/plugin/PluginIconManager.java

@ -0,0 +1,213 @@
package com.fine.theme.icon.plugin;
import com.fine.theme.icon.IconException;
import com.fine.theme.icon.IconManager;
import com.fine.theme.icon.IconSet;
import com.fine.theme.icon.IconType;
import com.fine.theme.icon.LazyIcon;
import com.fr.base.extension.FileExtension;
import com.fr.general.IOUtils;
import com.fr.log.FineLoggerFactory;
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.HashMap;
import java.util.Map;
/**
* 管理插件图标集
*
* @author lemon
* @since
* Created on 2024/08/23
*/
public class PluginIconManager {
public static final String ICON_DISABLE_SUFFIX = "_disable";
public static final Dimension DEFAULT_DIMENSION = new Dimension(16, 16);
private static final Map<String, IconSet> SOURCE_ICON_MAPS = new HashMap<>(2);
private static final HashMap<String, HashMap<String, WeakReference<Icon>>> CACHE = new HashMap<>();
/**
* 获取图标集
*
* @param id 图标集ID
* @return 图标集
*/
public static IconSet getSet(String source, String id) {
IconSet set = SOURCE_ICON_MAPS.get(source);
if (set != null && set.getId().equals(id)) {
return set;
}
throw new IconException("[PluginIconManager] Can not find icon set by id: " + id);
}
/**
* 添加图标集
*
* @param source 插件
* @param set 图标集
*/
public static void addSet(@NotNull String source, @NotNull IconSet set) {
if (SOURCE_ICON_MAPS.containsKey(source)) {
FineLoggerFactory.getLogger().warn("[PluginIconManager] plugin:{} icon set already exists: " + source);
}
SOURCE_ICON_MAPS.put(source, set);
clearCacheBySource(source);
}
/**
* 更新指定插件图标集
*
* @param source 插件
* @param set 图标集
*/
public static void updateSet(@NotNull String source, @NotNull IconSet set) {
SOURCE_ICON_MAPS.put(source, set);
clearCacheBySource(source);
}
/**
* 删除指定插件图标集
*
* @param source 插件
*/
public static void removeSet(@NotNull String source) {
SOURCE_ICON_MAPS.remove(source);
clearCacheBySource(source);
}
/**
* 根据图标ID获取图标
* <p>
* 查找路径
* 1查找图集图标
* 2路径为图片图标从路径再查找
* 3提供默认svg图标
*
* @param id 图标ID
* @param <I> 图标类型
* @return 图标
*/
@NotNull
public static <I extends Icon> I getIcon(@NotNull String source, @NotNull final String id, @NotNull Dimension dimension, @NotNull IconType type) {
Icon icon = findIcon(source, id, dimension, type);
if (icon == null) {
// 只有找不到再进行其他fallback,提升效率
if (IconManager.isImageIcon(id)) {
return (I) fallbackLegacyIcon(id);
} else {
FineLoggerFactory.getLogger().warn("[PluginIconManager] 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 source, String id, Dimension dimension, IconType type) {
String cacheKey = genCacheKey(source, id, dimension, type);
HashMap<String, WeakReference<Icon>> sourceCache = CACHE.getOrDefault(cacheKey, new HashMap<>(64));
final WeakReference<Icon> reference = sourceCache.get(cacheKey);
I icon = reference != null ? (I) reference.get() : null;
if (icon == null) {
IconSet set = SOURCE_ICON_MAPS.get(source);
if (set == null) {
return icon;
}
Icon f = set.findIcon(id, dimension, type);
if (f != null) {
icon = (I) f;
sourceCache.put(cacheKey, new WeakReference<>(icon));
CACHE.put(source, sourceCache);
}
}
return icon;
}
/**
* 生成缓存key
*
* @param id id
* @param dimension 尺寸
* @param type 图标类型
* @return 缓存key
*/
public static @NotNull String genCacheKey(String source, String id, Dimension dimension, IconType type) {
if (DEFAULT_DIMENSION.equals(dimension)) {
return source + "_" + id + "_" + type;
}
return source + "_" + 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, String source) {
IconSet set = SOURCE_ICON_MAPS.get(source);
if (set != null && set.getIds().contains(id)) {
return true;
}
return false;
}
/**
* 清理所有缓存
*/
public static void clearCache() {
CACHE.clear();
}
/**
* 清楚指定插件缓存
* @param source 插件标识
*/
public static void clearCacheBySource(String source) {
CACHE.remove(source);
}
/**
* 查找灰化图标
*
* @param path 原始路径
* @return 灰化路径
*/
public static String findDisablePath(String path) {
int i = path.lastIndexOf('.');
return path.substring(0, i) + ICON_DISABLE_SUFFIX + path.substring(i);
}
}

136
designer-base/src/main/java/com/fine/theme/icon/plugin/PluginIconSet.java

@ -0,0 +1,136 @@
package com.fine.theme.icon.plugin;
import com.fine.theme.icon.AbstractIconSet;
import com.fine.theme.icon.IconManager;
import com.fine.theme.icon.IconType;
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;
/**
* 插件图标集
*
* @author lemon
* @since
* Created on 2024/08/20
*/
public class PluginIconSet extends AbstractIconSet {
private String base;
public PluginIconSet(PluginUrlIconResource resource, Map<String, String> iconId2Path) {
addIconWithMap(iconId2Path);
if (resource.getPath() == null) {
return;
}
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());
}
}
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);
}
}
private void dealWithIconString(String key, String value) {
if (IconManager.isSvgIcon(value)) {
// 默认字符串提供正常图和灰化图
addIcon(new PluginSvgIconSource(key,
base + value,
IconManager.findDisablePath(base + value),
null
));
} else if (IconManager.isImageIcon(value)) {
addIcon(new PluginImageIconSource(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 PluginSvgIconSource(key,
base + normalPath,
StringUtils.isNotBlank(disablePath) ? base + disablePath : null,
StringUtils.isNotBlank(whitePath) ? base + whitePath : null
));
} else if (IconManager.isImageIcon(normalPath)) {
addIcon(new PluginImageIconSource(key,
base + normalPath,
StringUtils.isNotBlank(disablePath) ? base + disablePath : null,
StringUtils.isNotBlank(whitePath) ? base + whitePath : null
));
}
}
/**
* 根据 map 注册图标
* @param iconId2Path key: id, value: icon path
*/
public void addIconWithMap(Map<String, String> iconId2Path) {
for (Map.Entry<String, String> entry: iconId2Path.entrySet()) {
if (PluginIconManager.isSvgIcon(entry.getValue())) {
addIcon(new PluginSvgIconSource(entry.getKey(), entry.getValue()));
} else if (PluginIconManager.isImageIcon(entry.getValue())) {
addIcon(new PluginImageIconSource(entry.getKey(), entry.getValue()));
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PluginIconSet that = (PluginIconSet) o;
return Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hashCode(name);
}
}

129
designer-base/src/main/java/com/fine/theme/icon/plugin/PluginLazyIcon.java

@ -0,0 +1,129 @@
package com.fine.theme.icon.plugin;
import com.fine.theme.icon.DisabledIcon;
import com.fine.theme.icon.IconManager;
import com.fine.theme.icon.IconType;
import com.fine.theme.icon.Identifiable;
import com.fine.theme.icon.WhiteIcon;
import com.fr.design.fun.impl.AbstractLazyIconProvider;
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;
public class PluginLazyIcon implements Identifiable, DisabledIcon, WhiteIcon, Icon {
@NotNull
private final String id;
private final Dimension dimension;
private final IconType type;
private final String source;
/**
*
* @param source {@link AbstractLazyIconProvider#source()}
* @param id
*/
public PluginLazyIcon(@NotNull final String source, @NotNull final String id) {
this.id = id;
this.dimension = IconManager.DEFAULT_DIMENSION;
this.type = IconType.normal;
this.source = source;
}
public PluginLazyIcon(@NotNull final String source, @NotNull final String id, int side) {
this.id = id;
this.dimension = new Dimension(side, side);
this.type = IconType.normal;
this.source = source;
}
public PluginLazyIcon(@NotNull final String source, @NotNull final String id, @NotNull Dimension dimension) {
this.id = id;
this.dimension = dimension;
this.type = IconType.normal;
this.source = source;
}
private PluginLazyIcon(@NotNull final String source, @NotNull final String id, @NotNull IconType type) {
this.id = id;
this.dimension = IconManager.DEFAULT_DIMENSION;
this.type = type;
this.source = source;
}
public PluginLazyIcon(@NotNull final String source, @NotNull final String id, @NotNull Dimension dimension, @NotNull IconType type) {
this.id = id;
this.dimension = dimension;
this.type = type;
this.source = source;
}
@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 PluginIconManager.getIcon(source, getId(), dimension, type);
}
/**
* 创建一份灰化图标
*
* @return 灰化图标
*/
@NotNull
@Override
public Icon disabled() {
return new PluginLazyIcon(source, getId(), dimension, IconType.disable);
}
/**
* 创建一份白化图标
*
* @return 白化图标
*/
@NotNull
@Override
public Icon white() {
return new PluginLazyIcon(source, getId(), dimension, IconType.white);
}
@Override
public String toString() {
return new StringJoiner(", ", PluginLazyIcon.class.getSimpleName() + "[", "]")
.add("source='" + source + "'")
.add("id='" + id + "'")
.add("size=" + "[w=" + scale(dimension.width) + ",h=" + scale(dimension.height) + "]")
.add("type=" + type)
.toString();
}
}

68
designer-base/src/main/java/com/fr/design/fun/LazyIconProvider.java

@ -0,0 +1,68 @@
package com.fr.design.fun;
import com.fr.stable.fun.mark.Mutable;
import java.util.Map;
/**
* 插件图标适配接口
*
* @author lemon
* @since
* Created on
*/
public interface LazyIconProvider extends Mutable {
String MARK_STRING = "LazyIconProvider";
int CURRENT_LEVEL = 1;
/**
* 自定义icon 来源标识
*
* @return 来源标识
*/
String source();
/**
* json 文件路径
*
* @return 图标注册 json 路径
*/
String jsonPath();
/**
* 图标所属主题分类
*
* @return 主题类别
*/
THEME themeCategory();
/**
* 构建需要注册的图标 key: id, value: icon path
* @return map
*/
Map<String, String> iconId2Path();
/**
* 图标主题
*/
enum THEME {
/**
* light_icon
*/
LIGHT_ICON("light_icon"),
/**
* dark_icon
*/
DARK_ICON("dark_icon")
;
final String category;
THEME(String category) {
this.category = category;
}
}
}

71
designer-base/src/main/java/com/fr/design/fun/impl/AbstractLazyIconProvider.java

@ -0,0 +1,71 @@
package com.fr.design.fun.impl;
import com.fr.design.fun.LazyIconProvider;
import com.fr.stable.fun.impl.AbstractProvider;
import com.fr.stable.fun.mark.API;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Map;
/**
* 插件图标 LazyIcon 加载适配抽象类
*
* @author lemon
* @since
* Created on
*/
@API(level = LazyIconProvider.CURRENT_LEVEL)
public class AbstractLazyIconProvider extends AbstractProvider implements LazyIconProvider {
/**
* 当前接口的API等级,用于判断是否需要升级插件
* @return API等级
*/
@Override
public int currentAPILevel() {
return CURRENT_LEVEL;
}
/**
* 区分插件
*
* @return 插件 id
*/
@Override
public String source() {
throw new RuntimeException("source is blank");
}
/**
* 通过 json 注册图标
*
* @return 图标注册 json 路径
*/
@Override
public String jsonPath() {
return null;
}
/**
* 图标主题 {@link THEME}
*
* @return 主题类别
*/
@Override
public THEME themeCategory() {
return null;
}
/**
* 直接注册图标key icon id, value icon path
*
* @return map
*/
@Override
public Map<String, String> iconId2Path() {
return null;
}
}
Loading…
Cancel
Save