Levy.Xie-解安森
1 month ago
16 changed files with 1506 additions and 21 deletions
@ -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,122 @@ |
|||||||
|
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 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 || 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,374 @@ |
|||||||
|
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) { |
||||||
|
switch (e.getPropertyName()) { |
||||||
|
default: |
||||||
|
case TOAST_ICON: { |
||||||
|
JPanel c = (JPanel) e.getSource(); |
||||||
|
uninstallIconComponent(c); |
||||||
|
installIconComponent(c); |
||||||
|
refreshUI(c); |
||||||
|
break; |
||||||
|
} |
||||||
|
case TOAST_COMPONENT: { |
||||||
|
JPanel c = (JPanel) e.getSource(); |
||||||
|
uninstallComponent(c); |
||||||
|
installComponent(c); |
||||||
|
refreshUI(c); |
||||||
|
break; |
||||||
|
} |
||||||
|
case TOAST_SHOW_CLOSE_BUTTON: { |
||||||
|
JPanel c = (JPanel) e.getSource(); |
||||||
|
uninstallCloseButton(c); |
||||||
|
installCloseButton(c); |
||||||
|
refreshUI(c); |
||||||
|
break; |
||||||
|
} |
||||||
|
case STYLE: |
||||||
|
case STYLE_CLASS: { |
||||||
|
JPanel c = (JPanel) e.getSource(); |
||||||
|
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,193 @@ |
|||||||
|
package com.fine.theme.utils; |
||||||
|
|
||||||
|
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]; |
||||||
|
int historyIdx; |
||||||
|
int aSum; |
||||||
|
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; |
||||||
|
float hSumDivider = 1.0f / shadowSize; |
||||||
|
float vSumDivider = opacity / shadowSize; |
||||||
|
int[] hSumLookup = new int[256 * shadowSize]; |
||||||
|
for (int i = 0; i < hSumLookup.length; i++) { |
||||||
|
hSumLookup[i] = (int) (i * hSumDivider); |
||||||
|
} |
||||||
|
int[] vSumLookup = new int[256 * shadowSize]; |
||||||
|
for (int i = 0; i < vSumLookup.length; i++) { |
||||||
|
vSumLookup[i] = (int) (i * vSumDivider); |
||||||
|
} |
||||||
|
int srcOffset; |
||||||
|
for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) { |
||||||
|
for (historyIdx = 0; historyIdx < shadowSize; ) { |
||||||
|
aHistory[historyIdx++] = 0; |
||||||
|
} |
||||||
|
aSum = 0; |
||||||
|
historyIdx = 0; |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
for (int i = 0; i < shadowSize; i++) { |
||||||
|
int a = hSumLookup[aSum]; |
||||||
|
dstBuffer[dstOffset++] = a << 24; |
||||||
|
aSum -= aHistory[historyIdx]; |
||||||
|
if (++historyIdx >= shadowSize) { |
||||||
|
historyIdx -= shadowSize; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) { |
||||||
|
aSum = 0; |
||||||
|
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; |
||||||
|
} |
||||||
|
bufferOffset = x; |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
setPixels(dst, 0, 0, dstWidth, dstHeight, dstBuffer); |
||||||
|
return dst; |
||||||
|
} |
||||||
|
|
||||||
|
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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue