Browse Source

Pull request #15963: REPORT-145112 feat: 设计器交互动画 遮罩/弹出消息/折叠面板

Merge in DESIGN/design from ~LEVY.XIE/design:fbp/feature to fbp/feature

* commit '38abeab375683495e413fbf0d7448084e97ecbf7':
  无jira 解决冲突漏提交
  REPORT-145112 代码质量优化
  REPORT-145112 feat:设计器交互动画-弹出消息
  REPORT-145112 feat:设计器交互动画-折叠面板
  REPORT-145112 feat:设计器交互动画-遮罩
fbp/feature
Levy.Xie-解安森 1 month ago
parent
commit
65ed1f9841
  1. 242
      designer-base/src/main/java/com/fine/component/popup/ComponentLayer.java
  2. 59
      designer-base/src/main/java/com/fine/component/popup/GlassPaneChild.java
  3. 139
      designer-base/src/main/java/com/fine/component/popup/GlassPopup.java
  4. 292
      designer-base/src/main/java/com/fine/component/popup/GlassPopupManager.java
  5. 38
      designer-base/src/main/java/com/fine/component/popup/ImageChild.java
  6. 142
      designer-base/src/main/java/com/fine/component/popup/ProgressChild.java
  7. 91
      designer-base/src/main/java/com/fine/component/popup/WindowSnapshots.java
  8. 76
      designer-base/src/main/java/com/fine/component/toast/ToastMsgHolder.java
  9. 566
      designer-base/src/main/java/com/fine/component/toast/ToastMsgManager.java
  10. 95
      designer-base/src/main/java/com/fine/component/toast/ToastPane.java
  11. 125
      designer-base/src/main/java/com/fine/theme/light/ui/ShadowBorder.java
  12. 370
      designer-base/src/main/java/com/fine/theme/light/ui/ToastPanelUI.java
  13. 6
      designer-base/src/main/java/com/fine/theme/utils/FineClientProperties.java
  14. 53
      designer-base/src/main/java/com/fine/theme/utils/FineUIUtils.java
  15. 103
      designer-base/src/main/java/com/fine/theme/utils/GlassLayerLoader.java
  16. 220
      designer-base/src/main/java/com/fine/theme/utils/ShadowRenderer.java
  17. 7
      designer-base/src/main/java/com/fr/design/EnvChangeEntrance.java
  18. 6
      designer-base/src/main/java/com/fr/design/file/DefaultTemplateTreeDefineProcessor.java
  19. 84
      designer-base/src/main/java/com/fr/design/foldablepane/UIExpandablePane.java
  20. 4
      designer-base/src/main/java/com/fr/design/mainframe/ComponentReuseNotifyUtil.java
  21. 10
      designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeProfilePane.java
  22. 1
      designer-base/src/main/java/com/fr/design/mainframe/toast/DesignerToastMsgUtil.java
  23. 4
      designer-base/src/main/java/com/fr/design/style/background/image/ImageFileChooser.java
  24. 22
      designer-base/src/main/java/com/fr/start/ServerStarter.java
  25. 5
      designer-base/src/main/java/com/fr/start/server/DesignEmbedHelper.java
  26. 1
      designer-base/src/main/java/com/fr/start/server/FineEmbedServerMonitor.java
  27. 1
      designer-base/src/main/java/com/fr/startup/ui/StartupLoadingPanel.java
  28. 38
      designer-base/src/main/java/com/fr/startup/ui/StartupPageWindow.java
  29. BIN
      designer-base/src/main/resources/com/fine/component/pop/loading.gif
  30. 1
      designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLaf.properties
  31. 62
      designer-base/src/test/java/com/fr/design/gui/storybook/components/GlassLoaderStoryBoard.java
  32. 58
      designer-base/src/test/java/com/fr/design/gui/storybook/components/ToastStoryBoard.java
  33. BIN
      designer-base/src/test/resources/com/fr/design/gui/storybook/components/loadingBar.gif
  34. 7
      designer-realize/src/main/java/com/fr/design/actions/replace/ui/ITReplaceMainDialog.java

242
designer-base/src/main/java/com/fine/component/popup/ComponentLayer.java

@ -0,0 +1,242 @@
package com.fine.component.popup;
import com.fine.theme.utils.FineUIUtils;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.CubicBezierEasing;
import com.formdev.flatlaf.util.UIScale;
import net.miginfocom.swing.MigLayout;
import javax.swing.JPanel;
import java.awt.AlphaComposite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.Stack;
/**
* 遮罩功能渐显遮罩层
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/12/12
*/
public class ComponentLayer extends JPanel {
private GlassPaneChild component;
private GlassPaneChild nextComponent;
private Animator animator;
private float animate;
private boolean showSnapshot;
private boolean simpleSnapshot;
private Image image;
private Image nextImage;
private Stack<GlassPaneChild> componentStack;
private boolean push;
private void pushStack(GlassPaneChild component) {
if (componentStack == null) {
componentStack = new Stack<>();
}
componentStack.push(component);
}
public ComponentLayer(GlassPaneChild component) {
this.component = component;
init();
}
private void init() {
MigLayout layout = new MigLayout("insets 0,fill", "fill", "fill");
setLayout(layout);
if (component.getRoundBorder() > 0) {
setOpaque(false);
component.setOpaque(false);
}
add(component);
component.setVisible(false);
}
private void initAnimator() {
animator = new Animator(350, new Animator.TimingTarget() {
@Override
public void timingEvent(float v) {
animate = v;
repaint();
}
@Override
public void begin() {
nextImage = FineUIUtils.createImage(nextComponent);
}
@Override
public void end() {
showSnapshot = false;
component = nextComponent;
component.popupShow();
nextComponent = null;
animate = 0;
component.setVisible(true);
if (nextImage != null) {
nextImage.flush();
}
}
});
animator.setInterpolator(CubicBezierEasing.EASE);
}
private void startAnimate() {
if (animator == null) {
initAnimator();
}
if (animator.isRunning()) {
animator.stop();
}
animate = 0;
animator.start();
}
/**
* 放入组件
*
* @param component 组件
*/
public void pushComponent(GlassPaneChild component) {
component.onPush();
pushStack(this.component);
push = true;
showComponent(component);
}
/**
* 弹出组件
*/
public void popComponent() {
if (!componentStack.isEmpty()) {
GlassPaneChild component = componentStack.pop();
component.onPop();
push = false;
showComponent(component);
}
}
private void showComponent(GlassPaneChild component) {
showSnapshot = true;
this.nextComponent = component;
if (!component.isTransparent()) {
image = FineUIUtils.createImage(this.component);
}
if (component.getRoundBorder() > 0) {
setOpaque(false);
component.setOpaque(false);
}
component.setVisible(false);
remove(this.component);
add(component);
revalidate();
startAnimate();
}
/**
* 显示遮罩
*/
public void showSnapshot() {
if (animator != null && animator.isRunning()) {
animator.stop();
}
simpleSnapshot = true;
doLayout();
if (!component.isTransparent()) {
image = FineUIUtils.createImage(component, component.getRoundBorder());
}
component.setVisible(false);
}
/**
* 隐藏遮罩
*/
public void hideSnapshot() {
showSnapshot = false;
simpleSnapshot = false;
component.setVisible(true);
if (image != null) {
image.flush();
}
}
@Override
protected void paintComponent(Graphics g) {
if (!component.isTransparent()) {
if (!isOpaque() && component.getRoundBorder() > 0) {
Graphics2D g2 = (Graphics2D) g.create();
FlatUIUtils.setRenderingHints(g2);
g2.setColor(getBackground());
int arc = UIScale.scale(component.getRoundBorder());
FlatUIUtils.paintComponentBackground(g2, 0, 0, getWidth(), getHeight(), 0, arc);
g2.dispose();
}
}
super.paintComponent(g);
}
@Override
public void paint(Graphics g) {
if (simpleSnapshot) {
g.drawImage(image, 0, 0, null);
} else if (showSnapshot) {
int width = getWidth();
int height = getHeight();
if (width > 0 && height > 0) {
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = bufferedImage.createGraphics();
FlatUIUtils.setRenderingHints(g2);
int arc = UIScale.scale(component.getRoundBorder());
g2.setColor(getBackground());
FlatUIUtils.paintComponentBackground(g2, 0, 0, width, height, 0, arc);
if (image != null) {
paintImage(g2);
}
if (nextImage != null) {
paintNextImage(g2);
}
g2.dispose();
g.drawImage(bufferedImage, 0, 0, null);
}
} else {
super.paint(g);
}
}
private void paintNextImage(Graphics2D g2) {
int w = nextImage.getWidth(null);
double x;
if (push) {
x = getWidth() - (w * animate);
} else {
x = -getWidth() + (w * animate);
}
g2.setComposite(AlphaComposite.SrcAtop.derive(animate));
g2.drawImage(nextImage, (int) x, 0, null);
}
private void paintImage(Graphics2D g2) {
int w = image.getWidth(null);
double x;
if (push) {
x = -w * animate;
} else {
x = w * animate;
}
g2.setComposite(AlphaComposite.SrcAtop.derive(1f - animate));
g2.drawImage(image, (int) x, 0, null);
}
public GlassPaneChild getComponent() {
return component;
}
}

59
designer-base/src/main/java/com/fine/component/popup/GlassPaneChild.java

@ -0,0 +1,59 @@
package com.fine.component.popup;
import com.formdev.flatlaf.ui.FlatUIUtils;
import javax.swing.JPanel;
/**
* 遮罩加载面板组件
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/11/28
*/
public class GlassPaneChild extends JPanel {
protected boolean transparent;
public int getRoundBorder() {
return FlatUIUtils.getUIInt("Component.arc", 6);
}
/**
* 组件是否透明
*
* @return 组件透明
*/
protected boolean isTransparent() {
return transparent;
}
/**
* 放入组件时的回调方法
*/
public void onPush() {
}
/**
* 弹出组件时的回调方法
*/
public void onPop() {
}
/**
* 显示弹窗组件时的回调方法
*/
public void popupShow() {
}
/**
* 关闭窗口时的回调方法
*/
public void onClose() {
}
}

139
designer-base/src/main/java/com/fine/component/popup/GlassPopup.java

@ -0,0 +1,139 @@
package com.fine.component.popup;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.CubicBezierEasing;
import net.miginfocom.swing.MigLayout;
import javax.swing.JComponent;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
/**
* 遮罩功能: 弹出面板组件
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/12/12
*/
public class GlassPopup extends JComponent {
private final GlassPopupManager parent;
private final ComponentLayer componentLayer;
private Animator animator;
private MigLayout layout;
private float animate;
private boolean show;
public GlassPopup(GlassPopupManager parent, GlassPaneChild component) {
this.parent = parent;
this.componentLayer = new ComponentLayer(component);
init();
initAnimator();
}
private void init() {
layout = new MigLayout();
setLayout(layout);
add(componentLayer, getLayout(parent.layerPane, 0));
}
private void initAnimator() {
animator = new Animator(400, new Animator.TimingTarget() {
@Override
public void timingEvent(float fraction) {
if (show) {
animate = fraction;
} else {
animate = 1f - fraction;
}
float f = Math.round(animate * 100) / 100f;
String lc = getLayout(parent.layerPane, f);
layout.setComponentConstraints(componentLayer, lc);
repaint();
revalidate();
}
@Override
public void begin() {
prepareAnimation();
}
@Override
public void end() {
finishAnimation();
}
});
animator.setInterpolator(CubicBezierEasing.EASE);
}
private void prepareAnimation() {
componentLayer.showSnapshot();
parent.windowSnapshots.createSnapshot();
parent.contentPane.setVisible(false);
}
private void finishAnimation() {
componentLayer.hideSnapshot();
if (show) {
componentLayer.getComponent().popupShow();
} else {
parent.removePopup(GlassPopup.this);
}
parent.contentPane.setVisible(true);
parent.windowSnapshots.removeSnapshot();
}
public void setShowPopup(boolean show) {
if (this.show != show) {
// 前置动画未结束时,先关闭前置动画
if (animator.isRunning()) {
animator.stop();
}
this.show = show;
animator.start();
if (show) {
setFocusCycleRoot(true);
} else {
uninstallListener();
}
}
}
private void uninstallListener() {
if (getMouseListeners().length != 0) {
removeMouseListener(getMouseListeners()[0]);
}
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setComposite(AlphaComposite.SrcOver.derive(animate * 0.5f));
g2.setColor(background());
g2.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
g2.setComposite(AlphaComposite.SrcOver.derive(animate));
super.paintComponent(g);
}
protected Component getComponent() {
return componentLayer.getComponent();
}
protected ComponentLayer getComponentLayer() {
return componentLayer;
}
private Color background() {
return FlatLaf.isLafDark() ? new Color(50, 50, 50) : new Color(20, 20, 20);
}
private String getLayout(Component parent, float animate) {
float an = 20f / 600;
float space = 0.5f + an - (animate * an);
return "pos 0.5al " + space + "al,height ::100%-20";
}
}

