lemon
3 months ago
11 changed files with 142 additions and 601 deletions
@ -1,223 +0,0 @@
|
||||
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 com.fr.nx.app.web.out.widget.utils.CollectionUtils; |
||||
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.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
/** |
||||
* 管理插件图标集 |
||||
* |
||||
* @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, List<IconSet>> SOURCE_ICON_MAPS = new ConcurrentHashMap<>(2); |
||||
private static final Map<String, HashMap<String, WeakReference<Icon>>> CACHE = new ConcurrentHashMap<>(); |
||||
|
||||
|
||||
/** |
||||
* 获取图标集 |
||||
* |
||||
* @param id 图标集ID |
||||
* @return 图标集 |
||||
*/ |
||||
public static IconSet getSet(String source, String id) { |
||||
List<IconSet> sets = SOURCE_ICON_MAPS.get(source); |
||||
|
||||
for (IconSet set : sets) { |
||||
if (set != null && set.getId().equals(id)) { |
||||
return set; |
||||
} |
||||
} |
||||
throw new IconException("[PluginIconManager] Can not find icon set by id: " + id); |
||||
} |
||||
|
||||
/** |
||||
* 添加图标集 |
||||
* |
||||
* @param source 插件 |
||||
* @param sets 图标集 |
||||
*/ |
||||
public static void addSet(@NotNull String source, @NotNull List<IconSet> sets) { |
||||
if (SOURCE_ICON_MAPS.containsKey(source)) { |
||||
FineLoggerFactory.getLogger().warn("[PluginIconManager] plugin:{} icon set already exists: " + source); |
||||
} |
||||
SOURCE_ICON_MAPS.put(source, sets); |
||||
clearCacheBySource(source); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 更新指定插件图标集 |
||||
* |
||||
* @param source 插件 |
||||
* @param sets 图标集 |
||||
*/ |
||||
public static void updateSet(@NotNull String source, @NotNull List<IconSet> sets) { |
||||
removeSet(source); |
||||
addSet(source, sets); |
||||
} |
||||
|
||||
/** |
||||
* 删除指定插件图标集 |
||||
* |
||||
* @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) { |
||||
List<IconSet> sets = SOURCE_ICON_MAPS.get(source); |
||||
if (CollectionUtils.isEmpty(sets)) { |
||||
return icon; |
||||
} |
||||
for (IconSet set : sets) { |
||||
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) { |
||||
List<IconSet> sets = SOURCE_ICON_MAPS.get(source); |
||||
for (IconSet set : sets) { |
||||
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); |
||||
} |
||||
} |
@ -1,121 +1,22 @@
|
||||
package com.fine.theme.icon.plugin; |
||||
|
||||
import com.fine.theme.icon.AbstractIconSet; |
||||
import com.fine.theme.icon.IconType; |
||||
import com.fine.theme.icon.JsonIconSet; |
||||
import com.fine.theme.icon.UrlIconResource; |
||||
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 图标集 |
||||
* json 格式参考 fine_light.icon.json |
||||
* 为了保证 插件之间,插件与设计器 icon id 的唯一性,icons key 值规则参考 {@link PluginListIconSet} |
||||
* |
||||
* @author lemon |
||||
* @since |
||||
* Created on 2024/08/20 |
||||
*/ |
||||
public class PluginJsonIconSet extends AbstractIconSet { |
||||
|
||||
private String base; |
||||
|
||||
public PluginJsonIconSet(UrlIconResource resource) { |
||||
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 (PluginIconManager.isSvgIcon(value)) { |
||||
// 默认字符串提供正常图和灰化图
|
||||
addIcon(new SvgIconSource(key, |
||||
base + value, |
||||
PluginIconManager.findDisablePath(base + value), |
||||
null |
||||
)); |
||||
} else if (PluginIconManager.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 (PluginIconManager.isSvgIcon(normalPath)) { |
||||
addIcon(new SvgIconSource(key, |
||||
base + normalPath, |
||||
StringUtils.isNotBlank(disablePath) ? base + disablePath : null, |
||||
StringUtils.isNotBlank(whitePath) ? base + whitePath : null |
||||
)); |
||||
} else if (PluginIconManager.isImageIcon(normalPath)) { |
||||
addIcon(new ImageIconSource(key, |
||||
base + normalPath, |
||||
StringUtils.isNotBlank(disablePath) ? base + disablePath : null, |
||||
StringUtils.isNotBlank(whitePath) ? base + whitePath : null |
||||
)); |
||||
} |
||||
} |
||||
public class PluginJsonIconSet extends JsonIconSet { |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) { |
||||
return true; |
||||
} |
||||
if (o == null || getClass() != o.getClass()) { |
||||
return false; |
||||
} |
||||
PluginJsonIconSet that = (PluginJsonIconSet) o; |
||||
return Objects.equals(name, that.name); |
||||
public PluginJsonIconSet(String id, UrlIconResource resource) { |
||||
super(resource); |
||||
this.name = id; |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hashCode(name); |
||||
} |
||||
} |
||||
|
@ -1,137 +0,0 @@
|
||||
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 com.fr.stable.AssistUtils; |
||||
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 lemon |
||||
* @since |
||||
* Created on 2024/08/23 |
||||
*/ |
||||
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#pluginId()} |
||||
* @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(); |
||||
} |
||||
} |
@ -0,0 +1,57 @@
|
||||
package com.fine.theme.icon.plugin; |
||||
|
||||
import com.fine.theme.icon.AbstractIconSet; |
||||
import com.fine.theme.icon.IconManager; |
||||
import com.fine.theme.icon.img.ImageIconSource; |
||||
import com.fine.theme.icon.svg.SvgIconSource; |
||||
|
||||
import java.util.List; |
||||
import java.util.Objects; |
||||
|
||||
/** |
||||
* 插件 map 图标集 |
||||
* 为了保证 插件之间,插件与设计器 icon id 的唯一性,以图标 path 为 id 进行注册 |
||||
* |
||||
* @author lemon |
||||
* @since |
||||
* Created on 2024/08/20 |
||||
*/ |
||||
public class PluginListIconSet extends AbstractIconSet { |
||||
|
||||
public PluginListIconSet(String id, List<String> icons) { |
||||
this.name = id; |
||||
addIconWithList(icons); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 根据 list 注册图标 |
||||
* @param icons icon path list |
||||
*/ |
||||
public void addIconWithList(List<String> icons) { |
||||
for (String path : icons) { |
||||
if (IconManager.isSvgIcon(path)) { |
||||
addIcon(new SvgIconSource(path, path)); |
||||
} else if (IconManager.isImageIcon(path)) { |
||||
addIcon(new ImageIconSource(path, path)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) { |
||||
return true; |
||||
} |
||||
if (o == null || getClass() != o.getClass()) { |
||||
return false; |
||||
} |
||||
PluginListIconSet that = (PluginListIconSet) o; |
||||
return Objects.equals(name, that.name); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hashCode(name); |
||||
} |
||||
} |
@ -1,54 +0,0 @@
|
||||
package com.fine.theme.icon.plugin; |
||||
|
||||
import com.fine.theme.icon.AbstractIconSet; |
||||
import com.fine.theme.icon.img.ImageIconSource; |
||||
import com.fine.theme.icon.svg.SvgIconSource; |
||||
|
||||
import java.util.Map; |
||||
import java.util.Objects; |
||||
|
||||
/** |
||||
* 插件 map 图标集 |
||||
* |
||||
* @author lemon |
||||
* @since |
||||
* Created on 2024/08/20 |
||||
*/ |
||||
public class PluginMapIconSet extends AbstractIconSet { |
||||
|
||||
public PluginMapIconSet(Map<String, String> iconId2Path) { |
||||
addIconWithMap(iconId2Path); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 根据 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 SvgIconSource(entry.getKey(), entry.getValue())); |
||||
} else if (PluginIconManager.isImageIcon(entry.getValue())) { |
||||
addIcon(new ImageIconSource(entry.getKey(), entry.getValue())); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) { |
||||
return true; |
||||
} |
||||
if (o == null || getClass() != o.getClass()) { |
||||
return false; |
||||
} |
||||
PluginMapIconSet that = (PluginMapIconSet) o; |
||||
return Objects.equals(name, that.name); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hashCode(name); |
||||
} |
||||
} |
Loading…
Reference in new issue