diff --git a/designer-base/src/main/java/com/fine/component/toast/ToastMsgHolder.java b/designer-base/src/main/java/com/fine/component/toast/ToastMsgHolder.java new file mode 100755 index 0000000000..3dcea733a0 --- /dev/null +++ b/designer-base/src/main/java/com/fine/component/toast/ToastMsgHolder.java @@ -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 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--; + } + } + } + } +} diff --git a/designer-base/src/main/java/com/fine/component/toast/ToastMsgManager.java b/designer-base/src/main/java/com/fine/component/toast/ToastMsgManager.java new file mode 100755 index 0000000000..c70cf828b7 --- /dev/null +++ b/designer-base/src/main/java/com/fine/component/toast/ToastMsgManager.java @@ -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 + * + *

+ * 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) + *

+ * 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 + *

+ * Toast.shadowColor Color + * Toast.shadowOpacity float 0.1f (default) + * Toast.shadowInsets Insets 0,0,6,6 (default) + *

+ * Toast.outlineColor Color + * Toast.foreground Color + * Toast.background Color + *

+ * 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 + *

+ * Toast.frameInsets Insets 10,10,10,10 (default) + * Toast.margin Insets 8,8,8,8 (default) + *

+ * 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> 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 list = lists.get(location); + return list == null ? 0 : list.size(); + } + + private synchronized void move(Rectangle rectangle) { + for (Map.Entry> 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> 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 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 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 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 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; + } + } +} diff --git a/designer-base/src/main/java/com/fine/component/toast/ToastPane.java b/designer-base/src/main/java/com/fine/component/toast/ToastPane.java new file mode 100755 index 0000000000..c6f386fabb --- /dev/null +++ b/designer-base/src/main/java/com/fine/component/toast/ToastPane.java @@ -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"; + } + } + +} diff --git a/designer-base/src/main/java/com/fine/theme/light/ui/ShadowBorder.java b/designer-base/src/main/java/com/fine/theme/light/ui/ShadowBorder.java new file mode 100755 index 0000000000..16cbad12a0 --- /dev/null +++ b/designer-base/src/main/java/com/fine/theme/light/ui/ShadowBorder.java @@ -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); + } +} + diff --git a/designer-base/src/main/java/com/fine/theme/light/ui/ToastPanelUI.java b/designer-base/src/main/java/com/fine/theme/light/ui/ToastPanelUI.java new file mode 100755 index 0000000000..dd079693a1 --- /dev/null +++ b/designer-base/src/main/java/com/fine/theme/light/ui/ToastPanelUI.java @@ -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 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> 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); + } + } + } + } +} diff --git a/designer-base/src/main/java/com/fine/theme/utils/FineClientProperties.java b/designer-base/src/main/java/com/fine/theme/utils/FineClientProperties.java index da8e252ca5..8b9ba8b718 100644 --- a/designer-base/src/main/java/com/fine/theme/utils/FineClientProperties.java +++ b/designer-base/src/main/java/com/fine/theme/utils/FineClientProperties.java @@ -33,6 +33,12 @@ public interface FineClientProperties extends FlatClientProperties { String COMBO_BOX_TYPE = "comboBoxType"; String ADAPTIVE_COMBO_BOX = "adaptiveComboBox"; + //---------------------------- Toast ---------------------------- + String TOAST_ICON = "Toast.icon"; + String TOAST_COMPONENT = "Toast.component"; + String TOAST_SHOW_CLOSE_BUTTON = "Toast.showCloseButton"; + String TOAST_CLOSE_CALLBACK = "Toast.closeCallback"; + int GROUP_BUTTON_POSITION_INNER = 0; int GROUP_BUTTON_POSITION_LEFT = 1; int GROUP_BUTTON_POSITION_RIGHT = 2; diff --git a/designer-base/src/main/java/com/fine/theme/utils/ShadowRenderer.java b/designer-base/src/main/java/com/fine/theme/utils/ShadowRenderer.java new file mode 100755 index 0000000000..801c4ef8b2 --- /dev/null +++ b/designer-base/src/main/java/com/fine/theme/utils/ShadowRenderer.java @@ -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); + } + } +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/file/DefaultTemplateTreeDefineProcessor.java b/designer-base/src/main/java/com/fr/design/file/DefaultTemplateTreeDefineProcessor.java index af018d032f..d13b69ead4 100644 --- a/designer-base/src/main/java/com/fr/design/file/DefaultTemplateTreeDefineProcessor.java +++ b/designer-base/src/main/java/com/fr/design/file/DefaultTemplateTreeDefineProcessor.java @@ -22,8 +22,7 @@ import com.fr.design.mainframe.manager.clip.TemplateTreeClipboard; import com.fr.design.mainframe.manager.search.TemplateDirTreeSearchManager; import com.fr.design.mainframe.manager.search.TemplateTreeSearchManager; import com.fr.design.mainframe.manager.search.searcher.control.pane.TemplateDirTreeSearchPane; -import com.fr.design.mainframe.toast.DesignerToastMsgUtil; -import com.fr.design.mainframe.toast.ToastMsgDialog; +import com.fine.component.toast.ToastMsgManager; import com.fr.design.utils.TemplateUtils; import com.fr.design.utils.gui.GUICoreUtils; import com.fr.file.filetree.FileNode; @@ -402,9 +401,7 @@ public class DefaultTemplateTreeDefineProcessor extends AbstractTemplateTreeDefi boolean moveSuccess = doMove(); dispose(); if (moveSuccess) { - ToastMsgDialog dialog = DesignerToastMsgUtil.createPromptDialog(Toolkit.i18nText("Fine-Design_Basic_Template_Moved_Success")); - dialog.setVisible(true); - + ToastMsgManager.getInstance().info(Toolkit.i18nText("Fine-Design_Basic_Template_Moved_Success")); DesignerFrameFileDealerPane.getInstance().getSelectedOperation().refresh(); SwingUtilities.invokeLater(() -> { LocateAction.gotoEditingTemplateLeaf(targetFile); diff --git a/designer-base/src/main/java/com/fr/design/mainframe/ComponentReuseNotifyUtil.java b/designer-base/src/main/java/com/fr/design/mainframe/ComponentReuseNotifyUtil.java index 2dc77bb62c..ac3b2b5932 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/ComponentReuseNotifyUtil.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/ComponentReuseNotifyUtil.java @@ -3,7 +3,7 @@ package com.fr.design.mainframe; import com.fr.design.DesignerEnvManager; import com.fr.design.i18n.Toolkit; import com.fr.design.mainframe.reuse.ComponentReuseNotificationInfo; -import com.fr.design.mainframe.toast.DesignerToastMsgUtil; +import com.fine.component.toast.ToastMsgManager; import com.fr.design.notification.SnapChat; import com.fr.design.notification.SnapChatFactory; import com.fr.design.notification.SnapChatKey; @@ -29,7 +29,7 @@ public class ComponentReuseNotifyUtil { } }); if (snapChat.hasRead()) { - DesignerToastMsgUtil.toastPrompt(Toolkit.i18nText("Fine-Design_Component_Reuse_Merge_Prompt")); + ToastMsgManager.getInstance().info(Toolkit.i18nText("Fine-Design_Component_Reuse_Merge_Prompt")); } ComponentReuseNotificationInfo.getInstance().setClickedWidgetLib(true); DesignerEnvManager.getEnvManager().saveXMLFile(); diff --git a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeProfilePane.java b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeProfilePane.java index 4e8273161f..a607f416c3 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeProfilePane.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/theme/TemplateThemeProfilePane.java @@ -16,7 +16,7 @@ import com.fr.design.mainframe.theme.dialog.TemplateThemeProfileDialog; import com.fr.design.mainframe.theme.edit.ui.LabelUtils; import com.fr.design.mainframe.theme.ui.AutoCheckTextField; import com.fr.design.mainframe.theme.ui.AutoCheckThemeNameTextField; -import com.fr.design.mainframe.toast.DesignerToastMsgUtil; +import com.fine.component.toast.ToastMsgManager; import com.fr.design.utils.gui.GUICoreUtils; import com.fr.log.FineLoggerFactory; import com.fr.stable.StringUtils; @@ -226,12 +226,10 @@ public abstract class TemplateThemeProfilePane extends saveButton.setEnabled(false); saveAsButton.setEnabled(true); actionListener.onSaved(config.cachedFetch(getName())); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - DesignerToastMsgUtil.toastPrompt(Toolkit.i18nText("Fine-Design_Basic_Template_Theme_Profile_Pane_Save_Successfully")); - profileDialog.exit(); - } + SwingUtilities.invokeLater(() -> { + ToastMsgManager.getInstance().info( + Toolkit.i18nText("Fine-Design_Basic_Template_Theme_Profile_Pane_Save_Successfully")); + profileDialog.exit(); }); } }); diff --git a/designer-base/src/main/java/com/fr/design/mainframe/toast/DesignerToastMsgUtil.java b/designer-base/src/main/java/com/fr/design/mainframe/toast/DesignerToastMsgUtil.java index fc326c60e6..25d36786fa 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/toast/DesignerToastMsgUtil.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/toast/DesignerToastMsgUtil.java @@ -21,6 +21,7 @@ import java.awt.Window; /** * Created by kerry on 5/6/21 */ +@Deprecated public class DesignerToastMsgUtil { private static final int MIN_WIDTH = 134; private static final int MAX_WIDTH = 454; diff --git a/designer-base/src/main/java/com/fr/design/style/background/image/ImageFileChooser.java b/designer-base/src/main/java/com/fr/design/style/background/image/ImageFileChooser.java index 739dc81d55..9ef470609a 100644 --- a/designer-base/src/main/java/com/fr/design/style/background/image/ImageFileChooser.java +++ b/designer-base/src/main/java/com/fr/design/style/background/image/ImageFileChooser.java @@ -7,7 +7,7 @@ import com.fr.design.DesignerEnvManager; import com.fr.design.gui.ifilechooser.FileChooserFactory; import com.fr.design.gui.ifilechooser.FileChooserProvider; import com.fr.design.i18n.Toolkit; -import com.fr.design.mainframe.toast.DesignerToastMsgUtil; +import com.fine.component.toast.ToastMsgManager; import java.awt.Component; @@ -56,7 +56,7 @@ public class ImageFileChooser { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - DesignerToastMsgUtil.toastWarning(Toolkit.i18nText("Fine-Design_Image_Compress_Move_Tip")); + ToastMsgManager.getInstance().warn(Toolkit.i18nText("Fine-Design_Image_Compress_Move_Tip")); DesignerEnvManager.getEnvManager().setShowImageCompressMoveTip(false); } }); diff --git a/designer-base/src/main/java/com/fr/start/server/DesignEmbedHelper.java b/designer-base/src/main/java/com/fr/start/server/DesignEmbedHelper.java index 057d312143..fc9fdff739 100644 --- a/designer-base/src/main/java/com/fr/start/server/DesignEmbedHelper.java +++ b/designer-base/src/main/java/com/fr/start/server/DesignEmbedHelper.java @@ -27,7 +27,6 @@ import java.util.HashSet; import java.util.Properties; import java.util.Set; -; /** * 内置服务器工具类 diff --git a/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLaf.properties b/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLaf.properties index 456d77ac89..8ef2ec289f 100644 --- a/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLaf.properties +++ b/designer-base/src/main/resources/com/fine/theme/light/ui/laf/FineLaf.properties @@ -59,3 +59,4 @@ TemplateTabPaneUI=com.fine.theme.light.ui.FineTemplateTabPaneUI ReportComponentCompositeUI=com.fine.theme.light.ui.FineReportComponentCompositeUI ColorButtonUI=com.fine.theme.light.ui.FineColorButtonUI HeaderPaneUI=com.fine.theme.light.ui.FineHeaderPaneUI +ToastPanelUI=com.fine.theme.light.ui.ToastPanelUI diff --git a/designer-base/src/test/java/com/fr/design/gui/storybook/components/ToastStoryBoard.java b/designer-base/src/test/java/com/fr/design/gui/storybook/components/ToastStoryBoard.java new file mode 100644 index 0000000000..4aeec9babc --- /dev/null +++ b/designer-base/src/test/java/com/fr/design/gui/storybook/components/ToastStoryBoard.java @@ -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; + } + } + +} diff --git a/designer-realize/src/main/java/com/fr/design/actions/replace/ui/ITReplaceMainDialog.java b/designer-realize/src/main/java/com/fr/design/actions/replace/ui/ITReplaceMainDialog.java index b9c3ac37d5..5c52440f85 100644 --- a/designer-realize/src/main/java/com/fr/design/actions/replace/ui/ITReplaceMainDialog.java +++ b/designer-realize/src/main/java/com/fr/design/actions/replace/ui/ITReplaceMainDialog.java @@ -18,8 +18,7 @@ import com.fr.design.i18n.Toolkit; import com.fr.design.mainframe.DesignerContext; import com.fr.design.mainframe.JTemplate; -import com.fr.design.mainframe.toast.DesignerToastMsgUtil; -import com.fr.design.mainframe.toast.ToastMsgDialog; +import com.fine.component.toast.ToastMsgManager; import com.fr.general.GeneralUtils; import com.fr.stable.StringUtils; @@ -212,6 +211,7 @@ public class ITReplaceMainDialog extends UIDialog { * 模板内容替换相关 */ private void replace4Content() { + checkLegalValidity(); String type = GeneralUtils.objectToString(northPane.getFindCombobox().getSelectedItem()); String searchStr = ((UITextField) (northPane.getFindInputCombobox().getEditor().getEditorComponent())).getText(); String replaceStr = ((UITextField) (northPane.getReplaceInputCombobox().getEditor().getEditorComponent())).getText(); @@ -306,8 +306,7 @@ public class ITReplaceMainDialog extends UIDialog { */ public void checkLegalValidity() { if (contentReplaceFailedCount == 0) { - ToastMsgDialog dialog = DesignerToastMsgUtil.createPromptDialog(Toolkit.i18nText("Fine-Design_Replace_Check_None")); - dialog.setVisible(true); + ToastMsgManager.getInstance().info(Toolkit.i18nText("Fine-Design_Replace_Check_None")); } else { new ITCheckDialog(); }