292
designer-base/src/main/java/com/fine/component/popup/GlassPopupManager.java

@ -0,0 +1,292 @@
package com.fine.component.popup;
import javax.swing.JLayeredPane;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* 遮罩功能: 遮罩面板管理器
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/12/12
*/
public class GlassPopupManager {
private static GlassPopupManager instance;
protected WindowSnapshots windowSnapshots;
protected JLayeredPane layerPane;
protected Container contentPane;
private GlassPopupManager() {
init();
}
private void init() {
layerPane = new JLayeredPane();
layerPane.setLayout(new CardLayout());
}
/**
* 添加并显示弹窗组件
*
* @param component 组件
* @param name 组件名
*/
protected void addAndShowPopup(GlassPaneChild component, String name) {
GlassPopup popup = new GlassPopup(this, component);
instance.layerPane.applyComponentOrientation(instance.contentPane.getComponentOrientation());
popup.applyComponentOrientation(instance.contentPane.getComponentOrientation());
if (name != null) {
popup.setName(name);
}
layerPane.setLayer(popup, JLayeredPane.DRAG_LAYER);
layerPane.add(popup, 0);
popup.setVisible(true);
popup.setShowPopup(true);
if (!layerPane.isVisible()) {
layerPane.setVisible(true);
}
layerPane.grabFocus();
}
private void updateLayout() {
for (Component com : layerPane.getComponents()) {
com.revalidate();
}
}
/**
* 设置遮罩父窗口
*
* @param container 遮罩父容器
*/
public static void install(Container container) {
instance = new GlassPopupManager();
Window window;
if (container instanceof Window) {
window = (Window) container;
} else {
window = SwingUtilities.getWindowAncestor(container);
}
instance.windowSnapshots = new WindowSnapshots(window);
window.addWindowStateListener(new WindowAdapter() {
@Override
public void windowStateChanged(WindowEvent e) {
SwingUtilities.invokeLater(() -> {
instance.updateLayout();
});
}
});
JRootPane rootPane = SwingUtilities.getRootPane(container);
instance.contentPane = rootPane.getContentPane();
rootPane.setGlassPane(instance.layerPane);
}
/**
* 显示组件
*
* @param component 组件
* @param name 组件名
*/
public static void showPopup(GlassPaneChild component, String name) {
if (component.getMouseListeners().length == 0) {
component.addMouseListener(new MouseAdapter() {
});
}
instance.addAndShowPopup(component, name);
}
/**
* 显示组件
*
* @param component 组件
*/
public static void showPopup(GlassPaneChild component) {
showPopup(component, null);
}
/**
* 压栈放入弹窗组件
*
* @param component 组件
* @param name 组件名
*/
public static void push(GlassPaneChild component, String name) {
for (Component com : instance.layerPane.getComponents()) {
if (com.getName() != null && com.getName().equals(name)) {
if (com instanceof GlassPopup) {
GlassPopup popup = (GlassPopup) com;
popup.getComponentLayer().pushComponent(component);
break;
}
}
}
}
/**
* 出栈弹出弹窗组件
*
* @param name 组件名
*/
public static void pop(String name) {
for (Component com : instance.layerPane.getComponents()) {
if (com.getName() != null && com.getName().equals(name)) {
if (com instanceof GlassPopup) {
GlassPopup popup = (GlassPopup) com;
popup.getComponentLayer().popComponent();
break;
}
}
}
}
/**
* 出栈弹出弹窗组件
*
* @param component 组件
*/
public static void pop(Component component) {
for (Component com : instance.layerPane.getComponents()) {
if (com instanceof GlassPopup) {
GlassPopup popup = (GlassPopup) com;
if (popup.getComponent() == component) {
popup.getComponentLayer().popComponent();
}
}
}
}
/**
* 基于序号关闭弹窗组件
*
* @param index 序号
*/
public static void closePopup(int index) {
index = instance.layerPane.getComponentCount() - 1 - index;
if (index >= 0 && index < instance.layerPane.getComponentCount()) {
if (instance.layerPane.getComponent(index) instanceof GlassPopup) {
GlassPopup popup = (GlassPopup) instance.layerPane.getComponent(index);
popup.setShowPopup(false);
}
}
}
/**
* 基于名称关闭组件
*
* @param name 组件名称
*/
public static void closePopup(String name) {
for (Component com : instance.layerPane.getComponents()) {
if (com.getName() != null && com.getName().equals(name)) {
if (com instanceof GlassPopup) {
GlassPopup popup = (GlassPopup) com;
popup.setShowPopup(false);
}
}
}
}
/**
* 关闭组件
*
* @param component 组件
*/
public static void closePopup(Component component) {
for (Component com : instance.layerPane.getComponents()) {
if (com instanceof GlassPopup) {
GlassPopup popup = (GlassPopup) com;
if (popup.getComponent() == component) {
popup.setShowPopup(false);
}
}
}
}
/**
* 关闭最后一个组件
*/
public static void closePopupLast() {
closePopup(getPopupCount() - 1);
}
/**
* 关闭全部组件
*/
public static void closePopupAll() {
for (Component com : instance.layerPane.getComponents()) {
if (com instanceof GlassPopup) {
GlassPopup popup = (GlassPopup) com;
popup.setShowPopup(false);
}
}
}
/**
* 基于组件名判断组件是否显示
*
* @param name 组件名
* @return 是否显示
*/
public static boolean isShowing(String name) {
boolean act = false;
for (Component com : instance.layerPane.getComponents()) {
if (com.getName() != null && com.getName().equals(name)) {
act = true;
break;
}
}
return act;
}
/**
* 判断组件是否显示
*
* @param component 组件
* @return 是否显示
*/
public static boolean isShowing(Component component) {
boolean act = false;
for (Component com : instance.layerPane.getComponents()) {
if (com instanceof GlassPopup) {
GlassPopup popup = (GlassPopup) com;
if (popup.getComponent() == component) {
act = true;
break;
}
}
}
return act;
}
/**
* 获取弹窗组件数量
*
* @return 弹窗数量
*/
public static int getPopupCount() {
return instance.layerPane.getComponentCount();
}
/**
* 移除组件
*
* @param popup 弹窗组件
*/
protected synchronized void removePopup(Component popup) {
layerPane.remove(popup);
if (layerPane.getComponentCount() == 0) {
layerPane.setVisible(false);
}
}
}

38
designer-base/src/main/java/com/fine/component/popup/ImageChild.java

@ -0,0 +1,38 @@
package com.fine.component.popup;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.fr.design.gui.ilable.UILabel;
import javax.swing.ImageIcon;
import java.awt.BorderLayout;
import java.util.Objects;
/**
* 图片遮罩层支撑静态图片及动态GIF
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/12/20
*/
public class ImageChild extends GlassPaneChild {
private static final String DEFAULT_LOADING_PATH = "com/fine/component/pop/loading.gif";
public ImageChild() {
this(DEFAULT_LOADING_PATH);
}
public ImageChild(String imgPath) {
this(imgPath, true);
}
public ImageChild(String imgPath, boolean transparent) {
this.transparent = transparent;
setLayout(new BorderLayout());
setBorder(new ScaledEmptyBorder(8, 8, 8, 8));
ImageIcon icon = new ImageIcon(Objects.requireNonNull(getClass().getClassLoader().getResource(imgPath)));
add(new UILabel(icon), BorderLayout.CENTER);
}
}

142
designer-base/src/main/java/com/fine/component/popup/ProgressChild.java

@ -0,0 +1,142 @@
package com.fine.component.popup;
import com.fine.theme.utils.FineUIScale;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.fr.concurrent.NamedThreadFactory;
import com.fr.design.locale.impl.SupportLocaleImpl;
import com.fr.design.ui.util.UIUtil;
import com.fr.design.utils.DesignUtils;
import com.fr.general.FRFont;
import com.fr.general.locale.LocaleCenter;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.plaf.ColorUIResource;
import java.awt.BorderLayout;
import java.awt.Font;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static com.fine.swing.ui.layout.Layouts.cell;
import static com.fine.swing.ui.layout.Layouts.column;
import static com.fine.swing.ui.layout.Layouts.flex;
import static com.fine.swing.ui.layout.Layouts.row;
import static com.fine.theme.utils.FineUIScale.scale;
/**
* 进度条遮罩组件
*
* @author Levy.Xie
* @since 11.0
* Created on 2024/12/13
*/
public class ProgressChild extends GlassPaneChild {
private int progress;
protected JLabel text;
protected JProgressBar progressBar;
private int step = 10;
private static final int STEP_HEARTBEAT = 40;
private static final int FONT_RGB = 333334;
private static final int FONT_SIZE = 14;
private static final String FONT_NAME = "Dialog";
public ProgressChild(String text) {
this(text, DesignUtils.getDefaultGUIFont()
.applySize(scale(FONT_SIZE))
.applyForeground(new ColorUIResource(FONT_RGB)));
}
public ProgressChild(String text, Font font) {
initComponents(text, font);
initLayout();
initScheduler();
}
private void initComponents(String text, Font font) {
this.text = new JLabel(text);
this.text.setFont(font);
//由于默认值的字体不支持韩文,所以要对韩文单独生成字体
LocaleCenter.buildAction(() -> {
FRFont frFont = FRFont.getInstance().applySize(FONT_SIZE)
.applyForeground(new ColorUIResource(FONT_RGB)).applyName(FONT_NAME);
this.text.setFont(frFont);
}, SupportLocaleImpl.SUPPORT_KOREA);
this.progressBar = new JProgressBar();
progressBar.setBorderPainted(false);
progressBar.setOpaque(false);
progressBar.setBorder(null);
progressBar.setMaximum(1000);
}
/**
* 设置进度条最大加载时间单位为s
*
* @param maxWait 最大等待时间
* @return 进度条遮罩层
*/
public ProgressChild setMaxWait(int maxWait) {
this.step = progressBar.getMaximum() * STEP_HEARTBEAT / (maxWait * 1000);
return this;
}
private void initLayout() {
setLayout(new BorderLayout());
setPreferredSize(FineUIScale.createScaleDimension(400, 100));
add(column(10,
cell(progressBar).weight(1), row(flex(), cell(text), flex()).weight(1.5)
).with(it -> it.setBorder(new ScaledEmptyBorder(30, 30, 20, 30))).getComponent(),
BorderLayout.CENTER);
}
private void initScheduler() {
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1,
new NamedThreadFactory(getClass().getSimpleName()));
scheduler.scheduleWithFixedDelay(() -> {
if (isComplete() && !scheduler.isShutdown()) {
scheduler.shutdown();
return;
}
UIUtil.invokeLaterIfNeeded(() -> progressBar.setValue(incrementProgress()));
}, 0, STEP_HEARTBEAT, TimeUnit.MILLISECONDS);
}
/**
* 递增进度条
*
* @return 进度值
*/
public int incrementProgress() {
if (progress != progressBar.getMaximum()) {
progress += step;
}
return progress;
}
@Override
public void onClose() {
this.progress = progressBar.getMaximum();
}
/**
* 重置进度条
*/
public void reset() {
this.progress = 0;
}
/**
* 进度是否已结束
*
* @return 进度结束状态
*/
public boolean isComplete() {
return this.progress >= progressBar.getMaximum();
}
}

91
designer-base/src/main/java/com/fine/component/popup/WindowSnapshots.java

@ -0,0 +1,91 @@
package com.fine.component.popup;
import org.jetbrains.annotations.NotNull;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import java.awt.Graphics;
import java.awt.Window;
import java.awt.image.VolatileImage;
/**
* WindowSnapshots, 捕获 Window 的快照
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/12/12
*/
public class WindowSnapshots {
private final Window window;
private JComponent snapshotLayer;
private boolean inShowSnapshot;
public WindowSnapshots(Window window) {
this.window = window;
}
/**
* 创建窗口快照并将其显示为覆盖层
*/
public void createSnapshot() {
if (inShowSnapshot) {
return;
}
inShowSnapshot = true;
if ((window != null && window.isShowing())) {
VolatileImage snapshot = window.createVolatileImage(window.getWidth(), window.getHeight());
if (snapshot != null) {
JLayeredPane layeredPane = getLayeredPane();
if (layeredPane != null) {
layeredPane.paint(snapshot.getGraphics());
snapshotLayer = createSnapLayer(snapshot);
snapshotLayer.setSize(layeredPane.getSize());
layeredPane.add(snapshotLayer, Integer.valueOf(JLayeredPane.DRAG_LAYER + 1));
}
}
}
}
private static @NotNull JComponent createSnapLayer(VolatileImage snapshot) {
return new JComponent() {
@Override
public void paint(Graphics g) {
if (snapshot.contentsLost()) {
return;
}
g.drawImage(snapshot, 0, 0, null);
}
@Override
public void removeNotify() {
super.removeNotify();
snapshot.flush();
}
};
}
/**
* 移除窗口快照覆盖层
*/
public void removeSnapshot() {
if (snapshotLayer == null) {
return;
}
JLayeredPane layeredPane = getLayeredPane();
if (layeredPane != null) {
layeredPane.remove(snapshotLayer);
}
inShowSnapshot = false;
}
private JLayeredPane getLayeredPane() {
JRootPane rootPane = SwingUtilities.getRootPane(window);
if (rootPane != null) {
return rootPane.getLayeredPane();
}
return null;
}
}

76
designer-base/src/main/java/com/fine/component/toast/ToastMsgHolder.java

@ -0,0 +1,76 @@
package com.fine.component.toast;
import java.util.ArrayList;
import java.util.List;
/**
* 弹出消息容器
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/12/12
*/
public class ToastMsgHolder {
private final List<ToastMsgManager.ToastAnimation> lists = new ArrayList<>();
private final Object lock = new Object();
public ToastMsgManager.ToastAnimation getHold(ToastMsgManager.Location location) {
synchronized (lock) {
for (ToastMsgManager.ToastAnimation n : lists) {
if (n.getLocation() == location) {
return n;
}
}
return null;
}
}
/**
* 删除弹窗动画
*
* @param toastAnimation 弹窗动画
*/
public void removeHold(ToastMsgManager.ToastAnimation toastAnimation) {
synchronized (lock) {
lists.remove(toastAnimation);
}
}
/**
* 添加弹窗动画
*
* @param toastAnimation 弹窗动画
*/
public void hold(ToastMsgManager.ToastAnimation toastAnimation) {
synchronized (lock) {
lists.add(toastAnimation);
}
}
/**
* 清空弹窗动画
*/
public void clearHold() {
synchronized (lock) {
lists.clear();
}
}
/**
* 清空指定位置的弹窗动画
*
* @param location 弹窗位置
*/
public void clearHold(ToastMsgManager.Location location) {
synchronized (lock) {
for (int i = 0; i < lists.size(); i++) {
ToastMsgManager.ToastAnimation n = lists.get(i);
if (n.getLocation() == location) {
lists.remove(n);
i--;
}
}
}
}
}

566
designer-base/src/main/java/com/fine/component/toast/ToastMsgManager.java

@ -0,0 +1,566 @@
package com.fine.component.toast;
import com.fine.theme.utils.FineClientProperties;
import com.fine.theme.utils.FineUIUtils;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.UIScale;
import com.fr.design.mainframe.DesignerContext;
import javax.swing.JComponent;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import java.awt.Color;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* Toast Message Manager
* <!-- FlatLaf Property -->
* <p>
* Toast.outlineWidth int 0 (default)
* Toast.iconTextGap int 5 (default)
* Toast.closeButtonGap int 5 (default)
* Toast.arc int 20 (default)
* Toast.horizontalGap int 10 (default)
* <p>
* Toast.limit int -1 (default) -1 as unlimited
* Toast.duration long 2500 (default)
* Toast.animation int 200 (default)
* Toast.animationResolution int 5 (default)
* Toast.animationMove int 10 (default)
* Toast.minimumWidth int 50 (default)
* Toast.maximumWidth int -1 (default) -1 as not set
* <p>
* Toast.shadowColor Color
* Toast.shadowOpacity float 0.1f (default)
* Toast.shadowInsets Insets 0,0,6,6 (default)
* <p>
* Toast.outlineColor Color
* Toast.foreground Color
* Toast.background Color
* <p>
* Toast.success.outlineColor Color
* Toast.success.foreground Color
* Toast.success.background Color
* Toast.info.outlineColor Color
* Toast.info.foreground Color
* Toast.info.background Color
* Toast.warning.outlineColor Color
* Toast.warning.foreground Color
* Toast.warning.background Color
* Toast.error.outlineColor Color
* Toast.error.foreground Color
* Toast.error.background Color
* <p>
* Toast.frameInsets Insets 10,10,10,10 (default)
* Toast.margin Insets 8,8,8,8 (default)
* <p>
* Toast.showCloseButton boolean true (default)
* Toast.closeIconColor Color
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/12/10
*/
public class ToastMsgManager {
private static ToastMsgManager instance;
private Window window;
private final Map<Location, List<ToastAnimation>> lists = new HashMap<>();
private final ToastMsgHolder toastMsgHolder = new ToastMsgHolder();
private ComponentListener windowEvent;
private void installEvent(Window window) {
if (windowEvent == null && window != null) {
windowEvent = new ComponentAdapter() {
@Override
public void componentMoved(ComponentEvent e) {
move(window.getBounds());
}
@Override
public void componentResized(ComponentEvent e) {
move(window.getBounds());
}
};
}
if (this.window != null) {
this.window.removeComponentListener(windowEvent);
}
if (window != null) {
window.addComponentListener(windowEvent);
}
this.window = window;
}
public static ToastMsgManager getInstance() {
if (instance == null) {
instance = new ToastMsgManager();
}
return instance;
}
private int getCurrentShowCount(Location location) {
List<ToastAnimation> list = lists.get(location);
return list == null ? 0 : list.size();
}
private synchronized void move(Rectangle rectangle) {
for (Map.Entry<Location, List<ToastAnimation>> set : lists.entrySet()) {
for (int i = 0; i < set.getValue().size(); i++) {
ToastAnimation an = set.getValue().get(i);
if (an != null) {
an.move(rectangle);
}
}
}
}
public void setWindow(Window window) {
installEvent(window);
}
/**
* 弹窗: 成功提示
*
* @param message 文案
* @param window 父窗体
*/
public void success(String message, Window window) {
show(Type.SUCCESS, Location.TOP_CENTER, message, window);
}
/**
* 弹窗: 成功提示
*
* @param message 文案
*/
public void success(String message) {
show(Type.SUCCESS, Location.TOP_CENTER, message);
}
/**
* 弹窗: 信息提示
*
* @param message 文案
* @param window 父窗体
*/
public void info(String message, Window window) {
show(Type.INFO, Location.TOP_CENTER, message, window);
}
/**
* 弹窗: 信息提示
*
* @param message 文案
*/
public void info(String message) {
show(Type.INFO, Location.TOP_CENTER, message);
}
/**
* 弹窗: 警告提示
*
* @param message 文案
* @param window 父窗体
*/
public void warn(String message, Window window) {
show(Type.WARNING, Location.TOP_CENTER, message, window);
}
/**
* 弹窗: 警告提示
*
* @param message 文案
*/
public void warn(String message) {
show(Type.WARNING, Location.TOP_CENTER, message);
}
/**
* 弹窗: 错误提示
*
* @param message 文案
* @param window 父窗体
*/
public void error(String message, Window window) {
show(Type.ERROR, Location.TOP_CENTER, message, window);
}
/**
* 弹窗: 错误提示
*
* @param message 文案
*/
public void error(String message) {
show(Type.ERROR, Location.TOP_CENTER, message);
}
/**
* 弹窗: 支持自定义通知类型位置信息父窗体
*
* @param type 通知类型
* @param location 位置
* @param message 信息
*/
public void show(Type type, Location location, String message, Window window) {
setWindow(window);
long duration = FlatUIUtils.getUIInt("Toast.duration", 2500);
initStart(new ToastAnimation(type, location, duration, message), duration);
}
/**
* 弹窗: 支持自定义通知类型位置信息父窗体默认为设计器主Frame
*
* @param type 通知类型
* @param location 位置
* @param message 信息
*/
public void show(Type type, Location location, String message) {
show(type, location, message, DesignerContext.getDesignerFrame());
}
/**
* 弹窗: 组件提示
*
* @param component 组件
*/
public void show(JComponent component) {
long duration = FlatUIUtils.getUIInt("Toast.duration", 2500);
initStart(new ToastAnimation(Location.TOP_CENTER, duration, component), duration);
}
private synchronized boolean initStart(ToastAnimation toastAnimation, long duration) {
int limit = FlatUIUtils.getUIInt("Toast.limit", -1);
if (limit == -1 || getCurrentShowCount(toastAnimation.getLocation()) < limit) {
toastAnimation.start();
return true;
} else {
toastMsgHolder.hold(toastAnimation);
return false;
}
}
private synchronized void closeToast(ToastAnimation toastAnimation) {
ToastAnimation hold = toastMsgHolder.getHold(toastAnimation.getLocation());
if (hold != null) {
if (initStart(hold, hold.getDuration())) {
toastMsgHolder.removeHold(hold);
}
}
}
/**
* 清理全量弹出消息
*/
public void clearAll() {
toastMsgHolder.clearHold();
for (Map.Entry<Location, List<ToastAnimation>> set : lists.entrySet()) {
for (int i = 0; i < set.getValue().size(); i++) {
ToastAnimation an = set.getValue().get(i);
if (an != null) {
an.close();
}
}
}
}
/**
* 清理弹出消息
*
* @param location 位置
*/
public void clear(Location location) {
toastMsgHolder.clearHold(location);
List<ToastAnimation> list = lists.get(location);
if (list != null) {
for (ToastAnimation an : list) {
if (an != null) {
an.close();
}
}
}
}
/**
* 创建消息弹窗
*
* @param type 消息类型
* @param message 消息
* @return 弹出消息面板
*/
protected ToastPane createToastPane(Type type, String message) {
ToastPane toastPanel = new ToastPane();
toastPanel.set(type, message);
return toastPanel;
}
private synchronized void updateList(Location key, ToastAnimation values, boolean add) {
if (add) {
if (lists.containsKey(key)) {
lists.get(key).add(values);
} else {
List<ToastAnimation> list = new ArrayList<>();
list.add(values);
lists.put(key, list);
}
} else {
if (lists.containsKey(key)) {
lists.get(key).remove(values);
if (lists.get(key).isEmpty()) {
lists.remove(key);
}
}
}
}
/**
* 消息类型
*/
public enum Type {
// 成功
SUCCESS,
// 通知
INFO,
// 警告
WARNING,
// 错误
ERROR
}
/**
* 消息位置
*/
public enum Location {
// 顶部居左
TOP_LEFT,
// 顶部居中
TOP_CENTER,
// 顶部居右
TOP_RIGHT,
// 底部居左
BOTTOM_LEFT,
// 底部居中
BOTTOM_CENTER,
// 底部具右
BOTTOM_RIGHT
}
/**
* 消息弹窗动画
*/
public class ToastAnimation {
private JWindow window;
private Animator animator;
private boolean show = true;
private float animate;
private int x;
private int y;
private final Location location;
private final long duration;
private Insets frameInsets;
private int horizontalSpace;
private int animationMove;
private boolean top;
private boolean close = false;
public ToastAnimation(Type type, Location location, long duration, String message) {
installDefault();
this.location = location;
this.duration = duration;
window = new JWindow(ToastMsgManager.this.window);
ToastPane toastPanel = createToastPane(type, message);
toastPanel.putClientProperty(FineClientProperties.TOAST_CLOSE_CALLBACK, (Consumer) o -> close());
window.setContentPane(toastPanel);
window.setFocusableWindowState(false);
window.pack();
toastPanel.setDialog(window);
}
public ToastAnimation(Location location, long duration, JComponent component) {
installDefault();
this.location = location;
this.duration = duration;
window = new JWindow(ToastMsgManager.this.window);
window.setBackground(new Color(0, 0, 0, 0));
window.setContentPane(component);
window.setFocusableWindowState(false);
window.setSize(component.getPreferredSize());
}
private void installDefault() {
frameInsets = FineUIUtils.getUIInsets("Toast.frameInsets", new Insets(25, 25, 25, 25));
horizontalSpace = FlatUIUtils.getUIInt("Toast.horizontalGap", 10);
animationMove = FlatUIUtils.getUIInt("Toast.animationMove", 10);
}
/**
* 开始动画
*/
public void start() {
int animation = FlatUIUtils.getUIInt("Toast.duration", 200);
int resolution = FlatUIUtils.getUIInt("Toast.animationResolution", 5);
animator = new Animator(animation, new Animator.TimingTarget() {
@Override
public void begin() {
if (show) {
updateList(location, ToastAnimation.this, true);
installLocation();
}
}
@Override
public void timingEvent(float f) {
animate = show ? f : 1f - f;
updateLocation(true);
}
@Override
public void end() {
if (show && !close) {
closeAnimation();
} else {
updateList(location, ToastAnimation.this, false);
window.dispose();
closeToast(ToastAnimation.this);
}
}
});
animator.setResolution(resolution);
animator.start();
}
private void closeAnimation() {
SwingUtilities.invokeLater(() -> new Thread(() -> {
try {
Thread.sleep(duration);
} catch (InterruptedException ignored) {
}
if (!close) {
show = false;
animator.start();
}
}).start());
}
private void installLocation() {
Insets insets;
Rectangle rec;
if (ToastMsgManager.this.window == null) {
insets = UIScale.scale(frameInsets);
rec = new Rectangle(new Point(0, 0), Toolkit.getDefaultToolkit().getScreenSize());
} else {
insets = UIScale.scale(FlatUIUtils.addInsets(frameInsets, ToastMsgManager.this.window.getInsets()));
rec = ToastMsgManager.this.window.getBounds();
}
setupLocation(rec, insets);
window.setOpacity(0f);
window.setVisible(true);
}
private void move(Rectangle rec) {
Insets insets = UIScale.scale(FlatUIUtils.addInsets(frameInsets, ToastMsgManager.this.window.getInsets()));
setupLocation(rec, insets);
}
private void setupLocation(Rectangle rec, Insets insets) {
if (location == Location.TOP_LEFT) {
x = rec.x + insets.left;
y = rec.y + insets.top;
top = true;
} else if (location == Location.TOP_CENTER) {
x = rec.x + (rec.width - window.getWidth()) / 2;
y = rec.y + insets.top;
top = true;
} else if (location == Location.TOP_RIGHT) {
x = rec.x + rec.width - (window.getWidth() + insets.right);
y = rec.y + insets.top;
top = true;
} else if (location == Location.BOTTOM_LEFT) {
x = rec.x + insets.left;
y = rec.y + rec.height - (window.getHeight() + insets.bottom);
top = false;
} else if (location == Location.BOTTOM_CENTER) {
x = rec.x + (rec.width - window.getWidth()) / 2;
y = rec.y + rec.height - (window.getHeight() + insets.bottom);
top = false;
} else if (location == Location.BOTTOM_RIGHT) {
x = rec.x + rec.width - (window.getWidth() + insets.right);
y = rec.y + rec.height - (window.getHeight() + insets.bottom);
top = false;
}
int am = UIScale.scale(top ? animationMove : -animationMove);
int ly = (int) (getLocation(ToastAnimation.this) + y + animate * am);
window.setLocation(x, ly);
}
private void updateLocation(boolean loop) {
int am = UIScale.scale(top ? animationMove : -animationMove);
int ly = (int) (getLocation(ToastAnimation.this) + y + animate * am);
window.setLocation(x, ly);
window.setOpacity(animate);
if (loop) {
update(this);
}
}
private int getLocation(ToastAnimation notification) {
int height = 0;
List<ToastAnimation> list = lists.get(location);
for (ToastAnimation n : list) {
if (notification == n) {
return height;
}
double v = n.animate * (n.window.getHeight() + UIScale.scale(horizontalSpace));
height += (int) (top ? v : -v);
}
return height;
}
private void update(ToastAnimation except) {
List<ToastAnimation> list = lists.get(location);
for (ToastAnimation n : list) {
if (n != except) {
n.updateLocation(false);
}
}
}
/**
* 关闭动画
*/
public void close() {
if (show) {
if (animator.isRunning()) {
animator.stop();
}
close = true;
show = false;
animator.start();
}
}
public Location getLocation() {
return location;
}
public long getDuration() {
return duration;
}
}
}

