Browse Source
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
34 changed files with 2866 additions and 130 deletions
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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"; |
||||||
|
} |
||||||
|
} |
@ -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,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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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,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; |
||||||
|
} |
||||||
|
} |
@ -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--; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
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)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
After Width: | Height: | Size: 609 B |
Loading…
Reference in new issue