Browse Source

REPORT-145112 feat:设计器交互动画-遮罩

fbp/feature
Levy.Xie-解安森 1 month ago
parent
commit
28174e194b
  1. 228
      designer-base/src/main/java/com/fine/component/popup/ComponentLayer.java
  2. 47
      designer-base/src/main/java/com/fine/component/popup/GlassPaneChild.java
  3. 131
      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. 32
      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. 85
      designer-base/src/main/java/com/fine/component/popup/WindowSnapshots.java
  8. 53
      designer-base/src/main/java/com/fine/theme/utils/FineUIUtils.java
  9. 103
      designer-base/src/main/java/com/fine/theme/utils/GlassLayerLoader.java
  10. 25
      designer-base/src/main/java/com/fr/design/EnvChangeEntrance.java
  11. 32
      designer-base/src/main/java/com/fr/start/ServerStarter.java
  12. 4
      designer-base/src/main/java/com/fr/start/server/DesignEmbedHelper.java
  13. 1
      designer-base/src/main/java/com/fr/start/server/FineEmbedServerMonitor.java
  14. 1
      designer-base/src/main/java/com/fr/startup/ui/StartupLoadingPanel.java
  15. 76
      designer-base/src/main/java/com/fr/startup/ui/StartupPageWindow.java
  16. BIN
      designer-base/src/main/resources/com/fine/component/pop/loading.gif
  17. 62
      designer-base/src/test/java/com/fr/design/gui/storybook/components/GlassLoaderStoryBoard.java
  18. BIN
      designer-base/src/test/resources/com/fr/design/gui/storybook/components/loadingBar.gif

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

@ -0,0 +1,228 @@
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;
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();
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 (!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) {
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);
}
if (nextImage != null) {
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);
}
g2.dispose();
g.drawImage(bufferedImage, 0, 0, null);
}
} else {
super.paint(g);
}
}
public GlassPaneChild getComponent() {
return component;
}
}

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

@ -0,0 +1,47 @@
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 {
public int getRoundBorder() {
return FlatUIUtils.getUIInt("Component.arc", 6);
}
/**
* 放入组件时的回调方法
*/
public void onPush() {
}
/**
* 弹出组件时的回调方法
*/
public void onPop() {
}
/**
* 显示弹窗组件时的回调方法
*/
public void popupShow() {
}
/**
* 关闭窗口时的回调方法
*/
public void onClose() {
}
}

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

@ -0,0 +1,131 @@
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() {
componentLayer.showSnapshot();
parent.windowSnapshots.createSnapshot();
parent.contentPane.setVisible(false);
}
@Override
public void end() {
componentLayer.hideSnapshot();
if (show) {
componentLayer.getComponent().popupShow();
} else {
parent.removePopup(GlassPopup.this);
}
parent.contentPane.setVisible(true);
parent.windowSnapshots.removeSnapshot();
}
});
animator.setInterpolator(CubicBezierEasing.EASE);
}
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);
}
}
}

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

@ -0,0 +1,32 @@
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 11.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) {
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();
}
}

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

@ -0,0 +1,85 @@
package com.fine.component.popup;
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 = 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();
}
};
snapshotLayer.setSize(layeredPane.getSize());
layeredPane.add(snapshotLayer, Integer.valueOf(JLayeredPane.DRAG_LAYER + 1));
}
}
}
}
/**
* 移除窗口快照覆盖层
*/
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;
}
}

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.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.UIScale;
import com.fr.design.border.FineBorderFactory;
import com.fr.design.constants.LayoutConstants;
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.stable.os.OperatingSystem;
import com.fr.value.AtomicClearableLazyValue;
import org.jetbrains.annotations.Nullable;
import javax.swing.JLabel;
import javax.swing.JLayer;
import javax.swing.ScrollPaneConstants;
import javax.swing.UIManager;
import javax.swing.JTextArea;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
@ -27,9 +30,13 @@ import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.lang.reflect.Field;
import static com.fine.swing.ui.layout.Layouts.cell;
@ -517,4 +524,50 @@ public class FineUIUtils {
DesignerFrame parent = DesignerContext.getDesignerFrame();
return new Dimension((int) (parent.getWidth() * width),(int) (parent.getHeight() * height));
}
/**
* 创建一个与指定组件大小相同的 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));
}
}

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

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

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

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

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

@ -48,15 +48,11 @@ public class DesignEmbedHelper {
public static synchronized void start() {
try {
FineEmbedServerMonitor.getInstance().reset();
//初始化tomcat
initTomcat();
tomcat.start();
} catch (LifecycleException 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
* @date 2018/8/21
*/
@Deprecated
public class FineEmbedServerMonitor {
private int progress;
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
* created by Harrison on 2022/11/08
**/
@Deprecated
public class StartupLoadingPanel {
/**

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Loading…
Cancel
Save