95
designer-base/src/main/java/com/fine/component/toast/ToastPane.java

@ -0,0 +1,95 @@
package com.fine.component.toast;
import com.fine.theme.icon.LazyIcon;
import com.fine.theme.light.ui.ToastPanelUI;
import com.fine.theme.utils.FineClientProperties;
import com.fr.stable.StringUtils;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.JWindow;
import java.awt.Color;
import java.awt.Cursor;
/**
* 弹窗通知面板
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/11/27
*/
public class ToastPane extends JPanel {
private static final String UI_CLASS_ID = "ToastPanelUI";
protected JWindow window;
protected JLabel labelIcon;
protected JTextPane textPane;
private ToastMsgManager.Type type;
public ToastPane() {
installDefault();
}
@Override
public void updateUI() {
setUI(new ToastPanelUI());
removeDialogBackground();
}
@Override
public String getUIClassID() {
return UI_CLASS_ID;
}
private void removeDialogBackground() {
if (window != null) {
Color bg = getBackground();
window.setBackground(new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 0));
window.setSize(getPreferredSize());
}
}
private void installDefault() {
labelIcon = new JLabel();
textPane = new JTextPane();
textPane.setText(StringUtils.EMPTY);
textPane.setOpaque(false);
textPane.setFocusable(false);
textPane.setCursor(Cursor.getDefaultCursor());
putClientProperty(FineClientProperties.TOAST_ICON, labelIcon);
putClientProperty(FineClientProperties.TOAST_COMPONENT, textPane);
}
public void set(ToastMsgManager.Type type, String message) {
this.type = type;
labelIcon.setIcon(getDefaultIcon());
textPane.setText(message);
}
public void setDialog(JWindow window) {
this.window = window;
removeDialogBackground();
}
public Icon getDefaultIcon() {
String key = getKey();
return new LazyIcon(key, 20);
}
public String getKey() {
if (type == ToastMsgManager.Type.SUCCESS) {
return "success";
} else if (type == ToastMsgManager.Type.INFO) {
return "information";
} else if (type == ToastMsgManager.Type.WARNING) {
return "warning";
} else {
return "error";
}
}
}

125
designer-base/src/main/java/com/fine/theme/light/ui/ShadowBorder.java

@ -0,0 +1,125 @@
package com.fine.theme.light.ui;
import com.fine.theme.utils.ShadowRenderer;
import com.formdev.flatlaf.FlatPropertiesLaf;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.UIScale;
import com.fr.stable.AssistUtils;
import javax.swing.JComponent;
import javax.swing.border.EmptyBorder;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Shape;
import java.awt.image.BufferedImage;
/**
* 阴影边框
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/12/12
*/
public class ShadowBorder extends EmptyBorder {
@Styleable
protected Color shadowColor;
@Styleable
protected Insets shadowInsets;
@Styleable
protected float shadowOpacity;
private Image shadowImage;
private int shadowSize;
private Color lastShadowColor;
private float lastShadowOpacity;
private int lastShadowSize;
private int lastArc;
private int lastWidth;
private int lastHeight;
public ShadowBorder() {
this(new Color(0, 0, 0), new Insets(6, 6, 6, 6), 0.1f);
}
public ShadowBorder(Color shadowColor, Insets shadowInsets, float shadowOpacity) {
super(nonNegativeInsets(shadowInsets));
this.shadowColor = shadowColor;
this.shadowInsets = shadowInsets;
this.shadowOpacity = shadowOpacity;
this.shadowSize = maxInset(shadowInsets);
}
private static Insets nonNegativeInsets(Insets shadowInsets) {
return new Insets(Math.max(shadowInsets.top, 0), Math.max(shadowInsets.left, 0), Math.max(shadowInsets.bottom, 0), Math.max(shadowInsets.right, 0));
}
private int maxInset(Insets shadowInsets) {
return Math.max(Math.max(shadowInsets.left, shadowInsets.right), Math.max(shadowInsets.top, shadowInsets.bottom));
}
@Override
public Insets getBorderInsets() {
return UIScale.scale(super.getBorderInsets());
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
JComponent com = (JComponent) c;
int arc = FlatPropertiesLaf.getStyleableValue(com, "arc");
if (shadowImage == null || !shadowColor.equals(lastShadowColor)
|| width != lastWidth || height != lastHeight || shadowSize != lastShadowSize
|| !AssistUtils.equals(shadowOpacity, lastShadowOpacity) || arc != lastArc) {
shadowImage = createShadowImage(width, height, arc);
lastShadowColor = shadowColor;
lastWidth = width;
lastHeight = height;
lastShadowSize = shadowSize;
lastShadowOpacity = shadowOpacity;
lastArc = arc;
}
g.drawImage(shadowImage, 0, 0, null);
Insets insets = getBorderInsets();
int lx = insets.left;
int ly = insets.top;
int lw = width - (insets.left + insets.right);
int lh = height - (insets.top + insets.bottom);
Graphics2D g2 = (Graphics2D) g.create();
doPaintShadow(c, arc, g2, lx, ly, lw, lh, com);
g2.dispose();
}
private static void doPaintShadow(Component c, int arc, Graphics2D g2, int lx, int ly, int lw, int lh, JComponent component) {
if (arc > 0) {
FlatUIUtils.setRenderingHints(g2);
g2.setColor(c.getBackground());
FlatUIUtils.paintComponentBackground(g2, lx, ly, lw, lh, 0, UIScale.scale(arc));
} else {
g2.setColor(c.getBackground());
g2.fillRect(lx, ly, lw, lh);
}
int outlineWidth = FlatPropertiesLaf.getStyleableValue(component, "outlineWidth");
if (outlineWidth > 0) {
Color outlineColor = FlatPropertiesLaf.getStyleableValue(component, "outlineColor");
g2.setColor(outlineColor);
FlatUIUtils.paintOutline(g2, lx, ly, lw, lh, UIScale.scale(outlineWidth), UIScale.scale(arc));
}
}
private BufferedImage createShadowImage(int width, int height, int arc) {
int size = UIScale.scale(shadowSize);
float round = UIScale.scale(arc * 0.7f);
int shadowWidth = width - size * 2;
int shadowHeight = height - size * 2;
Shape shape = FlatUIUtils.createRoundRectanglePath(0, 0, shadowWidth, shadowHeight, round, round, round, round);
return new ShadowRenderer(size, shadowOpacity, shadowColor).createShadow(shape);
}
}

370
designer-base/src/main/java/com/fine/theme/light/ui/ToastPanelUI.java

