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