Levy.Xie-解安森
1 month ago
18 changed files with 1230 additions and 84 deletions
@ -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; |
||||
} |
||||
} |
@ -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() { |
||||
|
||||
} |
||||
|
||||
} |
@ -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"; |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
@ -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(); |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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)); |
||||
} |
||||
|
||||
} |
After Width: | Height: | Size: 33 KiB |
@ -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)); |
||||
} |
||||
|
||||
} |
After Width: | Height: | Size: 609 B |
Loading…
Reference in new issue