@ -0,0 +1,370 @@
package com.fine.theme.light.ui;
import com.fine.theme.icon.LazyIcon;
import com.fine.theme.utils.FineUIUtils;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.ui.FlatStylingSupport;
import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable;
import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.LoggingFacade;
import com.formdev.flatlaf.util.UIScale;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicPanelUI;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.function.Consumer;
import static com.fine.theme.utils.FineClientProperties.BUTTON_TYPE;
import static com.fine.theme.utils.FineClientProperties.BUTTON_TYPE_TOOLBAR_BUTTON;
import static com.fine.theme.utils.FineClientProperties.STYLE;
import static com.fine.theme.utils.FineClientProperties.STYLE_CLASS;
import static com.fine.theme.utils.FineClientProperties.TOAST_CLOSE_CALLBACK;
import static com.fine.theme.utils.FineClientProperties.TOAST_COMPONENT;
import static com.fine.theme.utils.FineClientProperties.TOAST_ICON;
import static com.fine.theme.utils.FineClientProperties.TOAST_SHOW_CLOSE_BUTTON;
import static com.formdev.flatlaf.FlatClientProperties.clientProperty;
import static com.formdev.flatlaf.FlatClientProperties.clientPropertyBoolean;
/**
* 弹出消息面板UI
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/12/12
*/
public class ToastPanelUI extends BasicPanelUI implements StyleableUI, PropertyChangeListener {
protected JComponent iconComponent;
protected JComponent component;
protected JComponent closeButton;
@Styleable
protected int iconTextGap;
@Styleable
protected int closeButtonGap;
@Styleable
protected int minimumWidth;
@Styleable
protected int maximumWidth;
@Styleable
protected int arc;
@Styleable
protected int outlineWidth;
@Styleable
protected Color outlineColor;
@Styleable
protected boolean showCloseButton;
@Styleable
protected Color closeIconColor;
@Styleable
protected Insets margin;
@Styleable
protected Icon closeButtonIcon;
private NotificationPanelLayout layout;
private Map<String, Object> oldStyleValues;
@Override
public void installUI(JComponent c) {
super.installUI(c);
c.addPropertyChangeListener(this);
installIconComponent(c);
installComponent(c);
installCloseButton(c);
installStyle((JPanel) c);
}
@Override
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
c.removePropertyChangeListener(this);
uninstallIconComponent(c);
uninstallComponent(c);
uninstallCloseButton(c);
}
@Override
protected void installDefaults(JPanel p) {
super.installDefaults(p);
String prefix = getPropertyPrefix();
iconTextGap = FlatUIUtils.getUIInt(prefix + ".iconTextGap", 5);
closeButtonGap = FlatUIUtils.getUIInt(prefix + ".closeButtonGap", 5);
minimumWidth = FlatUIUtils.getUIInt(prefix + ".minimumWidth", 50);
maximumWidth = FlatUIUtils.getUIInt(prefix + ".maximumWidth", -1);
arc = FlatUIUtils.getUIInt(prefix + ".arc", 6);
outlineWidth = FlatUIUtils.getUIInt(prefix + ".outlineWidth", 0);
outlineColor = FlatUIUtils.getUIColor(prefix + ".outlineColor", "Component.focusColor");
margin = FineUIUtils.getUIInsets(prefix + ".margin", new Insets(8, 8, 8, 8));
showCloseButton = FlatUIUtils.getUIBoolean(prefix + ".showCloseButton", true);
closeIconColor = FlatUIUtils.getUIColor(prefix + ".closeIconColor", new Color(150, 150, 150));
closeButtonIcon = new LazyIcon("close");
p.setBackground(FlatUIUtils.getUIColor(prefix + ".background", "Panel.background"));
p.setBorder(createDefaultBorder());
LookAndFeel.installProperty(p, "opaque", false);
}
@Override
protected void uninstallDefaults(JPanel p) {
super.uninstallDefaults(p);
oldStyleValues = null;
}
protected Border createDefaultBorder() {
Color color = FlatUIUtils.getUIColor("Toast.shadowColor", new Color(0, 0, 0));
Insets insets = FineUIUtils.getUIInsets("Toast.shadowInsets", new Insets(6, 6, 6, 6));
float shadowOpacity = FlatUIUtils.getUIFloat("Toast.shadowOpacity", 0.2f);
return new ShadowBorder(color, insets, shadowOpacity);
}
protected String getPropertyPrefix() {
return "Toast";
}
@Override
public void propertyChange(PropertyChangeEvent e) {
JPanel c = (JPanel) e.getSource();
switch (e.getPropertyName()) {
default:
case TOAST_ICON: {
uninstallIconComponent(c);
installIconComponent(c);
refreshUI(c);
break;
}
case TOAST_COMPONENT: {
uninstallComponent(c);
installComponent(c);
refreshUI(c);
break;
}
case TOAST_SHOW_CLOSE_BUTTON: {
uninstallCloseButton(c);
installCloseButton(c);
refreshUI(c);
break;
}
case STYLE:
case STYLE_CLASS: {
installStyle(c);
refreshUI(c);
break;
}
}
}
private static void refreshUI(JPanel c) {
c.revalidate();
c.repaint();
}
private void installIconComponent(JComponent c) {
iconComponent = clientProperty(c, TOAST_ICON, null, JComponent.class);
if (iconComponent != null) {
installLayout(c);
c.add(iconComponent);
}
}
private void uninstallIconComponent(JComponent c) {
if (iconComponent != null) {
c.remove(iconComponent);
iconComponent = null;
}
}
private void installComponent(JComponent c) {
component = FlatClientProperties.clientProperty(c, TOAST_COMPONENT, null, JComponent.class);
if (component != null) {
installLayout(c);
c.add(component);
}
}
private void uninstallComponent(JComponent c) {
if (component != null) {
c.remove(component);
component = null;
}
}
private void installCloseButton(JComponent c) {
if (clientPropertyBoolean(c, TOAST_SHOW_CLOSE_BUTTON, showCloseButton)) {
closeButton = createCloseButton(c);
installLayout(c);
c.add(closeButton);
}
}
private void uninstallCloseButton(JComponent c) {
if (closeButton != null) {
c.remove(closeButton);
closeButton = null;
}
}
protected JComponent createCloseButton(JComponent c) {
JButton button = new JButton();
button.setFocusable(false);
button.setName("Toast.closeButton");
button.putClientProperty(BUTTON_TYPE, BUTTON_TYPE_TOOLBAR_BUTTON);
button.putClientProperty(STYLE, "arc:999");
button.setIcon(closeButtonIcon);
button.addActionListener(e -> closeButtonClicked(c));
return button;
}
@SuppressWarnings("all")
protected void closeButtonClicked(JComponent c) {
Object callback = c.getClientProperty(TOAST_CLOSE_CALLBACK);
if (callback instanceof Runnable) {
((Runnable) callback).run();
} else if (callback instanceof Consumer) {
((Consumer) callback).accept(c);
}
}
private void installLayout(JComponent c) {
if (layout == null) {
layout = new NotificationPanelLayout();
}
c.setLayout(layout);
}
protected void installStyle(JPanel c) {
try {
applyStyle(c, FlatStylingSupport.getResolvedStyle(c, "ToastPanel"));
} catch (RuntimeException ex) {
LoggingFacade.INSTANCE.logSevere(null, ex);
}
}
protected void applyStyle(JPanel c, Object style) {
boolean oldShowCloseButton = showCloseButton;
oldStyleValues = FlatStylingSupport.parseAndApply(oldStyleValues, style, (key, value) -> applyStyleProperty(c, key, value));
if (oldShowCloseButton != showCloseButton) {
uninstallCloseButton(c);
installCloseButton(c);
}
}
protected Object applyStyleProperty(JPanel c, String key, Object value) {
return FlatStylingSupport.applyToAnnotatedObjectOrComponent(this, c, key, value);
}
@Override
public Map<String, Class<?>> getStyleableInfos(JComponent c) {
return FlatStylingSupport.getAnnotatedStyleableInfos(this);
}
@Override
public Object getStyleableValue(JComponent c, String key) {
return FlatStylingSupport.getAnnotatedStyleableValue(this, key);
}
protected class NotificationPanelLayout implements LayoutManager {
@Override
public void addLayoutComponent(String name, Component comp) {
}
@Override
public void removeLayoutComponent(Component comp) {
}
@Override
public Dimension preferredLayoutSize(Container parent) {
synchronized (parent.getTreeLock()) {
Insets insets = FlatUIUtils.addInsets(parent.getInsets(), UIScale.scale(margin));
int width = insets.left + insets.right;
int height = 0;
int gap = 0;
int closeGap = 0;
if (iconComponent != null) {
width += iconComponent.getPreferredSize().width;
height = Math.max(height, iconComponent.getPreferredSize().height);
gap = UIScale.scale(iconTextGap);
}
if (component != null) {
width += gap;
width += component.getPreferredSize().width;
height = Math.max(height, component.getPreferredSize().height);
closeGap = UIScale.scale(closeButtonGap);
}
if (closeButton != null) {
width += closeGap;
width += closeButton.getPreferredSize().width;
height = Math.max(height, closeButton.getPreferredSize().height);
}
height += (insets.top + insets.bottom);
width = Math.max(minimumWidth, maximumWidth == -1 ? width : Math.min(maximumWidth, width));
return new Dimension(width, height);
}
}
@Override
public Dimension minimumLayoutSize(Container parent) {
synchronized (parent.getTreeLock()) {
return new Dimension(0, 0);
}
}
private int getMaxWidth(int insets) {
int width = Math.max(maximumWidth, minimumWidth) - insets;
if (iconComponent != null) {
width -= (iconComponent.getPreferredSize().width + UIScale.scale(iconTextGap));
}
if (closeButton != null) {
width -= (UIScale.scale(closeButtonGap) + closeButton.getPreferredSize().width);
}
return width;
}
@Override
public void layoutContainer(Container parent) {
synchronized (parent.getTreeLock()) {
Insets insets = FlatUIUtils.addInsets(parent.getInsets(), UIScale.scale(margin));
int x = insets.left;
int y = insets.top;
int height = 0;
if (iconComponent != null) {
int iconW = iconComponent.getPreferredSize().width;
int iconH = iconComponent.getPreferredSize().height;
iconComponent.setBounds(x, y, iconW, iconH);
x += iconW;
height = iconH;
}
if (component != null) {
int cW = maximumWidth == -1 ? component.getPreferredSize().width : Math.min(component.getPreferredSize().width, getMaxWidth(insets.left + insets.right));
int cH = component.getPreferredSize().height;
x += UIScale.scale(iconTextGap);
component.setBounds(x, y, cW, cH);
height = Math.max(height, cH);
}
if (closeButton != null) {
int cW = closeButton.getPreferredSize().width;
int cH = closeButton.getPreferredSize().height;
int cX = parent.getWidth() - insets.right - cW;
int cy = y + ((height - cH) / 2);
closeButton.setBounds(cX, cy, cW, cH);
}
}
}
}
}

6
designer-base/src/main/java/com/fine/theme/utils/FineClientProperties.java

@ -33,6 +33,12 @@ public interface FineClientProperties extends FlatClientProperties {
String COMBO_BOX_TYPE = "comboBoxType"; String COMBO_BOX_TYPE = "comboBoxType";
String ADAPTIVE_COMBO_BOX = "adaptiveComboBox"; String ADAPTIVE_COMBO_BOX = "adaptiveComboBox";
//---------------------------- Toast ----------------------------
String TOAST_ICON = "Toast.icon";
String TOAST_COMPONENT = "Toast.component";
String TOAST_SHOW_CLOSE_BUTTON = "Toast.showCloseButton";
String TOAST_CLOSE_CALLBACK = "Toast.closeCallback";
int GROUP_BUTTON_POSITION_INNER = 0; int GROUP_BUTTON_POSITION_INNER = 0;
int GROUP_BUTTON_POSITION_LEFT = 1; int GROUP_BUTTON_POSITION_LEFT = 1;
int GROUP_BUTTON_POSITION_RIGHT = 2; int GROUP_BUTTON_POSITION_RIGHT = 2;

53
designer-base/src/main/java/com/fine/theme/utils/FineUIUtils.java

@ -2,6 +2,7 @@ package com.fine.theme.utils;
import com.fine.theme.light.ui.CollapsibleScrollBarLayerUI; import com.fine.theme.light.ui.CollapsibleScrollBarLayerUI;
import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.UIScale;
import com.fr.design.border.FineBorderFactory; import com.fr.design.border.FineBorderFactory;
import com.fr.design.constants.LayoutConstants; import com.fr.design.constants.LayoutConstants;
import com.fr.design.gui.icontainer.UIScrollPane; import com.fr.design.gui.icontainer.UIScrollPane;
@ -12,12 +13,14 @@ import com.fr.design.mainframe.theme.edit.ui.LabelUtils;
import com.fr.design.i18n.DesignSizeI18nManager; import com.fr.design.i18n.DesignSizeI18nManager;
import com.fr.stable.os.OperatingSystem; import com.fr.stable.os.OperatingSystem;
import com.fr.value.AtomicClearableLazyValue; import com.fr.value.AtomicClearableLazyValue;
import org.jetbrains.annotations.Nullable;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JLayer; import javax.swing.JLayer;
import javax.swing.ScrollPaneConstants; import javax.swing.ScrollPaneConstants;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.JTextArea; import javax.swing.JTextArea;
import java.awt.AlphaComposite;
import javax.swing.JScrollBar; import javax.swing.JScrollBar;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
@ -28,9 +31,13 @@ import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.GraphicsDevice; import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment; import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Insets; import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.geom.Path2D; import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D; import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import static com.fine.swing.ui.layout.Layouts.cell; import static com.fine.swing.ui.layout.Layouts.cell;
@ -602,4 +609,50 @@ public class FineUIUtils {
// 获取滚动条的单位增量,并计算实际的滚动增量 // 获取滚动条的单位增量,并计算实际的滚动增量
return scrollAmount * scrollBar.getUnitIncrement(1); return scrollAmount * scrollBar.getUnitIncrement(1);
} }
/**
* 创建一个与指定组件大小相同的 VolatileImage并将组件内容绘制到该图像上
*
* @param component 组件
* @return 包含组件内容的 VolatileImage如果组件的宽度或高度为零则返回 null
*/
@Nullable
public static VolatileImage createImage(Component component) {
int width = component.getWidth();
int height = component.getHeight();
if (width > 0 && height > 0) {
VolatileImage image = component.createVolatileImage(width, height);
component.paint(image.createGraphics());
return image;
}
return null;
}
/**
* 创建一个与指定组件内容相同并带有圆角的 BufferedImage
*
* @param component 要生成图像的组件
* @param round 圆角半径
* @return 包含圆角组件内容的 BufferedImage如果组件的宽度或高度为零则返回 null
*/
public static Image createImage(Component component, int round) {
int width = component.getWidth();
int height = component.getHeight();
if (width > 0 && height > 0) {
VolatileImage image = createImage(component);
if (image != null) {
component.paint(image.createGraphics());
BufferedImage buffImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = buffImage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int arc = UIScale.scale(round);
FlatUIUtils.paintComponentBackground(g, 0, 0, width, height, 0, arc);
g.setComposite(AlphaComposite.SrcIn);
g.drawImage(image, 0, 0, null);
image.flush();
return buffImage;
}
}
return null;
}
} }

103
designer-base/src/main/java/com/fine/theme/utils/GlassLayerLoader.java

@ -0,0 +1,103 @@
package com.fine.theme.utils;
import com.fine.component.popup.ProgressChild;
import com.fine.component.popup.GlassPopupManager;
import com.fine.component.popup.GlassPaneChild;
import com.fr.design.mainframe.DesignerContext;
import com.fr.design.ui.util.UIUtil;
import com.fr.log.FineLoggerFactory;
import javax.swing.SwingWorker;
import java.awt.Container;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
/**
* 遮罩层加载器
*
* @author Levy.Xie
* @since 11.0
* Created on 2024/12/06
*/
public class GlassLayerLoader {
private static volatile GlassLayerLoader loader = null;
private GlassLayerLoader() {
}
public static GlassLayerLoader getInstance() {
if (loader == null) {
synchronized (GlassLayerLoader.class) {
if (loader == null) {
loader = new GlassLayerLoader();
}
}
}
return loader;
}
/**
* 带有加载面板的后台任务执行器(无回调)
*
* @param task 耗时任务
* @param container 遮罩的底层容器
* @param loadingPane 显示加载动画的 GlassPaneChild
* @param <T> 任务的返回值类型
*/
public <T> void runWithLoader(Callable<T> task, Container container, GlassPaneChild loadingPane) {
// 默认无回调
runWithLoader(task, container, loadingPane, result -> {}, e -> {});
}
/**
* 带有加载面板的后台任务执行器(有成功和失败的回调)
*
* @param task 耗时任务
* @param container 遮罩的底层容器
* @param loadingPane 显示加载动画的 GlassPaneChild
* @param <T> 任务的返回值类型
*/
public <T> void runWithLoader(Callable<T> task, Container container, GlassPaneChild loadingPane,
Consumer<T> onSuccess, Consumer<Throwable> onFailure) {
// 安装并显示加载面板
UIUtil.invokeAndWaitIfNeeded(() -> {
GlassPopupManager.install(container);
GlassPopupManager.showPopup(loadingPane);
});
SwingWorker<T, Void> worker = new SwingWorker<T, Void>() {
@Override
protected T doInBackground() throws Exception {
return task.call();
}
@Override
protected void done() {
try {
T t = get();
onSuccess.accept(t);
} catch (Exception e) {
onFailure.accept(e);
FineLoggerFactory.getLogger().error(e.getMessage());
} finally {
loadingPane.onClose();
GlassPopupManager.closePopup(loadingPane);
}
}
};
worker.execute();
}
/**
* 进度条遮罩加载动画
*
* @param task 耗时任务
* @param info 显示加载面板的 JFrame
* @param maxWait 最大等待时间
* @param <T> 任务的返回值类型
*/
public <T> void runWithProgressLoader(Callable<T> task, String info, int maxWait) {
runWithLoader(task, DesignerContext.getDesignerFrame(), new ProgressChild(info).setMaxWait(maxWait));
}
}

220
designer-base/src/main/java/com/fine/theme/utils/ShadowRenderer.java

@ -0,0 +1,220 @@
package com.fine.theme.utils;
import org.jetbrains.annotations.NotNull;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
/**
* 阴影绘制工具类
*
* @author Levy.Xie
* @since 12.0
* Created on 2024/11/28
*/
public class ShadowRenderer {
private final int size;
private final float opacity;
private final Color color;
public ShadowRenderer() {
this(5, 0.5f, Color.BLACK);
}
public ShadowRenderer(final int size, final float opacity, final Color color) {
this.size = size;
this.opacity = opacity;
this.color = color;
}
public Color getColor() {
return color;
}
public float getOpacity() {
return opacity;
}
public int getSize() {
return size;
}
/**
* 基于形状创建阴影
*
* @param shape 形状
* @return 阴影图片
*/
public BufferedImage createShadow(Shape shape) {
Rectangle rec = shape.getBounds();
BufferedImage img = new BufferedImage(rec.width, rec.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setColor(Color.BLACK);
g2.translate(-rec.x, -rec.y);
g2.fill(shape);
g2.dispose();
return createShadow(img);
}
/**
* 基于图片创建阴影
*
* @param image 图片
* @return 阴影图片
*/
public BufferedImage createShadow(final BufferedImage image) {
int shadowSize = size * 2;
int srcWidth = image.getWidth();
int srcHeight = image.getHeight();
int dstWidth = srcWidth + shadowSize;
int dstHeight = srcHeight + shadowSize;
int left = size;
int right = shadowSize - left;
int yStop = dstHeight - right;
int shadowRgb = color.getRGB() & 0x00FFFFFF;
int[] aHistory = new int[shadowSize];
BufferedImage dst = new BufferedImage(dstWidth, dstHeight, BufferedImage.TYPE_INT_ARGB);
int[] dstBuffer = new int[dstWidth * dstHeight];
int[] srcBuffer = new int[srcWidth * srcHeight];
getPixels(image, 0, 0, srcWidth, srcHeight, srcBuffer);
int lastPixelOffset = right * dstWidth;
int[] hSumLookup = initSumLookupArray(1.0f, shadowSize);
int[] vSumLookup = initSumLookupArray(opacity, shadowSize);
applyHorizontalShadowBlur(left, dstWidth, srcHeight, shadowSize, aHistory, srcWidth, hSumLookup, dstBuffer, srcBuffer);
applyVerticalShadowBlur(dstWidth, left, aHistory, right, dstBuffer, yStop, vSumLookup, shadowRgb,
lastPixelOffset, shadowSize, dstHeight);
setPixels(dst, 0, 0, dstWidth, dstHeight, dstBuffer);
return dst;
}
private static int @NotNull [] initSumLookupArray(float x, int shadowSize) {
float sumDivider = x / shadowSize;
int[] sumLookup = new int[256 * shadowSize];
for (int i = 0; i < sumLookup.length; i++) {
sumLookup[i] = (int) (i * sumDivider);
}
return sumLookup;
}
private static void applyVerticalShadowBlur(int dstWidth, int left, int[] aHistory, int right, int[] dstBuffer,
int yStop, int[] vSumLookup, int shadowRgb, int lastPixelOffset,
int shadowSize, int dstHeight) {
for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
int aSum = initializeAlphaSum(dstWidth, left, aHistory, right, dstBuffer, bufferOffset, 0);
bufferOffset = x;
int historyIdx = 0;
for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) {
int a = vSumLookup[aSum];
dstBuffer[bufferOffset] = a << 24 | shadowRgb;
aSum -= aHistory[historyIdx];
a = dstBuffer[bufferOffset + lastPixelOffset] >>> 24;
aHistory[historyIdx] = a;
aSum += a;
if (++historyIdx >= shadowSize) {
historyIdx -= shadowSize;
}
}
for (int y = yStop; y < dstHeight; y++, bufferOffset += dstWidth) {
int a = vSumLookup[aSum];
dstBuffer[bufferOffset] = a << 24 | shadowRgb;
aSum -= aHistory[historyIdx];
if (++historyIdx >= shadowSize) {
historyIdx -= shadowSize;
}
}
}
}
private static int initializeAlphaSum(int dstWidth, int left, int[] aHistory, int right, int[] dstBuffer, int bufferOffset, int aSum) {
int historyIdx;
for (historyIdx = 0; historyIdx < left; ) {
aHistory[historyIdx++] = 0;
}
for (int y = 0; y < right; y++, bufferOffset += dstWidth) {
int a = dstBuffer[bufferOffset] >>> 24;
aHistory[historyIdx++] = a;
aSum += a;
}
return aSum;
}
private static void applyHorizontalShadowBlur(int left, int dstWidth, int srcHeight, int shadowSize,
int[] aHistory, int srcWidth, int[] hSumLookup, int[] dstBuffer, int[] srcBuffer) {
int historyIdx;
for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) {
for (historyIdx = 0; historyIdx < shadowSize; ) {
aHistory[historyIdx++] = 0;
}
int aSum = 0;
historyIdx = 0;
int srcOffset = srcY * srcWidth;
for (int srcX = 0; srcX < srcWidth; srcX++) {
int a = hSumLookup[aSum];
dstBuffer[dstOffset++] = a << 24;
aSum -= aHistory[historyIdx];
a = srcBuffer[srcOffset + srcX] >>> 24;
aHistory[historyIdx] = a;
aSum += a;
if (++historyIdx >= shadowSize) {
historyIdx -= shadowSize;
}
}
dstOffset = updateDstBufferOffset(shadowSize, aHistory, hSumLookup, dstBuffer, aSum, dstOffset, historyIdx);
}
}
private static int updateDstBufferOffset(int shadowSize, int[] aHistory, int[] hSumLookup, int[] dstBuffer,
int aSum, int dstOffset, int historyIdx) {
for (int i = 0; i < shadowSize; i++) {
int a = hSumLookup[aSum];
dstBuffer[dstOffset++] = a << 24;
aSum -= aHistory[historyIdx];
if (++historyIdx >= shadowSize) {
historyIdx -= shadowSize;
}
}
return dstOffset;
}
private int[] getPixels(BufferedImage img, int x, int y, int w, int h, int[] pixels) {
if (w == 0 || h == 0) {
return new int[0];
}
if (pixels == null) {
pixels = new int[w * h];
} else if (pixels.length < w * h) {
throw new IllegalArgumentException("pixels array must have a length" + " >= w*h");
}
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_INT_ARGB || imageType == BufferedImage.TYPE_INT_RGB) {
Raster raster = img.getRaster();
return (int[]) raster.getDataElements(x, y, w, h, pixels);
}
return img.getRGB(x, y, w, h, pixels, 0, w);
}
private void setPixels(BufferedImage img, int x, int y, int w, int h, int[] pixels) {
if (pixels == null || w == 0 || h == 0) {
return;
} else if (pixels.length < w * h) {
throw new IllegalArgumentException("pixels array must have a length" + " >= w*h");
}
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_INT_ARGB || imageType == BufferedImage.TYPE_INT_RGB) {
WritableRaster raster = img.getRaster();
raster.setDataElements(x, y, w, h, pixels);
} else {
img.setRGB(x, y, w, h, pixels, 0, w);
}
}
}

7
designer-base/src/main/java/com/fr/design/EnvChangeEntrance.java

@ -1,5 +1,6 @@
package com.fr.design; package com.fr.design;
import com.fine.theme.utils.GlassLayerLoader;
import com.fr.common.report.ReportState; import com.fr.common.report.ReportState;
import com.fr.design.backup.EnvBackupHelper; import com.fr.design.backup.EnvBackupHelper;
import com.fr.design.data.DesignTableDataManager; import com.fr.design.data.DesignTableDataManager;
@ -102,9 +103,12 @@ public class EnvChangeEntrance {
* @param envName 目标工作目录名称 * @param envName 目标工作目录名称
*/ */
public void switch2Env(final String envName) { public void switch2Env(final String envName) {
GlassLayerLoader.getInstance().runWithProgressLoader(() -> {
if (switch2Env(envName, PopTipStrategy.LATER)) { if (switch2Env(envName, PopTipStrategy.LATER)) {
VersionCheckUtils.showVersionCheckDialog(envName); VersionCheckUtils.showVersionCheckDialog(envName);
} }
return null;
}, Toolkit.i18nText("Fine-Design_Basic_M_Switch_Workspace"), 5);
} }
/** /**
@ -525,12 +529,15 @@ public class EnvChangeEntrance {
// 用户取消保存时,取消切换目录操作 // 用户取消保存时,取消切换目录操作
return; return;
} }
GlassLayerLoader.getInstance().runWithProgressLoader(() -> {
boolean changeResult = envListOkAction(envListPane, PopTipStrategy.LATER); boolean changeResult = envListOkAction(envListPane, PopTipStrategy.LATER);
// 切换完成后清理密码 // 切换完成后清理密码
updateNotRememberPwdEnv(); updateNotRememberPwdEnv();
if (changeResult) { if (changeResult) {
VersionCheckUtils.showVersionCheckDialog(envListPane.getSelectedName()); VersionCheckUtils.showVersionCheckDialog(envListPane.getSelectedName());
} }
return null;
}, Toolkit.i18nText("Fine-Design_Basic_M_Switch_Workspace"), 5);
} }
@Override @Override

6
designer-base/src/main/java/com/fr/design/file/DefaultTemplateTreeDefineProcessor.java

@ -22,8 +22,7 @@ import com.fr.design.mainframe.manager.clip.TemplateTreeClipboard;
import com.fr.design.mainframe.manager.search.TemplateDirTreeSearchManager; import com.fr.design.mainframe.manager.search.TemplateDirTreeSearchManager;
import com.fr.design.mainframe.manager.search.TemplateTreeSearchManager; import com.fr.design.mainframe.manager.search.TemplateTreeSearchManager;
import com.fr.design.mainframe.manager.search.searcher.control.pane.TemplateDirTreeSearchPane; import com.fr.design.mainframe.manager.search.searcher.control.pane.TemplateDirTreeSearchPane;
import com.fr.design.mainframe.toast.DesignerToastMsgUtil; import com.fine.component.toast.ToastMsgManager;
import com.fr.design.mainframe.toast.ToastMsgDialog;
import com.fr.design.utils.TemplateUtils; import com.fr.design.utils.TemplateUtils;
import com.fr.design.utils.gui.GUICoreUtils; import com.fr.design.utils.gui.GUICoreUtils;
import com.fr.file.filetree.FileNode; import com.fr.file.filetree.FileNode;
@ -402,8 +401,7 @@ public class DefaultTemplateTreeDefineProcessor extends AbstractTemplateTreeDefi
boolean moveSuccess = doMove(); boolean moveSuccess = doMove();
dispose(); dispose();
if (moveSuccess) { if (moveSuccess) {
ToastMsgDialog dialog = DesignerToastMsgUtil.createPromptDialog(Toolkit.i18nText("Fine-Design_Basic_Template_Moved_Success")); ToastMsgManager.getInstance().info(Toolkit.i18nText("Fine-Design_Basic_Template_Moved_Success"));
dialog.setVisible(true);
NodeAuthProcessor.getInstance().refresh(); NodeAuthProcessor.getInstance().refresh();
DesignerFrameFileDealerPane.getInstance().refreshRightToolBarBy(null); DesignerFrameFileDealerPane.getInstance().refreshRightToolBarBy(null);
LocateAction.gotoEditingTemplateLeaf(targetFile); LocateAction.gotoEditingTemplateLeaf(targetFile);

84
designer-base/src/main/java/com/fr/design/foldablepane/UIExpandablePane.java

@ -1,12 +1,12 @@
package com.fr.design.foldablepane; package com.fr.design.foldablepane;
import com.formdev.flatlaf.util.Animator;
import com.formdev.flatlaf.util.ScaledEmptyBorder; import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.fr.design.border.FineBorderFactory; import com.fr.design.border.FineBorderFactory;
import javax.swing.JPanel; import javax.swing.JPanel;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Dimension;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
@ -14,29 +14,29 @@ import static com.fine.swing.ui.layout.Layouts.cell;
import static com.fine.swing.ui.layout.Layouts.column; import static com.fine.swing.ui.layout.Layouts.column;
import static com.fine.swing.ui.layout.Layouts.fix; import static com.fine.swing.ui.layout.Layouts.fix;
/** /**
* Created by MoMeak on 2017/7/5. * 折叠面板
*
* @author Levy.Xie
* @since 11.0
* Created on 2024/11/15
*/ */
public class UIExpandablePane extends JPanel { public class UIExpandablePane extends JPanel {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private HeaderPane headerPanel; private boolean showExpand = true;
private JPanel contentPanel; private Animator animator;
private Color color = Color.black; private int fullHeight;
private String title; private final JPanel contentPanel;
private int headWidth; private final String title;
private int headHeight;
public JPanel getContentPanel() { public JPanel getContentPanel() {
return contentPanel; return contentPanel;
} }
public UIExpandablePane(String title, int headWidth, int headHeight, JPanel contentPanel) { public UIExpandablePane(String title, @Deprecated int headWidth, @Deprecated int headHeight, JPanel contentPanel) {
super(); super();
this.title = title; this.title = title;
this.headWidth = headWidth;
this.headHeight = headHeight;
this.contentPanel = contentPanel; this.contentPanel = contentPanel;
initComponents(false); initComponents(false);
} }
@ -48,48 +48,80 @@ public class UIExpandablePane extends JPanel {
public UIExpandablePane(String title, JPanel contentPanel, boolean withUnderline) { public UIExpandablePane(String title, JPanel contentPanel, boolean withUnderline) {
super(); super();
this.title = title; this.title = title;
this.headHeight = headHeight;
this.contentPanel = contentPanel; this.contentPanel = contentPanel;
initComponents(withUnderline); initComponents(withUnderline);
} }
private void initComponents(boolean withUnderline) { private void initComponents(boolean withUnderline) {
this.setLayout(new BorderLayout()); this.setLayout(new BorderLayout());
HeaderPane headerPanel = new HeaderPane(title);
headerPanel = new HeaderPane(title);
headerPanel.addMouseListener(new PanelAction()); headerPanel.addMouseListener(new PanelAction());
setcontentPanelontentPanelBorder(); setcontentPanelontentPanelBorder();
if (withUnderline) { if (withUnderline) {
this.add(column( this.add(column(
cell(headerPanel), cell(contentPanel).with(it -> it.setBorder(new ScaledEmptyBorder(0, 0, 10, 0))), cell(headerPanel),
cell(contentPanel).with(it -> it.setBorder(new ScaledEmptyBorder(0, 0, 10, 0))),
fix(1).with(it -> it.setBorder(FineBorderFactory.createDefaultUnderlineBorder()))) fix(1).with(it -> it.setBorder(FineBorderFactory.createDefaultUnderlineBorder())))
.getComponent()); .getComponent());
} else { } else {
this.add(headerPanel, BorderLayout.NORTH); this.add(headerPanel, BorderLayout.NORTH);
this.add(contentPanel, BorderLayout.CENTER); this.add(contentPanel, BorderLayout.CENTER);
} }
initAnimation();
setOpaque(false); setOpaque(false);
} }
/**
* 组件竖向折叠显示动画
*/
private void initAnimation() {
int width = contentPanel.getWidth();
boolean sizeSet = contentPanel.isPreferredSizeSet();
animator = new Animator(200, new Animator.TimingTarget() {
@Override
public void timingEvent(float fraction) {
float ratio = !showExpand ? fraction : 1f - fraction;
contentPanel.setPreferredSize(new Dimension(width, (int) (fullHeight * ratio)));
contentPanel.revalidate();
contentPanel.repaint();
}
@Override
public void begin() {
contentPanel.setVisible(true);
if (showExpand) {
fullHeight = contentPanel.getPreferredSize().height;
}
}
@Override
public void end() {
// 重置中心面板
if (!sizeSet) {
contentPanel.setPreferredSize(null);
}
contentPanel.setVisible(!showExpand);
showExpand = !showExpand;
}
});
}
protected void setcontentPanelontentPanelBorder() { protected void setcontentPanelontentPanelBorder() {
} }
class PanelAction extends MouseAdapter { class PanelAction extends MouseAdapter {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
HeaderPane hp = (HeaderPane) e.getSource(); HeaderPane hp = (HeaderPane) e.getSource();
if (contentPanel.isShowing()) { if (!Animator.useAnimation()) {
contentPanel.setVisible(false); contentPanel.setVisible(!showExpand);
hp.setShow(false); } else if (!animator.isRunning()) {
} else { animator.start();
contentPanel.setVisible(true);
hp.setShow(true);
} }
hp.setShow(!showExpand);
hp.setPressed(false); hp.setPressed(false);
hp.getParent().validate();
hp.getParent().repaint();
} }
public void mousePressed(MouseEvent e) { public void mousePressed(MouseEvent e) {

4
designer-base/src/main/java/com/fr/design/mainframe/ComponentReuseNotifyUtil.java

@ -3,7 +3,7 @@ package com.fr.design.mainframe;
import com.fr.design.DesignerEnvManager; import com.fr.design.DesignerEnvManager;
import com.fr.design.i18n.Toolkit; import com.fr.design.i18n.Toolkit;
import com.fr.design.mainframe.reuse.ComponentReuseNotificationInfo; import com.fr.design.mainframe.reuse.ComponentReuseNotificationInfo;
import com.fr.design.mainframe.toast.DesignerToastMsgUtil; import com.fine.component.toast.ToastMsgManager;
import com.fr.design.notification.SnapChat; import com.fr.design.notification.SnapChat;
import com.fr.design.notification.SnapChatFactory; import com.fr.design.notification.SnapChatFactory;
import com.fr.design.notification.SnapChatKey; import com.fr.design.notification.SnapChatKey;
@ -29,7 +29,7 @@ public class ComponentReuseNotifyUtil {
} }
}); });
if (snapChat.hasRead()) { if (snapChat.hasRead()) {
DesignerToastMsgUtil.toastPrompt(Toolkit.i18nText("Fine-Design_Component_Reuse_Merge_Prompt")); ToastMsgManager.getInstance().info(Toolkit.i18nText("Fine-Design_Component_Reuse_Merge_Prompt"));
} }
ComponentReuseNotificationInfo.getInstance().setClickedWidgetLib(true); ComponentReuseNotificationInfo.getInstance().setClickedWidgetLib(true);
DesignerEnvManager.getEnvManager().saveXMLFile(); DesignerEnvManager.getEnvManager().saveXMLFile();

10
designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeProfilePane.java

@ -16,7 +16,7 @@ import com.fr.design.mainframe.theme.dialog.TemplateThemeProfileDialog;
import com.fr.design.mainframe.theme.edit.ui.LabelUtils; import com.fr.design.mainframe.theme.edit.ui.LabelUtils;
import com.fr.design.mainframe.theme.ui.AutoCheckTextField; import com.fr.design.mainframe.theme.ui.AutoCheckTextField;
import com.fr.design.mainframe.theme.ui.AutoCheckThemeNameTextField; import com.fr.design.mainframe.theme.ui.AutoCheckThemeNameTextField;
import com.fr.design.mainframe.toast.DesignerToastMsgUtil; import com.fine.component.toast.ToastMsgManager;
import com.fr.design.utils.gui.GUICoreUtils; import com.fr.design.utils.gui.GUICoreUtils;
import com.fr.log.FineLoggerFactory; import com.fr.log.FineLoggerFactory;
import com.fr.stable.StringUtils; import com.fr.stable.StringUtils;
@ -226,12 +226,10 @@ public abstract class TemplateThemeProfilePane<T extends TemplateTheme> extends
saveButton.setEnabled(false); saveButton.setEnabled(false);
saveAsButton.setEnabled(true); saveAsButton.setEnabled(true);
actionListener.onSaved(config.cachedFetch(getName())); actionListener.onSaved(config.cachedFetch(getName()));
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(() -> {
@Override ToastMsgManager.getInstance().info(
public void run() { Toolkit.i18nText("Fine-Design_Basic_Template_Theme_Profile_Pane_Save_Successfully"));
DesignerToastMsgUtil.toastPrompt(Toolkit.i18nText("Fine-Design_Basic_Template_Theme_Profile_Pane_Save_Successfully"));
profileDialog.exit(); profileDialog.exit();
}
}); });
} }
}); });

1
designer-base/src/main/java/com/fr/design/mainframe/toast/DesignerToastMsgUtil.java

@ -21,6 +21,7 @@ import java.awt.Window;
/** /**
* Created by kerry on 5/6/21 * Created by kerry on 5/6/21
*/ */
@Deprecated
public class DesignerToastMsgUtil { public class DesignerToastMsgUtil {
private static final int MIN_WIDTH = 134; private static final int MIN_WIDTH = 134;
private static final int MAX_WIDTH = 454; private static final int MAX_WIDTH = 454;

4
designer-base/src/main/java/com/fr/design/style/background/image/ImageFileChooser.java

@ -7,7 +7,7 @@ import com.fr.design.DesignerEnvManager;
import com.fr.design.gui.ifilechooser.FileChooserFactory; import com.fr.design.gui.ifilechooser.FileChooserFactory;
import com.fr.design.gui.ifilechooser.FileChooserProvider; import com.fr.design.gui.ifilechooser.FileChooserProvider;
import com.fr.design.i18n.Toolkit; import com.fr.design.i18n.Toolkit;
import com.fr.design.mainframe.toast.DesignerToastMsgUtil; import com.fine.component.toast.ToastMsgManager;
import java.awt.Component; import java.awt.Component;
@ -56,7 +56,7 @@ public class ImageFileChooser {
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(new Runnable() {
@Override @Override
public void run() { public void run() {
DesignerToastMsgUtil.toastWarning(Toolkit.i18nText("Fine-Design_Image_Compress_Move_Tip")); ToastMsgManager.getInstance().warn(Toolkit.i18nText("Fine-Design_Image_Compress_Move_Tip"));
DesignerEnvManager.getEnvManager().setShowImageCompressMoveTip(false); DesignerEnvManager.getEnvManager().setShowImageCompressMoveTip(false);
} }
}); });

22
designer-base/src/main/java/com/fr/start/ServerStarter.java

@ -1,17 +1,15 @@
package com.fr.start; package com.fr.start;
import com.fine.theme.utils.GlassLayerLoader;
import com.fr.base.ServerConfig; import com.fr.base.ServerConfig;
import com.fr.concurrent.NamedThreadFactory;
import com.fr.design.DesignerEnvManager; import com.fr.design.DesignerEnvManager;
import com.fr.design.i18n.Toolkit;
import com.fr.design.mainframe.DesignerContext; import com.fr.design.mainframe.DesignerContext;
import com.fr.design.utils.BrowseUtils; import com.fr.design.utils.BrowseUtils;
import com.fr.general.GeneralContext; import com.fr.general.GeneralContext;
import com.fr.start.server.FineEmbedServer; import com.fr.start.server.FineEmbedServer;
import com.fr.start.server.FineEmbedServerMonitor;
import com.fr.workspace.WorkContext; import com.fr.workspace.WorkContext;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ServerStarter { public class ServerStarter {
@ -43,25 +41,15 @@ public class ServerStarter {
public static void browserURLWithLocalEnv(final String url) { public static void browserURLWithLocalEnv(final String url) {
// 内置服务器没有启动并且设计器已经打开,可以使用带进度条的启动方式 // 内置服务器没有启动并且设计器已经打开,可以使用带进度条的启动方式
if (!FineEmbedServer.isRunning() && DesignerContext.getDesignerFrame().isDesignerOpened()) { if (!FineEmbedServer.isRunning() && DesignerContext.getDesignerFrame().isDesignerOpened()) {
FineEmbedServerMonitor.getInstance().monitor(); GlassLayerLoader.getInstance().runWithProgressLoader(() -> {
ExecutorService service = Executors.newSingleThreadExecutor(new NamedThreadFactory("ServerStarter"));
service.submit(new Runnable() {
@Override
public void run() {
try {
try { try {
FineEmbedServer.start(); FineEmbedServer.start();
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} finally {
FineEmbedServerMonitor.getInstance().setComplete();
}
BrowseUtils.browser(url); BrowseUtils.browser(url);
} return null;
}); }, Toolkit.i18nText("Fine-Design_Basic_Loading_Embed_Server"), 10);
service.shutdown();
} else if (!FineEmbedServer.isRunning()) { } else if (!FineEmbedServer.isRunning()) {
// 普通方式启动内置服务器 // 普通方式启动内置服务器
try { try {

5
designer-base/src/main/java/com/fr/start/server/DesignEmbedHelper.java

@ -27,7 +27,6 @@ import java.util.HashSet;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
;
/** /**
* 内置服务器工具类 * 内置服务器工具类
@ -48,15 +47,11 @@ public class DesignEmbedHelper {
public static synchronized void start() { public static synchronized void start() {
try { try {
FineEmbedServerMonitor.getInstance().reset();
//初始化tomcat //初始化tomcat
initTomcat(); initTomcat();
tomcat.start(); tomcat.start();
} catch (LifecycleException e) { } catch (LifecycleException e) {
FineLoggerFactory.getLogger().error(e.getMessage(), e); FineLoggerFactory.getLogger().error(e.getMessage(), e);
} finally {
FineEmbedServerMonitor.getInstance().setComplete();
} }
} }

1
designer-base/src/main/java/com/fr/start/server/FineEmbedServerMonitor.java

@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
* @author zack * @author zack
* @date 2018/8/21 * @date 2018/8/21
*/ */
@Deprecated
public class FineEmbedServerMonitor { public class FineEmbedServerMonitor {
private int progress; private int progress;
private static final int STEP = 1; private static final int STEP = 1;

1
designer-base/src/main/java/com/fr/startup/ui/StartupLoadingPanel.java

@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit;
* @version 11.0 * @version 11.0
* created by Harrison on 2022/11/08 * created by Harrison on 2022/11/08
**/ **/
@Deprecated
public class StartupLoadingPanel { public class StartupLoadingPanel {
/** /**

38
designer-base/src/main/java/com/fr/startup/ui/StartupPageWindow.java

@ -1,8 +1,10 @@
package com.fr.startup.ui; package com.fr.startup.ui;
import com.fine.component.popup.ProgressChild;
import com.fine.swing.ui.layout.Column; import com.fine.swing.ui.layout.Column;
import com.fine.theme.utils.FineUIStyle; import com.fine.theme.utils.FineUIStyle;
import com.fine.theme.utils.FineUIUtils; import com.fine.theme.utils.FineUIUtils;
import com.fine.theme.utils.GlassLayerLoader;
import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.ScaledEmptyBorder; import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.fr.design.DesignerEnvManager; import com.fr.design.DesignerEnvManager;
@ -15,7 +17,6 @@ import com.fr.design.layout.FRGUIPaneFactory;
import com.fr.design.layout.VerticalFlowLayout; import com.fr.design.layout.VerticalFlowLayout;
import com.fr.design.mainframe.messagecollect.StartErrorMessageCollector; import com.fr.design.mainframe.messagecollect.StartErrorMessageCollector;
import com.fr.design.mainframe.messagecollect.entity.DesignerErrorMessage; import com.fr.design.mainframe.messagecollect.entity.DesignerErrorMessage;
import com.fr.design.ui.util.UIUtil;
import com.fr.design.utils.ColorUtils; import com.fr.design.utils.ColorUtils;
import com.fr.design.utils.gui.GUICoreUtils; import com.fr.design.utils.gui.GUICoreUtils;
import com.fr.exit.DesignerExiter; import com.fr.exit.DesignerExiter;
@ -35,7 +36,6 @@ import javax.swing.JPanel;
import javax.swing.JSeparator; import javax.swing.JSeparator;
import javax.swing.ScrollPaneConstants; import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import javax.swing.SwingWorker;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
@ -233,32 +233,17 @@ public class StartupPageWindow extends JFrame {
} }
private void enterWorkspace(Runnable action) { private void enterWorkspace(Runnable action) {
GlassLayerLoader.getInstance().runWithLoader(() -> {
UIUtil.invokeAndWaitIfNeeded(() -> {
// 必须直接初始化
// 见 https://work.fineres.com/browse/REPORT-85293
StartupLoadingPanel loadingPanel = new StartupLoadingPanel(this);
loadingPanel.show();
setEnabled(false); setEnabled(false);
SwingWorker<Void, Void> task = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
action.run(); action.run();
return null; return null;
} }, this, new ProgressChild(Toolkit.i18nText("Fine-Design_Basic_Initializing")).setMaxWait(10),
// 进入工作目录成功回调
@Override res -> {
protected void done() {
try {
Void result = get();
setVisible(false); setVisible(false);
StartErrorMessageCollector.getInstance().setExtraJudgeStart(false); StartErrorMessageCollector.getInstance().setExtraJudgeStart(false);
} catch (Exception e) { }, e -> {
// 处理错误 // 进入工作目录失败回调
UIUtil.invokeLaterIfNeeded(() -> {
UIExpandDialog.Builder() UIExpandDialog.Builder()
.owner(StartupPageWindow.this) .owner(StartupPageWindow.this)
.title(Toolkit.i18nText("Fine-Design_Basic_Remote_Env_Try")) .title(Toolkit.i18nText("Fine-Design_Basic_Remote_Env_Try"))
@ -270,17 +255,10 @@ public class StartupPageWindow extends JFrame {
.build() .build()
.setVisible(true); .setVisible(true);
setEnabled(true); setEnabled(true);
});
FineLoggerFactory.getLogger().error(e.getMessage(), e); FineLoggerFactory.getLogger().error(e.getMessage(), e);
StartErrorMessageCollector.getInstance().asyncRecord(DesignerErrorMessage.UNEXCEPTED_START_FAILED.getId(), StartErrorMessageCollector.getInstance().asyncRecord(DesignerErrorMessage.UNEXCEPTED_START_FAILED.getId(),
DesignerErrorMessage.UNEXCEPTED_START_FAILED.getMessage(), DesignerErrorMessage.UNEXCEPTED_START_FAILED.getMessage(),
e.getMessage()); e.getMessage());
} finally {
loadingPanel.hide();
}
}
};
task.execute();
}); });
} }

BIN
designer-base/src/main/resources/com/fine/component/pop/loading.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

1
designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLaf.properties

@ -59,3 +59,4 @@ TemplateTabPaneUI=com.fine.theme.light.ui.FineTemplateTabPaneUI
ReportComponentCompositeUI=com.fine.theme.light.ui.FineReportComponentCompositeUI ReportComponentCompositeUI=com.fine.theme.light.ui.FineReportComponentCompositeUI
ColorButtonUI=com.fine.theme.light.ui.FineColorButtonUI ColorButtonUI=com.fine.theme.light.ui.FineColorButtonUI
HeaderPaneUI=com.fine.theme.light.ui.FineHeaderPaneUI HeaderPaneUI=com.fine.theme.light.ui.FineHeaderPaneUI
ToastPanelUI=com.fine.theme.light.ui.ToastPanelUI

62
designer-base/src/test/java/com/fr/design/gui/storybook/components/GlassLoaderStoryBoard.java

@ -0,0 +1,62 @@
package com.fr.design.gui.storybook.components;
import com.fine.component.popup.ImageChild;
import com.fine.component.popup.ProgressChild;
import com.fine.theme.utils.GlassLayerLoader;
import com.fr.design.gui.ibutton.UIButton;
import com.fr.design.gui.storybook.StoryBoard;
import com.fr.stable.StringUtils;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import java.awt.Font;
import static com.fine.swing.ui.layout.Layouts.cell;
/**
* GlassLoaderStoryBoard
*
* @author Levy.Xie
* @since 11.0
* Created on 2024/12/19
*/
public class GlassLoaderStoryBoard extends StoryBoard {
public GlassLoaderStoryBoard() {
super("遮罩动画");
add(
cell(createProgressBtn()),
cell(createImageBtn("GIF图片遮罩-1", null)),
cell(createImageBtn("GIF图片遮罩-2", "com/fr/design/gui/storybook/components/loadingBar.gif"))
);
}
private UIButton createProgressBtn() {
UIButton btn = new UIButton("进度条遮罩");
btn.addActionListener(e -> {
GlassLayerLoader.getInstance().runWithLoader(() -> {
Thread.sleep(3000);
return null;
}, (JFrame) SwingUtilities.getWindowAncestor(this), new ProgressChild("running...",
new Font("宋体", Font.PLAIN, 14)).setMaxWait(3));
});
return btn;
}
private UIButton createImageBtn(String name, String path) {
UIButton btn = new UIButton(name);
btn.addActionListener(e -> {
showGlassLoadingPane(path);
});
return btn;
}
private void showGlassLoadingPane(String gifPath) {
GlassLayerLoader.getInstance().runWithLoader(() -> {
Thread.sleep(3000);
return null;
}, (JFrame) SwingUtilities.getWindowAncestor(this), StringUtils.isEmpty(gifPath) ? new ImageChild() : new ImageChild(gifPath));
}
}

58
designer-base/src/test/java/com/fr/design/gui/storybook/components/ToastStoryBoard.java

@ -0,0 +1,58 @@
package com.fr.design.gui.storybook.components;
import com.fine.component.toast.ToastMsgManager;
import com.fr.design.gui.ibutton.UIButton;
import com.fr.design.gui.storybook.StoryBoard;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import java.util.Random;
import static com.fine.swing.ui.layout.Layouts.cell;
/**
* ToastStoryBoard
*
* @author Levy.Xie
* @since 11.0
* Created on 2024/12/19
*/
public class ToastStoryBoard extends StoryBoard {
public ToastStoryBoard() {
super("消息弹窗");
add(
cell(createToastButton(ToastMsgManager.Location.TOP_LEFT)),
cell(createToastButton(ToastMsgManager.Location.TOP_CENTER)),
cell(createToastButton(ToastMsgManager.Location.TOP_RIGHT)),
cell(createToastButton(ToastMsgManager.Location.BOTTOM_LEFT)),
cell(createToastButton(ToastMsgManager.Location.BOTTOM_CENTER)),
cell(createToastButton(ToastMsgManager.Location.BOTTOM_RIGHT))
);
}
private UIButton createToastButton(ToastMsgManager.Location location) {
UIButton button = new UIButton(location.name().toLowerCase());
button.addActionListener(e -> {
ToastMsgManager.getInstance().show(getRandomType(), location, "Hello FineReport",
SwingUtilities.getWindowAncestor(this));
});
return button;
}
private ToastMsgManager.Type getRandomType() {
Random ran = new Random();
int a = ran.nextInt(4);
if (a == 0) {
return ToastMsgManager.Type.SUCCESS;
} else if (a == 1) {
return ToastMsgManager.Type.INFO;
} else if (a == 2) {
return ToastMsgManager.Type.WARNING;
} else {
return ToastMsgManager.Type.ERROR;
}
}
}

BIN
designer-base/src/test/resources/com/fr/design/gui/storybook/components/loadingBar.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

7
designer-realize/src/main/java/com/fr/design/actions/replace/ui/ITReplaceMainDialog.java

@ -18,8 +18,7 @@ import com.fr.design.i18n.Toolkit;
import com.fr.design.mainframe.DesignerContext; import com.fr.design.mainframe.DesignerContext;
import com.fr.design.mainframe.JTemplate; import com.fr.design.mainframe.JTemplate;
import com.fr.design.mainframe.toast.DesignerToastMsgUtil; import com.fine.component.toast.ToastMsgManager;
import com.fr.design.mainframe.toast.ToastMsgDialog;
import com.fr.general.GeneralUtils; import com.fr.general.GeneralUtils;
import com.fr.stable.StringUtils; import com.fr.stable.StringUtils;
@ -212,6 +211,7 @@ public class ITReplaceMainDialog extends UIDialog {
* 模板内容替换相关 * 模板内容替换相关
*/ */
private void replace4Content() { private void replace4Content() {
checkLegalValidity();
String type = GeneralUtils.objectToString(northPane.getFindCombobox().getSelectedItem()); String type = GeneralUtils.objectToString(northPane.getFindCombobox().getSelectedItem());
String searchStr = ((UITextField) (northPane.getFindInputCombobox().getEditor().getEditorComponent())).getText(); String searchStr = ((UITextField) (northPane.getFindInputCombobox().getEditor().getEditorComponent())).getText();
String replaceStr = ((UITextField) (northPane.getReplaceInputCombobox().getEditor().getEditorComponent())).getText(); String replaceStr = ((UITextField) (northPane.getReplaceInputCombobox().getEditor().getEditorComponent())).getText();
@ -306,8 +306,7 @@ public class ITReplaceMainDialog extends UIDialog {
*/ */
public void checkLegalValidity() { public void checkLegalValidity() {
if (contentReplaceFailedCount == 0) { if (contentReplaceFailedCount == 0) {
ToastMsgDialog dialog = DesignerToastMsgUtil.createPromptDialog(Toolkit.i18nText("Fine-Design_Replace_Check_None")); ToastMsgManager.getInstance().info(Toolkit.i18nText("Fine-Design_Replace_Check_None"));
dialog.setVisible(true);
} else { } else {
new ITCheckDialog(); new ITCheckDialog();
} }

Loading…
Cancel
Save